1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//! Macro-generated `from_f64` / `to_f64` / `to_f32`
//! for narrow decimal widths, with rounding-mode-aware variants.
//!
//! Two `from_f64` surfaces are emitted:
//!
//! - `from_f64(value)` — uses
//! `crate::rounding::DEFAULT_ROUNDING_MODE` (controlled by the
//! `rounding-*` Cargo features; `HalfToEven` by default).
//! - `from_f64_with(value, mode)` — explicit `RoundingMode`.
//!
//! Output saturation policy is uniform across modes: NaN -> ZERO,
//! +Infinity -> MAX, -Infinity -> MIN, finite out-of-range -> MAX/MIN
//! by sign. Float methods are `std`-only.
/// Emits `from_f64(value)`, `from_f64_with(value, mode)`,
/// `to_f64(self)`, `to_f32(self)` for a decimal type.
///
/// - `decl_decimal_float_bridge!(D9, i32)` — *native* storage; the
/// `f64` <-> storage conversions use `as`-casts.
/// - `decl_decimal_float_bridge!(wide D76, I256)` — *wide* storage;
/// the conversions use the `WideInt` cast. The experimental `f16` /
/// `f128` entry points route through `f64` for wide storage (the wide integer
/// only provides `f32` / `f64` casts), so they are lossier on the
/// wide tier than on D38-and-narrower.
macro_rules! decl_decimal_float_bridge {
// Wide storage.
(wide $Type:ident, $Storage:ty) => {
impl<const SCALE: u32> $Type<SCALE> {
/// Constructs from an `f64` using the crate default rounding
/// mode. NaN -> ZERO, +Infinity -> MAX, -Infinity -> MIN,
/// out-of-range -> saturate by sign.
#[cfg(feature = "std")]
#[inline]
#[must_use]
pub fn from_f64(value: f64) -> Self {
Self::from_f64_with(value, $crate::rounding::DEFAULT_ROUNDING_MODE)
}
/// Constructs from an `f64` using the supplied rounding
/// mode. Saturation policy as in [`Self::from_f64`].
#[cfg(feature = "std")]
#[inline]
#[must_use]
pub fn from_f64_with(value: f64, mode: $crate::rounding::RoundingMode) -> Self {
if value.is_nan() {
return Self::ZERO;
}
if value.is_infinite() {
return if value > 0.0 { Self::MAX } else { Self::MIN };
}
let mult_f64: f64 = Self::multiplier().as_f64();
let scaled = value * mult_f64;
let storage_max_f64: f64 = <$Storage>::MAX.as_f64();
let storage_min_f64: f64 = <$Storage>::MIN.as_f64();
if scaled >= storage_max_f64 {
return Self::MAX;
}
if scaled < storage_min_f64 {
return Self::MIN;
}
let rounded = match mode {
$crate::rounding::RoundingMode::HalfToEven => scaled.round_ties_even(),
$crate::rounding::RoundingMode::HalfAwayFromZero => scaled.round(),
$crate::rounding::RoundingMode::HalfTowardZero => {
if scaled >= 0.0 {
(scaled - 0.5).ceil()
} else {
(scaled + 0.5).floor()
}
}
$crate::rounding::RoundingMode::Trunc => scaled.trunc(),
$crate::rounding::RoundingMode::Floor => scaled.floor(),
$crate::rounding::RoundingMode::Ceiling => scaled.ceil(),
};
Self(<$Storage>::from_f64(rounded))
}
/// Converts to `f64` by dividing the raw storage by
/// `10^SCALE`. Lossy: an `f64` mantissa cannot hold the full
/// wide-storage precision.
#[inline]
#[must_use]
pub fn to_f64(self) -> f64 {
let raw_f64: f64 = self.0.as_f64();
let mult_f64: f64 = Self::multiplier().as_f64();
raw_f64 / mult_f64
}
/// Converts to `f32` via `f64`, then narrows.
#[inline]
#[must_use]
pub fn to_f32(self) -> f32 {
self.to_f64() as f32
}
/// Construct from an `f16` using the crate default rounding
/// mode. Routes through `f64`. Nightly + `experimental-floats`.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn from_f16(value: f16) -> Self {
Self::from_f64(value as f64)
}
/// Convert to `f16` (lossy). Nightly + `experimental-floats`.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn to_f16(self) -> f16 {
self.to_f64() as f16
}
/// Construct from an `f128` using the crate default rounding
/// mode. For wide storage this routes through `f64` (the wide integer
/// provides no `f128` cast), so it is lossier than the
/// D38-and-narrower path. Nightly + `experimental-floats`.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn from_f128(value: f128) -> Self {
Self::from_f128_with(value, $crate::rounding::DEFAULT_ROUNDING_MODE)
}
/// Construct from an `f128` using the supplied rounding mode.
/// Routes through `f64` for wide storage.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn from_f128_with(
value: f128,
mode: $crate::rounding::RoundingMode,
) -> Self {
Self::from_f64_with(value as f64, mode)
}
/// Convert to `f128`. Routes through `f64` for wide storage.
/// Nightly + `experimental-floats`.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn to_f128(self) -> f128 {
self.to_f64() as f128
}
}
};
// Native (primitive integer) storage.
($Type:ident, $Storage:ty) => {
impl<const SCALE: u32> $Type<SCALE> {
/// Constructs from an `f64` using the crate default rounding
/// mode (HalfToEven, or whichever a `rounding-*` Cargo
/// feature selects). NaN -> ZERO, +Infinity -> MAX,
/// -Infinity -> MIN, out-of-range -> saturate by sign.
#[cfg(feature = "std")]
#[inline]
#[must_use]
pub fn from_f64(value: f64) -> Self {
Self::from_f64_with(value, $crate::rounding::DEFAULT_ROUNDING_MODE)
}
/// Constructs from an `f64` using the supplied rounding
/// mode. Saturation policy as in [`Self::from_f64`].
#[cfg(feature = "std")]
#[inline]
#[must_use]
pub fn from_f64_with(value: f64, mode: $crate::rounding::RoundingMode) -> Self {
if value.is_nan() {
return Self::ZERO;
}
if value.is_infinite() {
return if value > 0.0 { Self::MAX } else { Self::MIN };
}
let scaled = value * (Self::multiplier() as f64);
let storage_max_f64 = <$Storage>::MAX as f64;
let storage_min_f64 = <$Storage>::MIN as f64;
if scaled >= storage_max_f64 {
return Self::MAX;
}
if scaled < storage_min_f64 {
return Self::MIN;
}
let rounded = match mode {
$crate::rounding::RoundingMode::HalfToEven => scaled.round_ties_even(),
$crate::rounding::RoundingMode::HalfAwayFromZero => scaled.round(),
$crate::rounding::RoundingMode::HalfTowardZero => {
// Round to nearest, ties toward zero. Bias the
// value by a half-LSB toward zero and then round.
if scaled >= 0.0 {
(scaled - 0.5).ceil()
} else {
(scaled + 0.5).floor()
}
}
$crate::rounding::RoundingMode::Trunc => scaled.trunc(),
$crate::rounding::RoundingMode::Floor => scaled.floor(),
$crate::rounding::RoundingMode::Ceiling => scaled.ceil(),
};
Self(rounded as $Storage)
}
/// Converts to `f64` by dividing the raw storage by `10^SCALE`.
/// Available in `no_std` because the `as f64` cast and float
/// division are part of `core`.
#[inline]
#[must_use]
pub fn to_f64(self) -> f64 {
(self.0 as f64) / (Self::multiplier() as f64)
}
/// Converts to `f32` via `f64`, then narrows. `no_std`-safe.
#[inline]
#[must_use]
pub fn to_f32(self) -> f32 {
self.to_f64() as f32
}
/// Construct from an `f16` using the crate default rounding
/// mode. Available only on nightly with the
/// `experimental-floats` feature.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn from_f16(value: f16) -> Self {
Self::from_f64(value as f64)
}
/// Convert to `f16` (lossy; f16 has only a 10-bit mantissa).
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn to_f16(self) -> f16 {
self.to_f64() as f16
}
/// Construct from an `f128` using the crate default rounding
/// mode. Available only on nightly with the
/// `experimental-floats` feature. The intermediate is `f128`
/// directly (no lossy `as f64` step) for the maximum
/// precision available before narrowing to the storage.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn from_f128(value: f128) -> Self {
Self::from_f128_with(value, $crate::rounding::DEFAULT_ROUNDING_MODE)
}
/// Construct from an `f128` using the supplied rounding mode.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn from_f128_with(
value: f128,
mode: $crate::rounding::RoundingMode,
) -> Self {
if value.is_nan() {
return Self::ZERO;
}
if value.is_infinite() {
return if value > 0.0 { Self::MAX } else { Self::MIN };
}
let scaled = value * (Self::multiplier() as f128);
let storage_max_f128 = <$Storage>::MAX as f128;
let storage_min_f128 = <$Storage>::MIN as f128;
if scaled >= storage_max_f128 {
return Self::MAX;
}
if scaled < storage_min_f128 {
return Self::MIN;
}
let rounded = match mode {
$crate::rounding::RoundingMode::HalfToEven => scaled.round_ties_even(),
$crate::rounding::RoundingMode::HalfAwayFromZero => scaled.round(),
$crate::rounding::RoundingMode::HalfTowardZero => {
if scaled >= 0.0 {
(scaled - 0.5).ceil()
} else {
(scaled + 0.5).floor()
}
}
$crate::rounding::RoundingMode::Trunc => scaled.trunc(),
$crate::rounding::RoundingMode::Floor => scaled.floor(),
$crate::rounding::RoundingMode::Ceiling => scaled.ceil(),
};
Self(rounded as $Storage)
}
/// Convert to `f128`. Lossless when the storage fits in the
/// f128 mantissa (113 bits), which holds for D38 and
/// narrower; D76 / D153 / D307 narrowing is lossy.
#[cfg(all(feature = "std", feature = "experimental-floats"))]
#[inline]
#[must_use]
pub fn to_f128(self) -> f128 {
(self.0 as f128) / (Self::multiplier() as f128)
}
}
};
}
pub(crate) use decl_decimal_float_bridge;