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