Skip to main content

nexus_decimal/
arithmetic.rs

1//! Checked, saturating, wrapping, and try arithmetic for `Decimal`.
2//!
3//! Add/Sub/Neg/Abs are shared via macro.
4//! Mul/Div differ per backing type:
5//! - i32: widen to i64, native division (LLVM magic multiply)
6//! - i64: widen to i128, chunked magic division for SCALE < 2^32
7//!   (3× u64 magic multiplies, ~14 cycles), native fallback otherwise
8//! - i128: 192-bit wide arithmetic (manual limb math)
9
10use crate::Decimal;
11use crate::error::{DivError, OverflowError};
12
13// ============================================================================
14// Add / Sub / Neg / Abs — shared across all backing types
15// ============================================================================
16
17macro_rules! impl_decimal_arithmetic {
18    ($backing:ty) => {
19        impl<const D: u8> Decimal<$backing, D> {
20            // ========================================================
21            // Default semantics (panic on overflow in debug, wrap in release)
22            // ========================================================
23
24            /// Computes the absolute value of `self`.
25            ///
26            /// # Overflow behavior
27            ///
28            /// The absolute value of `Self::MIN` cannot be represented as
29            /// a `Self`, and attempting to calculate it will cause an
30            /// overflow. This means that code in debug mode will trigger
31            /// a panic on this case and optimized code will return
32            /// `Self::MIN` without a panic.
33            ///
34            /// Matches the semantics of `<backing>::abs`. Use
35            /// [`checked_abs`](Self::checked_abs),
36            /// [`saturating_abs`](Self::saturating_abs), or
37            /// [`wrapping_abs`](Self::wrapping_abs) for explicit overflow
38            /// policies.
39            #[inline(always)]
40            pub const fn abs(self) -> Self {
41                Self {
42                    value: self.value.abs(),
43                }
44            }
45
46            // ========================================================
47            // Checked
48            // ========================================================
49
50            /// Checked addition. Returns `None` on overflow.
51            #[inline(always)]
52            pub const fn checked_add(self, rhs: Self) -> Option<Self> {
53                match self.value.checked_add(rhs.value) {
54                    Some(v) => Some(Self { value: v }),
55                    None => None,
56                }
57            }
58
59            /// Checked subtraction. Returns `None` on overflow.
60            #[inline(always)]
61            pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
62                match self.value.checked_sub(rhs.value) {
63                    Some(v) => Some(Self { value: v }),
64                    None => None,
65                }
66            }
67
68            /// Checked negation. Returns `None` if `self == MIN`.
69            #[inline(always)]
70            pub const fn checked_neg(self) -> Option<Self> {
71                match self.value.checked_neg() {
72                    Some(v) => Some(Self { value: v }),
73                    None => None,
74                }
75            }
76
77            /// Checked absolute value. Returns `None` if `self == MIN`.
78            #[inline(always)]
79            pub const fn checked_abs(self) -> Option<Self> {
80                if self.value >= 0 {
81                    Some(self)
82                } else {
83                    self.checked_neg()
84                }
85            }
86
87            // ========================================================
88            // Saturating
89            // ========================================================
90
91            /// Saturating addition. Clamps to `MIN`/`MAX` on overflow.
92            #[inline(always)]
93            pub const fn saturating_add(self, rhs: Self) -> Self {
94                Self {
95                    value: self.value.saturating_add(rhs.value),
96                }
97            }
98
99            /// Saturating subtraction.
100            #[inline(always)]
101            pub const fn saturating_sub(self, rhs: Self) -> Self {
102                Self {
103                    value: self.value.saturating_sub(rhs.value),
104                }
105            }
106
107            /// Saturating negation.
108            #[inline(always)]
109            pub const fn saturating_neg(self) -> Self {
110                Self {
111                    value: self.value.saturating_neg(),
112                }
113            }
114
115            /// Saturating absolute value.
116            #[inline(always)]
117            pub const fn saturating_abs(self) -> Self {
118                Self {
119                    value: self.value.saturating_abs(),
120                }
121            }
122
123            // ========================================================
124            // Wrapping
125            // ========================================================
126
127            /// Wrapping addition.
128            #[inline(always)]
129            pub const fn wrapping_add(self, rhs: Self) -> Self {
130                Self {
131                    value: self.value.wrapping_add(rhs.value),
132                }
133            }
134
135            /// Wrapping subtraction.
136            #[inline(always)]
137            pub const fn wrapping_sub(self, rhs: Self) -> Self {
138                Self {
139                    value: self.value.wrapping_sub(rhs.value),
140                }
141            }
142
143            /// Wrapping negation.
144            #[inline(always)]
145            pub const fn wrapping_neg(self) -> Self {
146                Self {
147                    value: self.value.wrapping_neg(),
148                }
149            }
150
151            /// Wrapping absolute value.
152            #[inline(always)]
153            pub const fn wrapping_abs(self) -> Self {
154                Self {
155                    value: self.value.wrapping_abs(),
156                }
157            }
158
159            // ========================================================
160            // Try (Result-returning) — add/sub/neg/abs
161            // ========================================================
162
163            /// Addition returning `Result`.
164            #[inline(always)]
165            pub const fn try_add(self, rhs: Self) -> Result<Self, OverflowError> {
166                match self.checked_add(rhs) {
167                    Some(v) => Ok(v),
168                    None => Err(OverflowError),
169                }
170            }
171
172            /// Subtraction returning `Result`.
173            #[inline(always)]
174            pub const fn try_sub(self, rhs: Self) -> Result<Self, OverflowError> {
175                match self.checked_sub(rhs) {
176                    Some(v) => Ok(v),
177                    None => Err(OverflowError),
178                }
179            }
180
181            /// Negation returning `Result`.
182            #[inline(always)]
183            pub const fn try_neg(self) -> Result<Self, OverflowError> {
184                match self.checked_neg() {
185                    Some(v) => Ok(v),
186                    None => Err(OverflowError),
187                }
188            }
189
190            /// Absolute value returning `Result`.
191            #[inline(always)]
192            pub const fn try_abs(self) -> Result<Self, OverflowError> {
193                match self.checked_abs() {
194                    Some(v) => Ok(v),
195                    None => Err(OverflowError),
196                }
197            }
198
199            // ========================================================
200            // Power-of-2 multiplication
201            // ========================================================
202
203            /// Multiply by `2^n` (left shift on the backing value).
204            ///
205            /// The `10^D` scale factor cancels because the multiplier is
206            /// dimensionless — multiplying the represented value by `2^n` is
207            /// exactly a left shift on the backing.
208            ///
209            /// # Overflow behavior
210            ///
211            /// Matches `<backing>::mul` semantics: debug builds panic on
212            /// overflow, release builds wrap (`wrapping_shl`, which masks
213            /// `n` to `n mod <backing>::BITS`). Use
214            /// [`checked_mul_pow2`](Self::checked_mul_pow2),
215            /// [`saturating_mul_pow2`](Self::saturating_mul_pow2), or
216            /// [`wrapping_mul_pow2`](Self::wrapping_mul_pow2) for explicit
217            /// overflow policies.
218            ///
219            /// In particular, `mul_pow2(v, BITS)` in release returns `v`
220            /// unchanged — the mask makes this a no-op, not a zeroing.
221            ///
222            /// # Codegen
223            ///
224            /// Lowers to a single backing-width shift in release builds
225            /// (both constant and variable `n`). For `i32` / `i64`
226            /// backings this is one instruction (~1 cycle); for `i128`
227            /// it expands to a branchless wide-shift sequence (`shld` +
228            /// `shl` on x86-64, ~4-5 cycles).
229            #[inline(always)]
230            pub const fn mul_pow2(self, n: u32) -> Self {
231                // `cfg!()` is const-evaluable; the unused branch is removed.
232                if cfg!(debug_assertions) {
233                    match self.checked_mul_pow2(n) {
234                        Some(v) => v,
235                        None => panic!("attempt to multiply with overflow"),
236                    }
237                } else {
238                    Self {
239                        value: self.value.wrapping_shl(n),
240                    }
241                }
242            }
243
244            /// Checked multiplication by `2^n`. Returns `None` on overflow.
245            ///
246            /// Uses leading-zero counting to detect overflow without
247            /// performing the shift first. For positive `v`, requires
248            /// `n < v.leading_zeros()`; for negative `v`, requires
249            /// `n < (!v).leading_zeros()`.
250            #[inline(always)]
251            pub const fn checked_mul_pow2(self, n: u32) -> Option<Self> {
252                if self.value == 0 {
253                    return Some(self);
254                }
255                let leading_sign_bits = if self.value >= 0 {
256                    self.value.leading_zeros()
257                } else {
258                    (!self.value).leading_zeros()
259                };
260                if n < leading_sign_bits {
261                    Some(Self {
262                        value: self.value.wrapping_shl(n),
263                    })
264                } else {
265                    None
266                }
267            }
268
269            /// Saturating multiplication by `2^n`. Clamps to
270            /// [`MAX`](Self::MAX) / [`MIN`](Self::MIN) on overflow.
271            #[inline(always)]
272            pub const fn saturating_mul_pow2(self, n: u32) -> Self {
273                match self.checked_mul_pow2(n) {
274                    Some(v) => v,
275                    None => {
276                        if self.value >= 0 {
277                            Self::MAX
278                        } else {
279                            Self::MIN
280                        }
281                    }
282                }
283            }
284
285            /// Wrapping multiplication by `2^n`.
286            ///
287            /// Silently wraps on overflow. Note that `wrapping_shl` masks
288            /// `n` to `n mod <backing>::BITS`, so e.g. shifting by `BITS`
289            /// is a no-op rather than zeroing the value.
290            #[inline(always)]
291            pub const fn wrapping_mul_pow2(self, n: u32) -> Self {
292                Self {
293                    value: self.value.wrapping_shl(n),
294                }
295            }
296
297            /// Multiplication by `2^n` returning `Result`.
298            #[inline(always)]
299            pub const fn try_mul_pow2(self, n: u32) -> Result<Self, OverflowError> {
300                match self.checked_mul_pow2(n) {
301                    Some(v) => Ok(v),
302                    None => Err(OverflowError),
303                }
304            }
305
306            // ========================================================
307            // Power-of-2 division
308            // ========================================================
309
310            /// Divide by `2^n` (truncate toward zero).
311            ///
312            /// Semantically identical to `/ 2^n`: truncates toward zero,
313            /// matching [`halve`](Self::halve), [`div10`](Self::div10),
314            /// [`div100`](Self::div100), and the rest of the division
315            /// surface. Invariant: `div_pow2(1) == halve()`.
316            ///
317            /// # Codegen
318            ///
319            /// Constant `n` folds to a branchless shift + sign-correction
320            /// sequence (~2 cycles on modern x86-64). Variable `n`
321            /// compiles to a hardware signed division (~8-12 cycles on
322            /// Ice Lake+ / Zen 3+) — use a constant when the shift
323            /// amount is known.
324            ///
325            /// # Panics
326            ///
327            /// Debug builds panic if `n >= <backing>::BITS`. Release
328            /// builds return [`ZERO`](Self::ZERO), which is the
329            /// mathematically correct result under truncate-toward-zero:
330            /// any value divided by `2^n` larger than its magnitude is 0.
331            #[inline(always)]
332            pub const fn div_pow2(self, n: u32) -> Self {
333                debug_assert!(n < <$backing>::BITS, "shift amount out of range");
334
335                if n >= <$backing>::BITS {
336                    // Release-mode safety net.
337                    return Self { value: 0 };
338                }
339                if n == <$backing>::BITS - 1 {
340                    // 2^(BITS-1) doesn't fit as positive signed.
341                    // value / 2^(BITS-1) truncated toward zero:
342                    //   value == MIN → -1; otherwise → 0
343                    return Self {
344                        value: if self.value == <$backing>::MIN { -1 } else { 0 },
345                    };
346                }
347                // n < BITS - 1: (1 << n) fits as positive signed.
348                Self {
349                    value: self.value / (1 << n),
350                }
351            }
352
353            // ========================================================
354            // Absolute difference
355            // ========================================================
356
357            /// Overflow-safe absolute difference: `|self - other|`.
358            ///
359            /// Returns `None` when the result would exceed
360            /// [`MAX`](Self::MAX) — this happens when the operands have
361            /// opposite signs near the rails, since `|MIN - MAX|` exceeds
362            /// `MAX` on every signed type.
363            ///
364            /// Named `checked_abs_diff` to match the crate's `checked_*`
365            /// convention for `Option`-returning operations. There is no
366            /// bare `abs_diff` — every call site must acknowledge the
367            /// overflow case. Stdlib's `<backing>::abs_diff` returns an
368            /// unsigned type to avoid overflow; since `Decimal` has no
369            /// unsigned variant, this returns `Option<Self>` instead.
370            #[inline(always)]
371            pub const fn checked_abs_diff(self, other: Self) -> Option<Self> {
372                let diff = if self.value >= other.value {
373                    self.value.checked_sub(other.value)
374                } else {
375                    other.value.checked_sub(self.value)
376                };
377                match diff {
378                    Some(v) => Some(Self { value: v }),
379                    None => None,
380                }
381            }
382        }
383    };
384}
385
386impl_decimal_arithmetic!(i32);
387impl_decimal_arithmetic!(i64);
388impl_decimal_arithmetic!(i128);
389
390// ============================================================================
391// Mul / Div — i32 (widen to i64, native division)
392// ============================================================================
393
394impl<const D: u8> Decimal<i32, D> {
395    /// Checked multiplication. Widens to i64, divides by SCALE.
396    #[inline(always)]
397    pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
398        // i32 * i32 always fits in i64 — no overflow possible
399        let product = (self.value as i64) * (rhs.value as i64);
400        let result = product / (Self::SCALE as i64);
401
402        if result > i32::MAX as i64 || result < i32::MIN as i64 {
403            None
404        } else {
405            Some(Self {
406                value: result as i32,
407            })
408        }
409    }
410
411    /// Checked division. Returns `None` if `rhs` is zero or result overflows.
412    #[inline(always)]
413    pub const fn checked_div(self, rhs: Self) -> Option<Self> {
414        if rhs.value == 0 {
415            return None;
416        }
417        let a = self.value as i64;
418        let b = rhs.value as i64;
419        let result = (a * Self::SCALE as i64) / b;
420
421        if result > i32::MAX as i64 || result < i32::MIN as i64 {
422            None
423        } else {
424            Some(Self {
425                value: result as i32,
426            })
427        }
428    }
429
430    /// Saturating multiplication.
431    #[inline(always)]
432    pub const fn saturating_mul(self, rhs: Self) -> Self {
433        let product = (self.value as i64) * (rhs.value as i64);
434        let result = product / (Self::SCALE as i64);
435
436        if result > i32::MAX as i64 {
437            Self::MAX
438        } else if result < i32::MIN as i64 {
439            Self::MIN
440        } else {
441            Self {
442                value: result as i32,
443            }
444        }
445    }
446
447    /// Wrapping multiplication.
448    #[inline(always)]
449    pub const fn wrapping_mul(self, rhs: Self) -> Self {
450        let product = (self.value as i64) * (rhs.value as i64);
451        Self {
452            value: (product / (Self::SCALE as i64)) as i32,
453        }
454    }
455
456    /// Saturating division.
457    #[inline(always)]
458    pub const fn saturating_div(self, rhs: Self) -> Self {
459        assert!(rhs.value != 0, "division by zero");
460        match self.checked_div(rhs) {
461            Some(v) => v,
462            None => {
463                if (self.value > 0) == (rhs.value > 0) {
464                    Self::MAX
465                } else {
466                    Self::MIN
467                }
468            }
469        }
470    }
471
472    /// Wrapping division.
473    #[inline(always)]
474    pub const fn wrapping_div(self, rhs: Self) -> Self {
475        assert!(rhs.value != 0, "division by zero");
476        let a = self.value as i64;
477        let b = rhs.value as i64;
478        Self {
479            value: ((a * Self::SCALE as i64) / b) as i32,
480        }
481    }
482
483    /// Multiply by a plain integer (no rescaling).
484    #[inline(always)]
485    pub const fn mul_int(self, rhs: i32) -> Option<Self> {
486        match self.value.checked_mul(rhs) {
487            Some(v) => Some(Self { value: v }),
488            None => None,
489        }
490    }
491
492    /// Fused multiply-add: `(self * mul) + add` with single rescaling.
493    #[inline(always)]
494    pub const fn mul_add(self, mul: Self, add: Self) -> Option<Self> {
495        let product = (self.value as i64) * (mul.value as i64);
496        let rescaled = product / (Self::SCALE as i64);
497        let result = rescaled + (add.value as i64);
498
499        if result > i32::MAX as i64 || result < i32::MIN as i64 {
500            None
501        } else {
502            Some(Self {
503                value: result as i32,
504            })
505        }
506    }
507
508    /// Multiplication returning `Result`.
509    #[inline(always)]
510    pub const fn try_mul(self, rhs: Self) -> Result<Self, OverflowError> {
511        match self.checked_mul(rhs) {
512            Some(v) => Ok(v),
513            None => Err(OverflowError),
514        }
515    }
516
517    /// Division returning `Result` with specific error.
518    #[inline(always)]
519    pub const fn try_div(self, rhs: Self) -> Result<Self, DivError> {
520        if rhs.value == 0 {
521            return Err(DivError::DivisionByZero);
522        }
523        match self.checked_div(rhs) {
524            Some(v) => Ok(v),
525            None => Err(DivError::Overflow),
526        }
527    }
528}
529
530// ============================================================================
531// Mul / Div — i64 (widen to i128, chunked magic division when SCALE < 2^32)
532// ============================================================================
533//
534// When SCALE < 2^32 (covers Decimal<i64, 1..=9>), uses chunked
535// u64 division (~14 cycles, 3 magic multiplies). Otherwise falls back to
536// native i128 division (__divti3 ~25 cycles). The const branch is
537// eliminated by LLVM — zero runtime cost for type selection.
538
539use crate::div_by_scale;
540
541impl<const D: u8> Decimal<i64, D> {
542    /// Whether this (i64, D) combination qualifies for chunked fast path.
543    const USE_CHUNKED: bool = (Self::SCALE as u64) < div_by_scale::CHUNK_THRESHOLD;
544
545    /// Divide an i128 product by SCALE, using the fast path when available.
546    #[inline(always)]
547    const fn div_product_by_scale(product: i128) -> Option<i64> {
548        div_by_scale::div_i128_by_scale(
549            product,
550            Self::SCALE as i128,
551            Self::SCALE as u64,
552            Self::USE_CHUNKED,
553        )
554    }
555
556    /// Wrapping version of SCALE division.
557    #[inline(always)]
558    const fn div_product_by_scale_wrapping(product: i128) -> i64 {
559        div_by_scale::div_i128_by_scale_wrapping(
560            product,
561            Self::SCALE as i128,
562            Self::SCALE as u64,
563            Self::USE_CHUNKED,
564        )
565    }
566
567    /// Checked multiplication. Widens to i128, divides by SCALE.
568    #[inline(always)]
569    pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
570        let a = self.value as i128;
571        let b = rhs.value as i128;
572
573        let Some(product) = a.checked_mul(b) else {
574            return None;
575        };
576
577        match Self::div_product_by_scale(product) {
578            Some(result) => Some(Self { value: result }),
579            None => None,
580        }
581    }
582
583    /// Checked division. Returns `None` if `rhs` is zero or result overflows.
584    ///
585    /// Division by a runtime value cannot use the chunked path — the
586    /// divisor isn't a compile-time constant. Uses native i128 division.
587    #[inline(always)]
588    pub const fn checked_div(self, rhs: Self) -> Option<Self> {
589        if rhs.value == 0 {
590            return None;
591        }
592        let a = self.value as i128;
593        let b = rhs.value as i128;
594        let result = (a * Self::SCALE as i128) / b;
595
596        if result > i64::MAX as i128 || result < i64::MIN as i128 {
597            None
598        } else {
599            Some(Self {
600                value: result as i64,
601            })
602        }
603    }
604
605    /// Saturating multiplication.
606    #[inline(always)]
607    pub const fn saturating_mul(self, rhs: Self) -> Self {
608        let product = (self.value as i128) * (rhs.value as i128);
609        match Self::div_product_by_scale(product) {
610            Some(result) => Self { value: result },
611            None => {
612                if product > 0 {
613                    Self::MAX
614                } else {
615                    Self::MIN
616                }
617            }
618        }
619    }
620
621    /// Wrapping multiplication.
622    #[inline(always)]
623    pub const fn wrapping_mul(self, rhs: Self) -> Self {
624        let product = (self.value as i128).wrapping_mul(rhs.value as i128);
625        Self {
626            value: Self::div_product_by_scale_wrapping(product),
627        }
628    }
629
630    /// Saturating division.
631    #[inline(always)]
632    pub const fn saturating_div(self, rhs: Self) -> Self {
633        assert!(rhs.value != 0, "division by zero");
634        match self.checked_div(rhs) {
635            Some(v) => v,
636            None => {
637                if (self.value > 0) == (rhs.value > 0) {
638                    Self::MAX
639                } else {
640                    Self::MIN
641                }
642            }
643        }
644    }
645
646    /// Wrapping division.
647    #[inline(always)]
648    pub const fn wrapping_div(self, rhs: Self) -> Self {
649        assert!(rhs.value != 0, "division by zero");
650        let a = self.value as i128;
651        let b = rhs.value as i128;
652        Self {
653            value: ((a * Self::SCALE as i128) / b) as i64,
654        }
655    }
656
657    /// Multiply by a plain integer (no rescaling).
658    #[inline(always)]
659    pub const fn mul_int(self, rhs: i64) -> Option<Self> {
660        match self.value.checked_mul(rhs) {
661            Some(v) => Some(Self { value: v }),
662            None => None,
663        }
664    }
665
666    /// Fused multiply-add: `(self * mul) + add` with single rescaling.
667    #[inline(always)]
668    pub const fn mul_add(self, mul: Self, add: Self) -> Option<Self> {
669        let a = self.value as i128;
670        let b = mul.value as i128;
671
672        let Some(product) = a.checked_mul(b) else {
673            return None;
674        };
675
676        let Some(rescaled) = Self::div_product_by_scale(product) else {
677            return None;
678        };
679        let rescaled = rescaled as i128;
680
681        let Some(result) = rescaled.checked_add(add.value as i128) else {
682            return None;
683        };
684
685        if result > i64::MAX as i128 || result < i64::MIN as i128 {
686            None
687        } else {
688            Some(Self {
689                value: result as i64,
690            })
691        }
692    }
693
694    /// Multiplication returning `Result`.
695    #[inline(always)]
696    pub const fn try_mul(self, rhs: Self) -> Result<Self, OverflowError> {
697        match self.checked_mul(rhs) {
698            Some(v) => Ok(v),
699            None => Err(OverflowError),
700        }
701    }
702
703    /// Division returning `Result` with specific error.
704    #[inline(always)]
705    pub const fn try_div(self, rhs: Self) -> Result<Self, DivError> {
706        if rhs.value == 0 {
707            return Err(DivError::DivisionByZero);
708        }
709        match self.checked_div(rhs) {
710            Some(v) => Ok(v),
711            None => Err(DivError::Overflow),
712        }
713    }
714}
715
716// ============================================================================
717// Mul / Div — i128 (192-bit wide arithmetic, NOT const fn)
718// ============================================================================
719
720use crate::wide;
721
722impl<const D: u8> Decimal<i128, D> {
723    /// Threshold for fast-path multiplication (both operands < 2^64).
724    const FAST_MUL_THRESHOLD: u128 = 1u128 << 64;
725
726    /// Checked multiplication using 192-bit wide arithmetic.
727    #[inline(always)]
728    pub fn checked_mul(self, rhs: Self) -> Option<Self> {
729        if self.value == 0 || rhs.value == 0 {
730            return Some(Self::ZERO);
731        }
732
733        let result_negative = (self.value < 0) != (rhs.value < 0);
734        let a = self.value.unsigned_abs();
735        let b = rhs.value.unsigned_abs();
736
737        // Fast path: both values fit in 64 bits → product fits in 128 bits
738        if a < Self::FAST_MUL_THRESHOLD && b < Self::FAST_MUL_THRESHOLD {
739            let product = a * b;
740            let quotient = product / (Self::SCALE as u128);
741            return Self::from_unsigned(quotient, result_negative);
742        }
743
744        // Slow path: 192-bit multiplication
745        let (prod_low, prod_high) = wide::mul_wide(a, b);
746        let quotient = wide::div_192_by_const(prod_low, prod_high, Self::SCALE as u128)?;
747        Self::from_unsigned(quotient, result_negative)
748    }
749
750    /// Checked division using 192-bit wide arithmetic.
751    #[inline(always)]
752    pub fn checked_div(self, rhs: Self) -> Option<Self> {
753        if rhs.value == 0 {
754            return None;
755        }
756        if self.value == 0 {
757            return Some(Self::ZERO);
758        }
759
760        let result_negative = (self.value < 0) != (rhs.value < 0);
761        let a = self.value.unsigned_abs();
762        let b = rhs.value.unsigned_abs();
763        let scale = Self::SCALE as u128;
764
765        // Widen: a * SCALE (can exceed 128 bits)
766        let (prod_low, prod_high) = wide::mul_u128_by_small(a, scale);
767
768        // Divide 192-bit by runtime divisor
769        let quotient = wide::div_192_by_u128(prod_low, prod_high, b)?;
770        Self::from_unsigned(quotient, result_negative)
771    }
772
773    /// Saturating multiplication.
774    #[inline(always)]
775    pub fn saturating_mul(self, rhs: Self) -> Self {
776        self.checked_mul(rhs).unwrap_or({
777            if (self.value > 0) == (rhs.value > 0) {
778                Self::MAX
779            } else {
780                Self::MIN
781            }
782        })
783    }
784
785    /// Wrapping multiplication.
786    #[inline(always)]
787    pub fn wrapping_mul(self, rhs: Self) -> Self {
788        if self.value == 0 || rhs.value == 0 {
789            return Self::ZERO;
790        }
791
792        let result_negative = (self.value < 0) != (rhs.value < 0);
793        let a = self.value.unsigned_abs();
794        let b = rhs.value.unsigned_abs();
795
796        let (prod_low, prod_high) = wide::mul_wide(a, b);
797        let quotient = wide::div_192_by_const_wrapping(prod_low, prod_high, Self::SCALE as u128);
798
799        if result_negative {
800            Self {
801                value: (quotient as i128).wrapping_neg(),
802            }
803        } else {
804            Self {
805                value: quotient as i128,
806            }
807        }
808    }
809
810    /// Saturating division.
811    #[inline(always)]
812    pub fn saturating_div(self, rhs: Self) -> Self {
813        assert!(rhs.value != 0, "division by zero");
814        self.checked_div(rhs).unwrap_or({
815            if (self.value > 0) == (rhs.value > 0) {
816                Self::MAX
817            } else {
818                Self::MIN
819            }
820        })
821    }
822
823    /// Wrapping division.
824    #[inline(always)]
825    pub fn wrapping_div(self, rhs: Self) -> Self {
826        assert!(rhs.value != 0, "division by zero");
827
828        let result_negative = (self.value < 0) != (rhs.value < 0);
829        let a = self.value.unsigned_abs();
830        let b = rhs.value.unsigned_abs();
831        let scale = Self::SCALE as u128;
832
833        let (prod_low, prod_high) = wide::mul_u128_by_small(a, scale);
834        let quotient = wide::div_192_by_u128_wrapping(prod_low, prod_high, b);
835
836        if result_negative {
837            Self {
838                value: (quotient as i128).wrapping_neg(),
839            }
840        } else {
841            Self {
842                value: quotient as i128,
843            }
844        }
845    }
846
847    /// Multiply by a plain integer (no rescaling).
848    #[inline(always)]
849    pub const fn mul_int(self, rhs: i128) -> Option<Self> {
850        match self.value.checked_mul(rhs) {
851            Some(v) => Some(Self { value: v }),
852            None => None,
853        }
854    }
855
856    /// Fused multiply-add: `(self * mul) + add` with single rescaling.
857    #[inline(always)]
858    pub fn mul_add(self, mul: Self, add: Self) -> Option<Self> {
859        if self.value == 0 || mul.value == 0 {
860            return Some(add);
861        }
862
863        let product = self.checked_mul(mul)?;
864        product.checked_add(add)
865    }
866
867    /// Multiplication returning `Result`.
868    #[inline(always)]
869    pub fn try_mul(self, rhs: Self) -> Result<Self, OverflowError> {
870        self.checked_mul(rhs).ok_or(OverflowError)
871    }
872
873    /// Division returning `Result` with specific error.
874    #[inline(always)]
875    pub fn try_div(self, rhs: Self) -> Result<Self, DivError> {
876        if rhs.value == 0 {
877            return Err(DivError::DivisionByZero);
878        }
879        self.checked_div(rhs).ok_or(DivError::Overflow)
880    }
881
882    /// Helper: convert unsigned quotient + sign to Decimal, with bounds check.
883    #[inline(always)]
884    fn from_unsigned(quotient: u128, negative: bool) -> Option<Self> {
885        if negative {
886            // i128::MIN.unsigned_abs() = i128::MAX + 1
887            if quotient > (i128::MAX as u128) + 1 {
888                return None;
889            }
890            Some(Self {
891                value: (quotient as i128).wrapping_neg(),
892            })
893        } else {
894            if quotient > i128::MAX as u128 {
895                return None;
896            }
897            Some(Self {
898                value: quotient as i128,
899            })
900        }
901    }
902}