Skip to main content

ancdec/ancdec128/
rounding.rs

1use super::AncDec128;
2use crate::util::pow10_128;
3use crate::wide::{divmod_u256, mul_wide};
4use crate::RoundMode;
5
6impl AncDec128 {
7    /// Rounds to the given number of decimal places using the specified mode.
8    pub fn round(&self, decimal_places: u8, mode: RoundMode) -> Self {
9        if mode == RoundMode::Fract {
10            return Self {
11                int: 0,
12                frac: self.frac,
13                scale: self.scale,
14                neg: self.neg,
15            };
16        }
17        if self.scale <= decimal_places {
18            return *self;
19        }
20
21        // combined = int * 10^scale + frac as u256
22        let (hi, lo) = mul_wide(self.int, pow10_128(self.scale));
23        let (lo, carry) = lo.overflowing_add(self.frac);
24        let combined = (hi + carry as u128, lo);
25
26        let cut = self.scale - decimal_places;
27        let divisor = pow10_128(cut);
28
29        // divmod_u256: combined / divisor -> (truncated_u256, remainder_u128)
30        let (truncated_256, remainder) = divmod_u256(combined.0, combined.1, divisor);
31        // truncated fits in u128 for practical values (int * 10^decimal_places + frac_part)
32        let truncated = truncated_256.1;
33
34        if self.should_round_up(truncated, remainder, divisor, mode) {
35            // from_combined with truncated + 1, carry into hi limb on overflow
36            let (lo, carry) = truncated.overflowing_add(1);
37            Self::from_combined((carry as u128, lo), decimal_places, self.neg)
38        } else {
39            Self::from_combined((0, truncated), decimal_places, self.neg)
40        }
41    }
42
43    fn should_round_up(
44        &self,
45        truncated: u128,
46        remainder: u128,
47        divisor: u128,
48        mode: RoundMode,
49    ) -> bool {
50        if remainder == 0 {
51            return false;
52        }
53        let half = divisor / 2;
54
55        match mode {
56            RoundMode::Floor => self.neg,
57            RoundMode::Ceil => !self.neg,
58            RoundMode::Truncate => false,
59            RoundMode::HalfUp => remainder >= half,
60            RoundMode::HalfDown => remainder > half,
61            RoundMode::HalfEven => remainder > half || (remainder == half && truncated % 2 == 1),
62            RoundMode::Fract => false,
63        }
64    }
65
66    /// Returns the largest integer less than or equal to `self`.
67    #[inline(always)]
68    pub fn floor(&self) -> Self {
69        self.round(0, RoundMode::Floor)
70    }
71    /// Returns the smallest integer greater than or equal to `self`.
72    #[inline(always)]
73    pub fn ceil(&self) -> Self {
74        self.round(0, RoundMode::Ceil)
75    }
76    /// Returns the integer part, truncating toward zero.
77    #[inline(always)]
78    pub fn trunc(&self) -> Self {
79        self.round(0, RoundMode::Truncate)
80    }
81    /// Returns the fractional part only.
82    #[inline(always)]
83    pub fn fract(&self) -> Self {
84        self.round(0, RoundMode::Fract)
85    }
86}