Skip to main content

icydb_schema/decimal/
arithmetic.rs

1use crate::decimal::{DEFAULT_DIVISION_SCALE, Decimal, MAX_SUPPORTED_SCALE};
2use std::{
3    cmp::Ordering,
4    iter::Sum,
5    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign},
6};
7
8impl Decimal {
9    fn checked_add_impl(self, rhs: Self) -> Option<Self> {
10        let target_scale = self.scale.max(rhs.scale);
11        let lhs = Self::align_to_scale(self.mantissa, self.scale, target_scale)?;
12        let rhs = Self::align_to_scale(rhs.mantissa, rhs.scale, target_scale)?;
13
14        Some(Self {
15            mantissa: lhs.checked_add(rhs)?,
16            scale: target_scale,
17        })
18    }
19
20    /// Checked addition; returns `None` when scale alignment or mantissa
21    /// addition overflows the fixed decimal representation.
22    #[must_use]
23    pub fn checked_add(self, rhs: Self) -> Option<Self> {
24        self.checked_add_impl(rhs)
25    }
26
27    /// Checked subtraction; returns `None` when negating the right side,
28    /// scale alignment, or mantissa addition overflows.
29    #[must_use]
30    pub fn checked_sub(self, rhs: Self) -> Option<Self> {
31        self.checked_add_impl(Self {
32            mantissa: rhs.mantissa.checked_neg()?,
33            scale: rhs.scale,
34        })
35    }
36
37    /// Checked multiplication; returns `None` when scale or mantissa
38    /// multiplication overflows the fixed decimal representation.
39    #[must_use]
40    pub fn checked_mul(self, rhs: Self) -> Option<Self> {
41        self.checked_mul_impl(rhs)
42    }
43
44    /// Checked division; returns `None` when the divisor is zero or the
45    /// rounded fixed-scale result cannot be represented.
46    #[must_use]
47    pub fn checked_div(self, rhs: Self) -> Option<Self> {
48        self.checked_div_impl(rhs)
49    }
50
51    fn checked_mul_impl(self, rhs: Self) -> Option<Self> {
52        let scale = self.scale.checked_add(rhs.scale)?;
53        let mantissa = self.mantissa.checked_mul(rhs.mantissa)?;
54        Self::checked_from_mantissa_scale(mantissa, scale)
55    }
56
57    fn checked_div_impl(self, rhs: Self) -> Option<Self> {
58        if rhs.is_zero() {
59            return None;
60        }
61
62        let lhs = self.normalize();
63        let rhs = rhs.normalize();
64        let mut target_scale = DEFAULT_DIVISION_SCALE;
65
66        // Retry at lower precision when intermediate scaling overflows i128.
67        loop {
68            if let Some((numerator, denominator)) = Self::division_operands(lhs, rhs, target_scale)
69            {
70                let mantissa = Self::div_round_half_away_from_zero(numerator, denominator)?;
71                if let Some(value) = Self::checked_from_mantissa_scale(mantissa, target_scale) {
72                    return Some(value.normalize());
73                }
74            }
75
76            if target_scale == 0 {
77                return None;
78            }
79
80            target_scale = target_scale.saturating_sub(1);
81        }
82    }
83
84    fn checked_rem_impl(self, rhs: Self) -> Option<Self> {
85        if rhs.is_zero() {
86            return None;
87        }
88
89        let target_scale = self.scale.max(rhs.scale);
90        let lhs = Self::align_to_scale(self.mantissa, self.scale, target_scale)?;
91        let rhs = Self::align_to_scale(rhs.mantissa, rhs.scale, target_scale)?;
92
93        Some(Self {
94            mantissa: lhs.checked_rem(rhs)?,
95            scale: target_scale,
96        })
97    }
98
99    /// Round to a given number of decimal places.
100    #[must_use]
101    pub const fn round_dp(&self, dp: u32) -> Self {
102        if self.scale <= dp {
103            return *self;
104        }
105
106        let diff = self.scale - dp;
107        let Some(divisor) = Self::checked_pow10(diff) else {
108            return *self;
109        };
110        let quotient = self.mantissa / divisor;
111        let remainder = self.mantissa % divisor;
112
113        // `divisor` is 10^diff and always positive here.
114        let should_round = remainder.unsigned_abs() >= divisor.unsigned_abs() / 2;
115        let rounded = if should_round {
116            if self.mantissa.is_negative() {
117                quotient.saturating_sub(1)
118            } else {
119                quotient.saturating_add(1)
120            }
121        } else {
122            quotient
123        };
124
125        Self {
126            mantissa: rounded,
127            scale: dp,
128        }
129    }
130
131    /// Truncate toward zero to a given number of decimal places.
132    #[must_use]
133    pub const fn trunc_dp(&self, dp: u32) -> Self {
134        if self.scale <= dp {
135            return *self;
136        }
137
138        let diff = self.scale - dp;
139        let Some(divisor) = Self::checked_pow10(diff) else {
140            return *self;
141        };
142
143        Self {
144            mantissa: self.mantissa / divisor,
145            scale: dp,
146        }
147    }
148
149    /// Return the absolute value of the decimal.
150    #[must_use]
151    pub const fn abs(&self) -> Self {
152        Self {
153            mantissa: self.mantissa.saturating_abs(),
154            scale: self.scale,
155        }
156    }
157
158    /// Return the greatest integral decimal less than or equal to the value.
159    #[must_use]
160    pub const fn floor_dp0(&self) -> Self {
161        if self.scale == 0 {
162            return *self;
163        }
164
165        let Some(divisor) = Self::checked_pow10(self.scale) else {
166            return *self;
167        };
168        let quotient = self.mantissa / divisor;
169        let remainder = self.mantissa % divisor;
170        let integer = if self.mantissa.is_negative() && remainder != 0 {
171            quotient.saturating_sub(1)
172        } else {
173            quotient
174        };
175
176        Self {
177            mantissa: integer,
178            scale: 0,
179        }
180    }
181
182    /// Return the least integral decimal greater than or equal to the value.
183    #[must_use]
184    pub const fn ceil_dp0(&self) -> Self {
185        if self.scale == 0 {
186            return *self;
187        }
188
189        let Some(divisor) = Self::checked_pow10(self.scale) else {
190            return *self;
191        };
192        let quotient = self.mantissa / divisor;
193        let remainder = self.mantissa % divisor;
194        let integer = if self.mantissa.is_positive() && remainder != 0 {
195            quotient.saturating_add(1)
196        } else {
197            quotient
198        };
199
200        Self {
201            mantissa: integer,
202            scale: 0,
203        }
204    }
205
206    /// Saturating addition.
207    #[must_use]
208    pub fn saturating_add(self, rhs: Self) -> Self {
209        if let Some(sum) = self.checked_add_impl(rhs) {
210            return sum;
211        }
212
213        let target_scale = self.scale.max(rhs.scale);
214
215        if self.is_sign_negative() == rhs.is_sign_negative() {
216            return Self::saturating_extreme(target_scale, self.is_sign_negative());
217        }
218
219        match self.cmp_decimal(&rhs) {
220            Ordering::Equal => Self {
221                mantissa: 0,
222                scale: target_scale,
223            },
224            Ordering::Greater => Self::saturating_extreme(target_scale, self.is_sign_negative()),
225            Ordering::Less => Self::saturating_extreme(target_scale, rhs.is_sign_negative()),
226        }
227    }
228
229    /// Saturating subtraction.
230    #[must_use]
231    pub fn saturating_sub(self, rhs: Self) -> Self {
232        self.saturating_add(Self {
233            mantissa: rhs.mantissa.saturating_neg(),
234            scale: rhs.scale,
235        })
236    }
237
238    /// Checked remainder; returns `None` on division by zero.
239    #[must_use]
240    pub fn checked_rem(self, rhs: Self) -> Option<Self> {
241        self.checked_rem_impl(rhs)
242    }
243
244    /// Checked absolute value; returns `None` for the one `i128::MIN`
245    /// mantissa case that cannot be represented as positive `i128`.
246    #[must_use]
247    pub const fn checked_abs(&self) -> Option<Self> {
248        let Some(mantissa) = self.mantissa.checked_abs() else {
249            return None;
250        };
251
252        Some(Self {
253            mantissa,
254            scale: self.scale,
255        })
256    }
257
258    /// Integer exponentiation.
259    #[must_use]
260    pub fn powu(&self, exp: u64) -> Self {
261        if exp == 0 {
262            return Self::new(1, 0);
263        }
264
265        let mut base = *self;
266        let mut power = exp;
267        let mut acc = Self::new(1, 0);
268
269        while power > 0 {
270            if power & 1 == 1 {
271                acc *= base;
272            }
273
274            power >>= 1;
275
276            if power > 0 {
277                base = base * base;
278            }
279        }
280
281        acc
282    }
283
284    /// Checked integer exponentiation using the same exponentiation-by-squaring
285    /// shape as `powu`, but failing instead of saturating on intermediate
286    /// multiplication overflow.
287    #[must_use]
288    pub fn checked_powu(&self, exp: u64) -> Option<Self> {
289        if exp == 0 {
290            return Some(Self::new(1, 0));
291        }
292
293        let mut base = *self;
294        let mut power = exp;
295        let mut acc = Self::new(1, 0);
296
297        while power > 0 {
298            if power & 1 == 1 {
299                acc = acc.checked_mul(base)?;
300            }
301
302            power >>= 1;
303
304            if power > 0 {
305                base = base.checked_mul(base)?;
306            }
307        }
308
309        Some(acc)
310    }
311
312    fn saturating_mul(self, rhs: Self) -> Self {
313        if self.is_zero() || rhs.is_zero() {
314            return Self::ZERO;
315        }
316
317        let scale = self
318            .scale
319            .saturating_add(rhs.scale)
320            .min(MAX_SUPPORTED_SCALE);
321        let negative = self.is_sign_negative() != rhs.is_sign_negative();
322        Self::saturating_extreme(scale, negative)
323    }
324
325    fn align_to_scale(mantissa: i128, current_scale: u32, target_scale: u32) -> Option<i128> {
326        if current_scale == target_scale {
327            return Some(mantissa);
328        }
329
330        let factor = Self::checked_pow10(target_scale.checked_sub(current_scale)?)?;
331        mantissa.checked_mul(factor)
332    }
333
334    // Prepare integer operands for fixed-scale decimal division.
335    fn division_operands(lhs: Self, rhs: Self, target_scale: u32) -> Option<(i128, i128)> {
336        let exponent = i64::from(target_scale) + i64::from(rhs.scale) - i64::from(lhs.scale);
337
338        if exponent >= 0 {
339            let factor = Self::checked_pow10(u32::try_from(exponent).ok()?)?;
340            let numerator = lhs.mantissa.checked_mul(factor)?;
341            return Some((numerator, rhs.mantissa));
342        }
343
344        let factor = Self::checked_pow10(u32::try_from(exponent.unsigned_abs()).ok()?)?;
345        let denominator = rhs.mantissa.checked_mul(factor)?;
346        Some((lhs.mantissa, denominator))
347    }
348
349    // Divide with round-half-away-from-zero semantics.
350    fn div_round_half_away_from_zero(numerator: i128, denominator: i128) -> Option<i128> {
351        if denominator == 0 {
352            return None;
353        }
354
355        let quotient = numerator / denominator;
356        let remainder = numerator % denominator;
357
358        if remainder == 0 {
359            return Some(quotient);
360        }
361
362        let twice_remainder = remainder.unsigned_abs().checked_mul(2)?;
363        if twice_remainder < denominator.unsigned_abs() {
364            return Some(quotient);
365        }
366
367        if (numerator < 0) == (denominator < 0) {
368            quotient.checked_add(1)
369        } else {
370            quotient.checked_sub(1)
371        }
372    }
373}
374
375impl Add for Decimal {
376    type Output = Self;
377
378    fn add(self, rhs: Self) -> Self::Output {
379        self.saturating_add(rhs)
380    }
381}
382
383impl AddAssign for Decimal {
384    fn add_assign(&mut self, rhs: Self) {
385        *self = *self + rhs;
386    }
387}
388
389impl Sub for Decimal {
390    type Output = Self;
391
392    fn sub(self, rhs: Self) -> Self::Output {
393        self.saturating_sub(rhs)
394    }
395}
396
397impl SubAssign for Decimal {
398    fn sub_assign(&mut self, rhs: Self) {
399        *self = *self - rhs;
400    }
401}
402
403impl Mul for Decimal {
404    type Output = Self;
405
406    fn mul(self, rhs: Self) -> Self::Output {
407        self.checked_mul_impl(rhs)
408            .unwrap_or_else(|| self.saturating_mul(rhs))
409    }
410}
411
412impl MulAssign for Decimal {
413    fn mul_assign(&mut self, rhs: Self) {
414        *self = *self * rhs;
415    }
416}
417
418impl Div for Decimal {
419    type Output = Self;
420
421    fn div(self, rhs: Self) -> Self::Output {
422        if rhs.is_zero() {
423            return Self::ZERO;
424        }
425
426        self.checked_div_impl(rhs).unwrap_or_else(|| {
427            let negative = self.is_sign_negative() != rhs.is_sign_negative();
428            Self::saturating_extreme(DEFAULT_DIVISION_SCALE, negative)
429        })
430    }
431}
432
433impl DivAssign for Decimal {
434    fn div_assign(&mut self, rhs: Self) {
435        *self = *self / rhs;
436    }
437}
438
439impl Rem for Decimal {
440    type Output = Self;
441
442    fn rem(self, rhs: Self) -> Self::Output {
443        self.checked_rem_impl(rhs).unwrap_or(Self::ZERO)
444    }
445}
446
447impl Sum for Decimal {
448    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
449        iter.fold(Self::ZERO, |acc, value| acc + value)
450    }
451}