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");