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}