bigdecimal/rounding.rs
1//! Rounding structures and subroutines
2#![allow(dead_code)]
3
4use crate::*;
5use crate::arithmetic::{add_carry, store_carry, extend_adding_with_carry};
6use stdlib;
7
8// const DEFAULT_ROUNDING_MODE: RoundingMode = ${RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE} or HalfUp;
9include!(concat!(env!("OUT_DIR"), "/default_rounding_mode.rs"));
10
11/// Determines how to calculate the last digit of the number
12///
13/// Default rounding mode is HalfUp
14///
15#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
16pub enum RoundingMode {
17 /// Always round away from zero
18 ///
19 ///
20 /// * 5.5 → 6.0
21 /// * 2.5 → 3.0
22 /// * 1.6 → 2.0
23 /// * 1.1 → 2.0
24 /// * -1.1 → -2.0
25 /// * -1.6 → -2.0
26 /// * -2.5 → -3.0
27 /// * -5.5 → -6.0
28 Up,
29
30 /// Always round towards zero
31 ///
32 /// * 5.5 → 5.0
33 /// * 2.5 → 2.0
34 /// * 1.6 → 1.0
35 /// * 1.1 → 1.0
36 /// * -1.1 → -1.0
37 /// * -1.6 → -1.0
38 /// * -2.5 → -2.0
39 /// * -5.5 → -5.0
40 Down,
41
42 /// Towards +∞
43 ///
44 /// * 5.5 → 6.0
45 /// * 2.5 → 3.0
46 /// * 1.6 → 2.0
47 /// * 1.1 → 2.0
48 /// * -1.1 → -1.0
49 /// * -1.6 → -1.0
50 /// * -2.5 → -2.0
51 /// * -5.5 → -5.0
52 Ceiling,
53
54 /// Towards -∞
55 ///
56 /// * 5.5 → 5.0
57 /// * 2.5 → 2.0
58 /// * 1.6 → 1.0
59 /// * 1.1 → 1.0
60 /// * -1.1 → -2.0
61 /// * -1.6 → -2.0
62 /// * -2.5 → -3.0
63 /// * -5.5 → -6.0
64 Floor,
65
66 /// Round to 'nearest neighbor', or up if ending decimal is 5
67 ///
68 /// * 5.5 → 6.0
69 /// * 2.5 → 3.0
70 /// * 1.6 → 2.0
71 /// * 1.1 → 1.0
72 /// * -1.1 → -1.0
73 /// * -1.6 → -2.0
74 /// * -2.5 → -3.0
75 /// * -5.5 → -6.0
76 HalfUp,
77
78 /// Round to 'nearest neighbor', or down if ending decimal is 5
79 ///
80 /// * 5.5 → 5.0
81 /// * 2.5 → 2.0
82 /// * 1.6 → 2.0
83 /// * 1.1 → 1.0
84 /// * -1.1 → -1.0
85 /// * -1.6 → -2.0
86 /// * -2.5 → -2.0
87 /// * -5.5 → -5.0
88 HalfDown,
89
90 /// Round to 'nearest neighbor', if equidistant, round towards
91 /// nearest even digit
92 ///
93 /// * 5.5 → 6.0
94 /// * 2.5 → 2.0
95 /// * 1.6 → 2.0
96 /// * 1.1 → 1.0
97 /// * -1.1 → -1.0
98 /// * -1.6 → -2.0
99 /// * -2.5 → -2.0
100 /// * -5.5 → -6.0
101 ///
102 HalfEven,
103}
104
105
106impl RoundingMode {
107 /// Perform the rounding operation
108 ///
109 /// Parameters
110 /// ----------
111 /// * sign (Sign) - Sign of the number to be rounded
112 /// * pair (u8, u8) - The two digits in question to be rounded.
113 /// i.e. to round 0.345 to two places, you would pass (4, 5).
114 /// As decimal digits, they
115 /// must be less than ten!
116 /// * trailing_zeros (bool) - True if all digits after the pair are zero.
117 /// This has an effect if the right hand digit is 0 or 5.
118 ///
119 /// Returns
120 /// -------
121 /// Returns the first number of the pair, rounded. The sign is not preserved.
122 ///
123 /// Examples
124 /// --------
125 /// - To round 2341, pass in `Plus, (4, 1), true` → get 4 or 5 depending on scheme
126 /// - To round -0.1051, to two places: `Minus, (0, 5), false` → returns either 0 or 1
127 /// - To round -0.1, pass in `true, (0, 1)` → returns either 0 or 1
128 ///
129 /// Calculation of pair of digits from full number, and the replacement of that number
130 /// should be handled separately
131 ///
132 pub fn round_pair(&self, sign: Sign, pair: (u8, u8), trailing_zeros: bool) -> u8 {
133 use self::RoundingMode::*;
134 use stdlib::cmp::Ordering::*;
135
136 let (lhs, rhs) = pair;
137 // if all zero after digit, never round
138 if rhs == 0 && trailing_zeros {
139 return lhs;
140 }
141 let up = lhs + 1;
142 let down = lhs;
143 match (*self, rhs.cmp(&5)) {
144 (Up, _) => up,
145 (Down, _) => down,
146 (Floor, _) => if sign == Sign::Minus { up } else { down },
147 (Ceiling, _) => if sign == Sign::Minus { down } else { up },
148 (_, Less) => down,
149 (_, Greater) => up,
150 (_, Equal) if !trailing_zeros => up,
151 (HalfUp, Equal) => up,
152 (HalfDown, Equal) => down,
153 (HalfEven, Equal) => if lhs % 2 == 0 { down } else { up },
154 }
155 }
156
157 /// Round digits, and if rounded up to 10, store 1 in carry and return zero
158 pub(crate) fn round_pair_with_carry(
159 &self,
160 sign: Sign,
161 pair: (u8, u8),
162 trailing_zeros: bool,
163 carry: &mut u8,
164 ) -> u8 {
165 let r = self.round_pair(sign, pair, trailing_zeros);
166 store_carry(r, carry)
167 }
168
169 /// Round value at particular digit, returning replacement digit
170 ///
171 /// Parameters
172 /// ----------
173 /// * at_digit (NonZeroU8) - 0-based index of digit at which to round.
174 /// 0 would be the first digit, and would
175 ///
176 /// * sign (Sign) - Sign of the number to be rounded
177 /// * value (u32) - The number containing digits to be rounded.
178 /// * trailing_zeros (bool) - True if all digits after the value are zero.
179 ///
180 /// Returns
181 /// -------
182 /// Returns the first number of the pair, rounded. The sign is not preserved.
183 ///
184 /// Examples
185 /// --------
186 /// - To round 823418, at digit-index 3: `3, Plus, 823418, true` → 823000 or 824000, depending on scheme
187 /// - To round -100205, at digit-index 1: `1, Minus, 100205, true` → 100200 or 100210
188 ///
189 /// Calculation of pair of digits from full number, and the replacement of that number
190 /// should be handled separately
191 ///
192 pub fn round_u32(
193 &self,
194 at_digit: stdlib::num::NonZeroU8,
195 sign: Sign,
196 value: u32,
197 trailing_zeros: bool,
198 ) -> u32 {
199 let shift = 10u32.pow(at_digit.get() as u32 - 1);
200 let splitter = shift * 10;
201
202 // split 'value' into high and low
203 let (top, bottom) = num_integer::div_rem(value, splitter);
204 let lhs = (top % 10) as u8;
205 let (rhs, remainder) = num_integer::div_rem(bottom, shift);
206 let pair = (lhs, rhs as u8);
207 let rounded = self.round_pair(sign, pair, trailing_zeros && remainder == 0);
208
209 // replace low digit with rounded value
210 let full = top - lhs as u32 + rounded as u32;
211
212 // shift rounded value back to position
213 full * splitter
214 }
215
216 /// Hint used to skip calculating trailing_zeros if they don't matter
217 fn needs_trailing_zeros(&self, insig_digit: u8) -> bool {
218 use RoundingMode::*;
219
220 // only need trailing zeros if the rounding digit is 0 or 5
221 if matches!(self, HalfUp | HalfDown | HalfEven) {
222 insig_digit == 5
223 } else {
224 insig_digit == 0
225 }
226 }
227
228}
229
230/// Return compile-time constant default rounding mode
231///
232/// Defined by RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE at compile time
233///
234impl Default for RoundingMode {
235 fn default() -> Self {
236 DEFAULT_ROUNDING_MODE
237 }
238}
239
240
241/// All non-digit information required to round digits
242///
243/// Just the mode and the sign.
244///
245#[derive(Debug,Clone,Copy)]
246pub(crate) struct NonDigitRoundingData {
247 /// Rounding mode
248 pub mode: RoundingMode,
249 /// Sign of digits
250 pub sign: Sign,
251}
252
253impl NonDigitRoundingData {
254 /// Round pair of digits, storing overflow (10) in the carry
255 pub fn round_pair(&self, pair: (u8, u8), trailing_zeros: bool) -> u8 {
256 self.mode.round_pair(self.sign, pair, trailing_zeros)
257 }
258
259 /// round-pair with carry-digits
260 pub fn round_pair_with_carry(&self, pair: (u8, u8), trailing_zeros: bool, carry: &mut u8) -> u8 {
261 self.mode.round_pair_with_carry(self.sign, pair, trailing_zeros, carry)
262 }
263
264 /// Use sign and default rounding mode
265 pub fn default_with_sign(sign: Sign) -> Self {
266 NonDigitRoundingData { sign, mode: RoundingMode::default() }
267 }
268}
269
270
271/// Relevant information about insignificant digits, used for rounding
272///
273/// If rounding at indicated point:
274///
275/// ```txt
276/// aaaaizzzzzzzz
277/// ^
278/// ```
279///
280/// 'a' values are significant, 'i' is the insignificant digit,
281/// and trailing_zeros is true if all 'z' are 0.
282///
283#[derive(Debug,Clone,Copy)]
284pub(crate) struct InsigData {
285 /// highest insignificant digit
286 pub digit: u8,
287
288 /// true if all digits more insignificant than 'digit' is zero
289 ///
290 /// This is only useful if relevant for the rounding mode, it
291 /// may be 'wrong' in these cases.
292 pub trailing_zeros: bool,
293
294 /// rounding-mode and sign
295 pub rounding_data: NonDigitRoundingData
296}
297
298impl InsigData {
299 /// Build from insig data and lazily calculated trailing-zeros callable
300 pub fn from_digit_and_lazy_trailing_zeros(
301 rounder: NonDigitRoundingData,
302 insig_digit: u8,
303 calc_trailing_zeros: impl FnOnce() -> bool
304 ) -> Self {
305 Self {
306 digit: insig_digit,
307 trailing_zeros: rounder.mode.needs_trailing_zeros(insig_digit) && calc_trailing_zeros(),
308 rounding_data: rounder,
309 }
310 }
311
312 /// Build from slice of insignificant little-endian digits
313 pub fn from_digit_slice(rounder: NonDigitRoundingData, digits: &[u8]) -> Self {
314 match digits.split_last() {
315 Some((&d0, trailing)) => {
316 Self::from_digit_and_lazy_trailing_zeros(
317 rounder, d0, || trailing.iter().all(Zero::is_zero)
318 )
319 }
320 None => {
321 Self {
322 digit: 0,
323 trailing_zeros: true,
324 rounding_data: rounder,
325 }
326 }
327 }
328 }
329
330 /// from sum of overlapping digits, (a is longer than b)
331 pub fn from_overlapping_digits_backward_sum(
332 rounder: NonDigitRoundingData,
333 mut a_digits: stdlib::iter::Rev<stdlib::slice::Iter<u8>>,
334 mut b_digits: stdlib::iter::Rev<stdlib::slice::Iter<u8>>,
335 carry: &mut u8,
336 ) -> Self {
337 debug_assert!(a_digits.len() >= b_digits.len());
338 debug_assert_eq!(carry, &0);
339
340 // most-significant insignificant digit
341 let insig_digit;
342 match (a_digits.next(), b_digits.next()) {
343 (Some(a), Some(b)) => {
344 // store 'full', initial sum, we will handle carry below
345 insig_digit = a + b;
346 }
347 (Some(d), None) | (None, Some(d)) => {
348 insig_digit = *d;
349 }
350 (None, None) => {
351 // both digit slices were empty; all zeros
352 return Self {
353 digit: 0,
354 trailing_zeros: true,
355 rounding_data: rounder,
356 };
357 }
358 };
359
360 // find first non-nine value
361 let mut sum = 9;
362 while sum == 9 {
363 let next_a = a_digits.next().unwrap_or(&0);
364 let next_b = b_digits.next().unwrap_or(&0);
365 sum = next_a + next_b;
366 }
367
368 // if previous sum was greater than ten,
369 // the one would carry through all the 9s
370 let sum = store_carry(sum, carry);
371
372 // propagate carry to the highest insignificant digit
373 let insig_digit = add_carry(insig_digit, carry);
374
375 // if the last 'sum' value isn't zero, or if any remaining
376 // digit is not zero, then it's not trailing zeros
377 let trailing_zeros = sum == 0
378 && rounder.mode.needs_trailing_zeros(insig_digit)
379 && a_digits.all(Zero::is_zero)
380 && b_digits.all(Zero::is_zero);
381
382 Self {
383 digit: insig_digit,
384 trailing_zeros: trailing_zeros,
385 rounding_data: rounder,
386 }
387 }
388
389 pub fn round_digit(&self, digit: u8) -> u8 {
390 self.rounding_data.round_pair((digit, self.digit), self.trailing_zeros)
391 }
392
393 pub fn round_digit_with_carry(&self, digit: u8, carry: &mut u8) -> u8 {
394 self.rounding_data.round_pair_with_carry((digit, self.digit), self.trailing_zeros, carry)
395 }
396
397 pub fn round_slice_into(&self, dest: &mut Vec<u8>, digits: &[u8]) {
398 let (&d0, rest) = digits.split_first().unwrap_or((&0, &[]));
399 let digits = rest.iter().copied();
400 let mut carry = 0;
401 let r0 = self.round_digit_with_carry(d0, &mut carry);
402 dest.push(r0);
403 extend_adding_with_carry(dest, digits, &mut carry);
404 if !carry.is_zero() {
405 dest.push(carry);
406 }
407 }
408
409 #[allow(dead_code)]
410 pub fn round_slice_into_with_carry(&self, dest: &mut Vec<u8>, digits: &[u8], carry: &mut u8) {
411 let (&d0, rest) = digits.split_first().unwrap_or((&0, &[]));
412 let digits = rest.iter().copied();
413 let r0 = self.round_digit_with_carry(d0, carry);
414 dest.push(r0);
415
416 extend_adding_with_carry(dest, digits, carry);
417 }
418}
419
420
421#[cfg(test)]
422include!("rounding.tests.rs");