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
//! Macro-generated `rescale` / `rescale_with` for all decimal widths.
//!
//! The body lives in `rescale_with`, which takes an explicit
//! `RoundingMode`. The no-arg `rescale` delegates to it with the
//! crate's `DEFAULT_ROUNDING_MODE`, which is `HalfToEven` unless a
//! `rounding-*` Cargo feature selects something else.
//!
//! - The *native* arm emits `rescale` / `rescale_with` as `const fn`,
//! since primitive integer arithmetic is `const`.
//! - The *wide* arm emits them as ordinary `fn`: the wide integer's `Div` / `Rem`
//! operators are not `const`, so the wide rescale path cannot be a
//! `const fn`. The semantics are otherwise identical.
/// Emits `rescale` (no-arg, uses `DEFAULT_ROUNDING_MODE`) and
/// `rescale_with` (explicit mode) methods for `$Type<SCALE>` with
/// storage `$Storage`.
macro_rules! decl_decimal_rescale {
// Wide storage. Not `const` — the wide integer's `Div`/`Rem`
// operators are not const fns.
(wide $Type:ident, $Storage:ty) => {
impl<const SCALE: u32> $Type<SCALE> {
/// Rescales to `TARGET_SCALE` using the crate's default
/// rounding mode (`HalfToEven`, or whatever a `rounding-*`
/// Cargo feature selects). Delegates to [`Self::rescale_with`].
#[inline]
#[must_use]
pub fn rescale<const TARGET_SCALE: u32>(self) -> $Type<TARGET_SCALE> {
self.rescale_with::<TARGET_SCALE>($crate::rounding::DEFAULT_ROUNDING_MODE)
}
/// Builder-style alias for [`Self::rescale`].
///
/// Returns a new value at `TARGET_SCALE` using the crate's
/// default rounding mode. Use [`Self::rescale_with`] when
/// you need to pass an explicit [`RoundingMode`].
///
/// [`RoundingMode`]: $crate::rounding::RoundingMode
#[inline]
#[must_use]
pub fn with_scale<const TARGET_SCALE: u32>(self) -> $Type<TARGET_SCALE> {
self.rescale::<TARGET_SCALE>()
}
/// Rescales to `TARGET_SCALE` using the supplied rounding
/// mode.
///
/// - `TARGET_SCALE == SCALE`: bit-identity.
/// - `TARGET_SCALE > SCALE`: scale-up multiplies by
/// `10^(TARGET - SCALE)`; lossless; panics on overflow.
/// - `TARGET_SCALE < SCALE`: scale-down divides by
/// `10^(SCALE - TARGET)` with the requested rounding rule.
#[inline]
#[must_use]
pub fn rescale_with<const TARGET_SCALE: u32>(
self,
mode: $crate::rounding::RoundingMode,
) -> $Type<TARGET_SCALE> {
if TARGET_SCALE == SCALE {
return $Type::<TARGET_SCALE>(self.0);
}
let ten = <$Storage>::from_str_radix("10", 10)
.expect("wide decimal: invalid base-10 literal");
let one = <$Storage>::from_str_radix("1", 10)
.expect("wide decimal: invalid base-10 literal");
let zero = <$Storage>::from_str_radix("0", 10)
.expect("wide decimal: invalid base-10 literal");
if TARGET_SCALE > SCALE {
let shift = TARGET_SCALE - SCALE;
let multiplier = ten.pow(shift);
let result = match self.0.checked_mul(multiplier) {
Some(v) => v,
None => panic!(concat!(stringify!($Type), "::rescale: scale-up overflow")),
};
return $Type::<TARGET_SCALE>(result);
}
let shift = SCALE - TARGET_SCALE;
let divisor = ten.pow(shift);
let raw = self.0;
let quotient = raw / divisor;
let remainder = raw % divisor;
if remainder == zero {
return $Type::<TARGET_SCALE>(quotient);
}
let abs_rem = remainder.unsigned_abs();
let half = divisor.unsigned_abs() >> 1;
let non_negative = !raw.is_negative();
let bits = match mode {
$crate::rounding::RoundingMode::HalfToEven => {
if abs_rem < half {
quotient
} else if abs_rem > half {
if non_negative { quotient + one } else { quotient - one }
} else if !quotient.bit(0) {
quotient
} else if non_negative {
quotient + one
} else {
quotient - one
}
}
$crate::rounding::RoundingMode::HalfAwayFromZero => {
if abs_rem < half {
quotient
} else if non_negative {
quotient + one
} else {
quotient - one
}
}
$crate::rounding::RoundingMode::HalfTowardZero => {
if abs_rem > half {
if non_negative { quotient + one } else { quotient - one }
} else {
quotient
}
}
$crate::rounding::RoundingMode::Trunc => quotient,
$crate::rounding::RoundingMode::Floor => {
if non_negative { quotient } else { quotient - one }
}
$crate::rounding::RoundingMode::Ceiling => {
if non_negative { quotient + one } else { quotient }
}
};
$Type::<TARGET_SCALE>(bits)
}
}
};
// Native (primitive integer) storage.
($Type:ident, $Storage:ty) => {
impl<const SCALE: u32> $Type<SCALE> {
/// Rescales to `TARGET_SCALE` using the crate's default
/// rounding mode (`HalfToEven`, or whatever a
/// `rounding-*` Cargo feature selects).
///
/// Delegates to [`Self::rescale_with`]; see that method for
/// scale-up / scale-down semantics and the overflow policy.
#[inline]
#[must_use]
pub const fn rescale<const TARGET_SCALE: u32>(self) -> $Type<TARGET_SCALE> {
self.rescale_with::<TARGET_SCALE>($crate::rounding::DEFAULT_ROUNDING_MODE)
}
/// Builder-style alias for [`Self::rescale`].
///
/// Returns a new value at `TARGET_SCALE` using the crate's
/// default rounding mode. Use [`Self::rescale_with`] when
/// you need to pass an explicit [`RoundingMode`].
///
/// [`RoundingMode`]: $crate::rounding::RoundingMode
#[inline]
#[must_use]
pub const fn with_scale<const TARGET_SCALE: u32>(self) -> $Type<TARGET_SCALE> {
self.rescale::<TARGET_SCALE>()
}
/// Rescales to `TARGET_SCALE` using the supplied rounding
/// mode.
///
/// - `TARGET_SCALE == SCALE`: bit-identity.
/// - `TARGET_SCALE > SCALE`: scale-up multiplies by
/// `10^(TARGET - SCALE)`; lossless; panics on overflow.
/// - `TARGET_SCALE < SCALE`: scale-down divides by
/// `10^(SCALE - TARGET)` with the requested rounding rule.
#[inline]
#[must_use]
pub const fn rescale_with<const TARGET_SCALE: u32>(
self,
mode: $crate::rounding::RoundingMode,
) -> $Type<TARGET_SCALE> {
if TARGET_SCALE == SCALE {
return $Type::<TARGET_SCALE>(self.0);
}
if TARGET_SCALE > SCALE {
let shift = TARGET_SCALE - SCALE;
let multiplier = (10 as $Storage).pow(shift);
let result = match self.0.checked_mul(multiplier) {
Some(v) => v,
None => panic!(concat!(stringify!($Type), "::rescale: scale-up overflow")),
};
return $Type::<TARGET_SCALE>(result);
}
let shift = SCALE - TARGET_SCALE;
let divisor = (10 as $Storage).pow(shift);
let raw = self.0;
let quotient = raw / divisor;
let remainder = raw % divisor;
if remainder == 0 {
return $Type::<TARGET_SCALE>(quotient);
}
let abs_rem = remainder.unsigned_abs();
let half = (divisor / 2) as _;
let bits = match mode {
$crate::rounding::RoundingMode::HalfToEven => {
if abs_rem < half {
quotient
} else if abs_rem > half {
if raw >= 0 { quotient + 1 } else { quotient - 1 }
} else if quotient % 2 == 0 {
quotient
} else if raw >= 0 {
quotient + 1
} else {
quotient - 1
}
}
$crate::rounding::RoundingMode::HalfAwayFromZero => {
if abs_rem < half {
quotient
} else if raw >= 0 {
quotient + 1
} else {
quotient - 1
}
}
$crate::rounding::RoundingMode::HalfTowardZero => {
if abs_rem > half {
if raw >= 0 { quotient + 1 } else { quotient - 1 }
} else {
quotient
}
}
$crate::rounding::RoundingMode::Trunc => quotient,
$crate::rounding::RoundingMode::Floor => {
if raw >= 0 { quotient } else { quotient - 1 }
}
$crate::rounding::RoundingMode::Ceiling => {
if raw >= 0 { quotient + 1 } else { quotient }
}
};
$Type::<TARGET_SCALE>(bits)
}
}
};
}
pub(crate) use decl_decimal_rescale;