Skip to main content

decimal_scaled/
overflow_variants.rs

1//! Overflow-aware arithmetic variants for [`D38`].
2//!
3//! Provides the standard Rust explicit-overflow method families as
4//! inherent methods on [`D38<SCALE>`], covering six operations:
5//!
6//! - `add`, `sub`, `mul`, `div`, `rem` (binary)
7//! - `neg` (unary)
8//!
9//! Each operation is available in four forms:
10//!
11//! - **`checked_*`** — returns `Option<Self>`; `None` on overflow or
12//! div-by-zero; never panics.
13//! - **`wrapping_*`** — returns `Self` with two's-complement wrap on
14//! overflow; never panics. `div` and `rem` variants still panic on
15//! `rhs == ZERO` to match `i128::wrapping_div` / `i128::wrapping_rem`
16//! semantics.
17//! - **`saturating_*`** — clamps to `MAX` / `MIN` on overflow; never
18//! panics. No `saturating_rem` is provided because remainder is always
19//! bounded by `|rhs|` and the standard library does not define one for
20//! primitive integers. `div` variants still panic on `rhs == ZERO`.
21//! - **`overflowing_*`** — returns `(Self, bool)` where the `bool`
22//! indicates overflow and `Self` is the wrapping result. `div` and
23//! `rem` variants panic on `rhs == ZERO`.
24//!
25//! # Algorithm notes
26//!
27//! - `add`, `sub`, `neg`, `rem` variants delegate directly to the
28//! corresponding `i128` intrinsics on the raw storage field.
29//! - `mul` variants route through the same widening multiply-then-divide
30//! helper (`crate::mg_divide::mul_div_pow10`) as the default `Mul`
31//! operator. The intermediate product uses 256-bit arithmetic and
32//! cannot observably overflow; the only failure mode is a final `i128`
33//! quotient that does not fit.
34//! - `div` variants route through the same widening long-divide helper
35//! (`crate::mg_divide::div_pow10_div`) as the default `Div` operator.
36
37use crate::core_type::D38;
38
39impl<const SCALE: u32> D38<SCALE> {
40    // Mul (rescale-aware via widening multiply-then-divide)
41    //
42    // All mul variants share the same widening boundary as the default
43    // `Mul` operator. The helper `mul_div_pow10::<SCALE>` returns
44    // `Some(q)` when the final i128 quotient fits, or `None` on overflow.
45    // The four variants differ only in how they handle that `None`.
46
47    /// Returns `self * rhs`, or `None` if the rescaled product does not
48    /// fit in `i128`.
49    ///
50    /// The intermediate product is computed with 256-bit arithmetic and
51    /// cannot itself overflow. The only failure mode is a final `i128`
52    /// quotient that exceeds the storage range.
53    ///
54    /// # Precision
55    ///
56    /// Strict: the result is truncated (not rounded) toward zero during
57    /// the scale-restoring divide, identical to the default `*` operator.
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// use decimal_scaled::D38s12;
63    ///
64    /// let half = D38s12::from_bits(500_000_000_000); // 0.5
65    /// assert_eq!(half.checked_mul(half), Some(D38s12::from_bits(250_000_000_000)));
66    /// assert_eq!(D38s12::MAX.checked_mul(D38s12::from_bits(2_000_000_000_000)), None);
67    /// ```
68    #[inline]
69    #[must_use]
70    pub fn checked_mul(self, rhs: Self) -> Option<Self> {
71        crate::mg_divide::mul_div_pow10::<SCALE>(self.0, rhs.0).map(Self)
72    }
73
74    /// Returns `self * rhs` with two's-complement wrap when the rescaled
75    /// product does not fit in `i128`.
76    ///
77    /// On overflow, falls back to `(a.wrapping_mul(b)).wrapping_div(multiplier())`.
78    /// The exact bit pattern of the wrapping result at extreme magnitudes
79    /// is an implementation detail; only the no-panic contract is guaranteed.
80    ///
81    /// # Precision
82    ///
83    /// Strict: truncates toward zero during the scale-restoring divide.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use decimal_scaled::D38s12;
89    ///
90    /// let half = D38s12::from_bits(500_000_000_000); // 0.5
91    /// assert_eq!(half.wrapping_mul(half), D38s12::from_bits(250_000_000_000));
92    /// // Overflow does not panic.
93    /// let _ = D38s12::MAX.wrapping_mul(D38s12::from_bits(2_000_000_000_000));
94    /// ```
95    #[inline]
96    #[must_use]
97    pub fn wrapping_mul(self, rhs: Self) -> Self {
98        match crate::mg_divide::mul_div_pow10::<SCALE>(self.0, rhs.0) {
99            Some(q) => Self(q),
100            None => Self(
101                self.0
102                    .wrapping_mul(rhs.0)
103                    .wrapping_div(Self::multiplier()),
104            ),
105        }
106    }
107
108    /// Returns `self * rhs`, clamped to `D38::MAX` or `D38::MIN` on overflow.
109    ///
110    /// The clamp direction is determined by the XOR of operand signs:
111    /// same-sign operands saturate to `MAX`; mixed-sign operands saturate
112    /// to `MIN`.
113    ///
114    /// # Precision
115    ///
116    /// Strict: truncates toward zero during the scale-restoring divide.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use decimal_scaled::D38s12;
122    ///
123    /// let two = D38s12::from_bits(2_000_000_000_000); // 2.0
124    /// assert_eq!(D38s12::MAX.saturating_mul(two), D38s12::MAX);
125    /// assert_eq!(D38s12::MAX.saturating_mul(-two), D38s12::MIN);
126    /// ```
127    #[inline]
128    #[must_use]
129    pub fn saturating_mul(self, rhs: Self) -> Self {
130        if let Some(q) = crate::mg_divide::mul_div_pow10::<SCALE>(self.0, rhs.0) { Self(q) } else {
131            // Clamp direction: negative result iff exactly one operand
132            // is negative (a zero operand cannot produce overflow).
133            let neg = (self.0 < 0) ^ (rhs.0 < 0);
134            if neg { Self::MIN } else { Self::MAX }
135        }
136    }
137
138    /// Returns `(self * rhs, did_overflow)` where the value is the
139    /// wrapping result when overflow occurs.
140    ///
141    /// # Precision
142    ///
143    /// Strict: truncates toward zero during the scale-restoring divide.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use decimal_scaled::D38s12;
149    ///
150    /// let half = D38s12::from_bits(500_000_000_000); // 0.5
151    /// assert_eq!(half.overflowing_mul(half), (D38s12::from_bits(250_000_000_000), false));
152    /// let (_, ovf) = D38s12::MAX.overflowing_mul(D38s12::from_bits(2_000_000_000_000));
153    /// assert!(ovf);
154    /// ```
155    #[inline]
156    #[must_use]
157    pub fn overflowing_mul(self, rhs: Self) -> (Self, bool) {
158        match crate::mg_divide::mul_div_pow10::<SCALE>(self.0, rhs.0) {
159            Some(q) => (Self(q), false),
160            None => (self.wrapping_mul(rhs), true),
161        }
162    }
163
164    // Div (rescale-aware via widening long-divide)
165    //
166    // All div variants use `div_pow10_div` as the default `Div` operator.
167    // Div-by-zero policy:
168    // - `checked_div(_, ZERO)` returns `None`.
169    // - `wrapping_div`, `saturating_div`, `overflowing_div` panic on
170    // `rhs == ZERO`, matching their `i128` counterparts.
171
172    /// Returns `self / rhs`, or `None` on division by zero or if the
173    /// rescaled quotient does not fit in `i128`.
174    ///
175    /// The only finite-operand overflow case is
176    /// `D38::MIN / NEG_ONE` (storage negation of `i128::MIN` overflows).
177    ///
178    /// # Precision
179    ///
180    /// Strict: the widening divide truncates toward zero, identical to
181    /// the default `/` operator.
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use decimal_scaled::D38s12;
187    ///
188    /// let six = D38s12::from_bits(6_000_000_000_000); // 6.0
189    /// let two = D38s12::from_bits(2_000_000_000_000); // 2.0
190    /// assert_eq!(six.checked_div(two), Some(D38s12::from_bits(3_000_000_000_000)));
191    /// assert_eq!(D38s12::ONE.checked_div(D38s12::ZERO), None);
192    /// ```
193    #[inline]
194    #[must_use]
195    pub fn checked_div(self, rhs: Self) -> Option<Self> {
196        crate::mg_divide::div_pow10_div::<SCALE>(self.0, rhs.0).map(Self)
197    }
198
199    /// Returns `self / rhs` with two's-complement wrap when the rescaled
200    /// quotient does not fit in `i128`.
201    ///
202    /// On overflow, falls back to
203    /// `(a.wrapping_mul(multiplier())).wrapping_div(b)`.
204    ///
205    /// # Precision
206    ///
207    /// Strict: truncates toward zero.
208    ///
209    /// # Panics
210    ///
211    /// Panics on `rhs == ZERO` (matches `i128::wrapping_div`).
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// use decimal_scaled::D38s12;
217    ///
218    /// let six = D38s12::from_bits(6_000_000_000_000); // 6.0
219    /// let two = D38s12::from_bits(2_000_000_000_000); // 2.0
220    /// assert_eq!(six.wrapping_div(two), D38s12::from_bits(3_000_000_000_000));
221    /// ```
222    #[inline]
223    #[must_use]
224    pub fn wrapping_div(self, rhs: Self) -> Self {
225        assert!(rhs.0 != 0, "attempt to divide by zero");
226        match crate::mg_divide::div_pow10_div::<SCALE>(self.0, rhs.0) {
227            Some(q) => Self(q),
228            None => Self(
229                self.0
230                    .wrapping_mul(Self::multiplier())
231                    .wrapping_div(rhs.0),
232            ),
233        }
234    }
235
236    /// Returns `self / rhs`, clamped to `D38::MAX` or `D38::MIN` on
237    /// overflow.
238    ///
239    /// The clamp direction is determined by the XOR of operand signs,
240    /// because the scale multiplier is always positive.
241    ///
242    /// # Precision
243    ///
244    /// Strict: truncates toward zero.
245    ///
246    /// # Panics
247    ///
248    /// Panics on `rhs == ZERO` (matches `i128::saturating_div`).
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// use decimal_scaled::D38s12;
254    ///
255    /// let six = D38s12::from_bits(6_000_000_000_000); // 6.0
256    /// let two = D38s12::from_bits(2_000_000_000_000); // 2.0
257    /// assert_eq!(six.saturating_div(two), D38s12::from_bits(3_000_000_000_000));
258    /// // MAX / 0.5 overflows; both positive so clamp to MAX.
259    /// assert_eq!(D38s12::MAX.saturating_div(D38s12::from_bits(500_000_000_000)), D38s12::MAX);
260    /// ```
261    #[inline]
262    #[must_use]
263    pub fn saturating_div(self, rhs: Self) -> Self {
264        assert!(rhs.0 != 0, "attempt to divide by zero");
265        if let Some(q) = crate::mg_divide::div_pow10_div::<SCALE>(self.0, rhs.0) { Self(q) } else {
266            // Clamp direction: negative iff exactly one operand is negative.
267            let neg = (self.0 < 0) ^ (rhs.0 < 0);
268            if neg { Self::MIN } else { Self::MAX }
269        }
270    }
271
272    /// Returns `(self / rhs, did_overflow)` where the value is the
273    /// wrapping result when overflow occurs.
274    ///
275    /// # Precision
276    ///
277    /// Strict: truncates toward zero.
278    ///
279    /// # Panics
280    ///
281    /// Panics on `rhs == ZERO` (matches `i128::overflowing_div`).
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use decimal_scaled::D38s12;
287    ///
288    /// let six = D38s12::from_bits(6_000_000_000_000); // 6.0
289    /// let two = D38s12::from_bits(2_000_000_000_000); // 2.0
290    /// assert_eq!(six.overflowing_div(two), (D38s12::from_bits(3_000_000_000_000), false));
291    /// let half = D38s12::from_bits(500_000_000_000); // 0.5
292    /// let (_, ovf) = D38s12::MAX.overflowing_div(half);
293    /// assert!(ovf);
294    /// ```
295    #[inline]
296    #[must_use]
297    pub fn overflowing_div(self, rhs: Self) -> (Self, bool) {
298        assert!(rhs.0 != 0, "attempt to divide by zero");
299        match crate::mg_divide::div_pow10_div::<SCALE>(self.0, rhs.0) {
300            Some(q) => (Self(q), false),
301            None => (self.wrapping_div(rhs), true),
302        }
303    }
304
305}
306
307#[cfg(test)]
308#[allow(clippy::arithmetic_side_effects)]
309mod tests {
310    use crate::core_type::{D38, D38s12};
311
312    /// Returns `-ONE` as a convenience value.
313    fn neg_one() -> D38s12 {
314        -D38s12::ONE
315    }
316
317    /// Returns `2.0` in `D38s12` canonical form.
318    fn two() -> D38s12 {
319        D38s12::from_bits(2_000_000_000_000)
320    }
321
322    /// Returns `3.0` in `D38s12` canonical form.
323    fn three() -> D38s12 {
324        D38s12::from_bits(3_000_000_000_000)
325    }
326
327    // Add variants
328
329    #[test]
330    fn checked_add_normal() {
331        assert_eq!(D38s12::ONE.checked_add(D38s12::ONE), Some(two()));
332    }
333
334    #[test]
335    fn checked_add_overflow_returns_none() {
336        // MAX + ONE overflows (MAX is i128::MAX raw; ONE is 10^SCALE raw).
337        assert_eq!(D38s12::MAX.checked_add(D38s12::ONE), None);
338        // Boundary: MAX + 1 LSB also overflows.
339        assert_eq!(
340            D38s12::MAX.checked_add(D38s12::from_bits(1)),
341            None
342        );
343    }
344
345    #[test]
346    fn checked_add_negative_overflow_returns_none() {
347        assert_eq!(D38s12::MIN.checked_add(neg_one()), None);
348        // Boundary: MIN + (-1 LSB) also overflows.
349        assert_eq!(
350            D38s12::MIN.checked_add(D38s12::from_bits(-1)),
351            None
352        );
353    }
354
355    #[test]
356    fn wrapping_add_normal_matches_op() {
357        assert_eq!(D38s12::ONE.wrapping_add(D38s12::ONE), two());
358    }
359
360    #[test]
361    fn wrapping_add_overflow_wraps_to_min() {
362        // MAX + 1 LSB wraps to MIN under two's-complement.
363        assert_eq!(
364            D38s12::MAX.wrapping_add(D38s12::from_bits(1)),
365            D38s12::MIN
366        );
367    }
368
369    #[test]
370    fn wrapping_add_negative_overflow_wraps_to_max() {
371        // MIN + (-1 LSB) wraps to MAX.
372        assert_eq!(
373            D38s12::MIN.wrapping_add(D38s12::from_bits(-1)),
374            D38s12::MAX
375        );
376    }
377
378    #[test]
379    fn saturating_add_normal_matches_op() {
380        assert_eq!(D38s12::ONE.saturating_add(D38s12::ONE), two());
381    }
382
383    #[test]
384    fn saturating_add_overflow_clamps_to_max() {
385        assert_eq!(D38s12::MAX.saturating_add(D38s12::ONE), D38s12::MAX);
386    }
387
388    #[test]
389    fn saturating_add_negative_overflow_clamps_to_min() {
390        assert_eq!(D38s12::MIN.saturating_add(neg_one()), D38s12::MIN);
391    }
392
393    #[test]
394    fn overflowing_add_normal_no_overflow() {
395        assert_eq!(
396            D38s12::ONE.overflowing_add(D38s12::ONE),
397            (two(), false)
398        );
399    }
400
401    #[test]
402    fn overflowing_add_overflow_flagged() {
403        // MAX + 1 LSB wraps exactly to MIN; overflow flag is set.
404        assert_eq!(
405            D38s12::MAX.overflowing_add(D38s12::from_bits(1)),
406            (D38s12::MIN, true)
407        );
408    }
409
410    #[test]
411    fn overflowing_add_negative_overflow_flagged() {
412        // MIN + (-1 LSB) wraps exactly to MAX.
413        assert_eq!(
414            D38s12::MIN.overflowing_add(D38s12::from_bits(-1)),
415            (D38s12::MAX, true)
416        );
417    }
418
419    // Sub variants
420
421    #[test]
422    fn checked_sub_normal() {
423        assert_eq!(three().checked_sub(D38s12::ONE), Some(two()));
424    }
425
426    #[test]
427    fn checked_sub_underflow_returns_none() {
428        assert_eq!(D38s12::MIN.checked_sub(D38s12::ONE), None);
429    }
430
431    #[test]
432    fn checked_sub_positive_overflow_returns_none() {
433        // MAX - (-ONE) = MAX + ONE -> overflows.
434        assert_eq!(D38s12::MAX.checked_sub(neg_one()), None);
435    }
436
437    #[test]
438    fn wrapping_sub_normal() {
439        assert_eq!(three().wrapping_sub(D38s12::ONE), two());
440    }
441
442    #[test]
443    fn wrapping_sub_underflow_wraps_to_max() {
444        // MIN - 1 LSB wraps exactly to MAX.
445        assert_eq!(
446            D38s12::MIN.wrapping_sub(D38s12::from_bits(1)),
447            D38s12::MAX
448        );
449    }
450
451    #[test]
452    fn saturating_sub_normal() {
453        assert_eq!(three().saturating_sub(D38s12::ONE), two());
454    }
455
456    #[test]
457    fn saturating_sub_underflow_clamps_to_min() {
458        assert_eq!(D38s12::MIN.saturating_sub(D38s12::ONE), D38s12::MIN);
459    }
460
461    #[test]
462    fn saturating_sub_overflow_clamps_to_max() {
463        // MAX - (-ONE) saturates to MAX.
464        assert_eq!(D38s12::MAX.saturating_sub(neg_one()), D38s12::MAX);
465    }
466
467    #[test]
468    fn overflowing_sub_normal() {
469        assert_eq!(
470            three().overflowing_sub(D38s12::ONE),
471            (two(), false)
472        );
473    }
474
475    #[test]
476    fn overflowing_sub_underflow_flagged() {
477        // MIN - 1 LSB wraps exactly to MAX.
478        assert_eq!(
479            D38s12::MIN.overflowing_sub(D38s12::from_bits(1)),
480            (D38s12::MAX, true)
481        );
482    }
483
484    // Neg variants
485
486    #[test]
487    fn checked_neg_normal() {
488        assert_eq!(D38s12::ONE.checked_neg(), Some(neg_one()));
489        assert_eq!(neg_one().checked_neg(), Some(D38s12::ONE));
490        assert_eq!(D38s12::ZERO.checked_neg(), Some(D38s12::ZERO));
491    }
492
493    #[test]
494    fn checked_neg_min_returns_none() {
495        assert_eq!(D38s12::MIN.checked_neg(), None);
496    }
497
498    #[test]
499    fn checked_neg_max_succeeds() {
500        // MAX = i128::MAX, -MAX = i128::MIN + 1, fits.
501        let neg_max = D38s12::from_bits(-i128::MAX);
502        assert_eq!(D38s12::MAX.checked_neg(), Some(neg_max));
503    }
504
505    #[test]
506    fn wrapping_neg_normal() {
507        assert_eq!(D38s12::ONE.wrapping_neg(), neg_one());
508        assert_eq!(D38s12::ZERO.wrapping_neg(), D38s12::ZERO);
509    }
510
511    #[test]
512    fn wrapping_neg_min_returns_min() {
513        // -i128::MIN wraps to i128::MIN under two's-complement.
514        assert_eq!(D38s12::MIN.wrapping_neg(), D38s12::MIN);
515    }
516
517    #[test]
518    fn saturating_neg_normal() {
519        assert_eq!(D38s12::ONE.saturating_neg(), neg_one());
520        assert_eq!(D38s12::ZERO.saturating_neg(), D38s12::ZERO);
521    }
522
523    #[test]
524    fn saturating_neg_min_returns_max() {
525        assert_eq!(D38s12::MIN.saturating_neg(), D38s12::MAX);
526    }
527
528    #[test]
529    fn overflowing_neg_normal() {
530        assert_eq!(
531            D38s12::ONE.overflowing_neg(),
532            (neg_one(), false)
533        );
534        assert_eq!(
535            D38s12::ZERO.overflowing_neg(),
536            (D38s12::ZERO, false)
537        );
538    }
539
540    #[test]
541    fn overflowing_neg_min_flagged() {
542        assert_eq!(
543            D38s12::MIN.overflowing_neg(),
544            (D38s12::MIN, true)
545        );
546    }
547
548    // Mul variants
549
550    #[test]
551    fn checked_mul_normal() {
552        let half = D38s12::from_bits(500_000_000_000);
553        let quarter = D38s12::from_bits(250_000_000_000);
554        assert_eq!(half.checked_mul(half), Some(quarter));
555    }
556
557    #[test]
558    fn checked_mul_zero() {
559        assert_eq!(D38s12::MAX.checked_mul(D38s12::ZERO), Some(D38s12::ZERO));
560        assert_eq!(D38s12::ZERO.checked_mul(D38s12::ZERO), Some(D38s12::ZERO));
561    }
562
563    #[test]
564    fn checked_mul_one_identity() {
565        let v = D38s12::from_bits(7_500_000_000_000); // 7.5
566        assert_eq!(v.checked_mul(D38s12::ONE), Some(v));
567        assert_eq!(D38s12::ONE.checked_mul(v), Some(v));
568    }
569
570    #[test]
571    fn checked_mul_overflow_returns_none() {
572        // MAX * 2.0 overflows the final i128 quotient.
573        assert_eq!(D38s12::MAX.checked_mul(two()), None);
574    }
575
576    #[test]
577    fn checked_mul_min_overflow_returns_none() {
578        // MIN * 2.0 overflows.
579        assert_eq!(D38s12::MIN.checked_mul(two()), None);
580    }
581
582    #[test]
583    fn wrapping_mul_normal() {
584        let half = D38s12::from_bits(500_000_000_000);
585        let quarter = D38s12::from_bits(250_000_000_000);
586        assert_eq!(half.wrapping_mul(half), quarter);
587    }
588
589    #[test]
590    fn wrapping_mul_overflow_does_not_panic() {
591        // Verify it does not panic; the exact bit pattern is unspecified.
592        let _ = D38s12::MAX.wrapping_mul(two());
593        let _ = D38s12::MIN.wrapping_mul(two());
594    }
595
596    #[test]
597    fn saturating_mul_normal() {
598        let half = D38s12::from_bits(500_000_000_000);
599        let quarter = D38s12::from_bits(250_000_000_000);
600        assert_eq!(half.saturating_mul(half), quarter);
601    }
602
603    #[test]
604    fn saturating_mul_positive_overflow_clamps_to_max() {
605        // MAX * 2.0 (both positive) saturates to MAX.
606        assert_eq!(D38s12::MAX.saturating_mul(two()), D38s12::MAX);
607    }
608
609    #[test]
610    fn saturating_mul_negative_overflow_clamps_to_min() {
611        // MAX * (-2.0) (mixed sign) saturates to MIN.
612        assert_eq!(
613            D38s12::MAX.saturating_mul(-two()),
614            D38s12::MIN
615        );
616    }
617
618    #[test]
619    fn saturating_mul_min_times_two_clamps_to_min() {
620        // MIN * 2.0 (MIN negative, 2 positive) saturates to MIN.
621        assert_eq!(D38s12::MIN.saturating_mul(two()), D38s12::MIN);
622    }
623
624    #[test]
625    fn saturating_mul_min_times_neg_two_clamps_to_max() {
626        // MIN * -2.0 (both negative) saturates to MAX.
627        assert_eq!(D38s12::MIN.saturating_mul(-two()), D38s12::MAX);
628    }
629
630    #[test]
631    fn overflowing_mul_normal_no_overflow() {
632        let half = D38s12::from_bits(500_000_000_000);
633        let quarter = D38s12::from_bits(250_000_000_000);
634        assert_eq!(half.overflowing_mul(half), (quarter, false));
635    }
636
637    #[test]
638    fn overflowing_mul_overflow_flagged() {
639        let (_, ovf) = D38s12::MAX.overflowing_mul(two());
640        assert!(ovf);
641    }
642
643    // Div variants
644
645    #[test]
646    fn checked_div_normal() {
647        // 6.0 / 2.0 = 3.0
648        let six = D38s12::from_bits(6_000_000_000_000);
649        assert_eq!(six.checked_div(two()), Some(three()));
650    }
651
652    #[test]
653    fn checked_div_by_zero_returns_none() {
654        assert_eq!(D38s12::ONE.checked_div(D38s12::ZERO), None);
655    }
656
657    #[test]
658    fn checked_div_overflow_returns_none() {
659        // MAX / 0.5 = 2 * MAX -> overflows the final quotient.
660        let half = D38s12::from_bits(500_000_000_000);
661        assert_eq!(D38s12::MAX.checked_div(half), None);
662    }
663
664    #[test]
665    fn checked_div_negative_normal() {
666        let neg_six = D38s12::from_bits(-6_000_000_000_000);
667        assert_eq!(neg_six.checked_div(two()), Some(-three()));
668    }
669
670    #[test]
671    fn wrapping_div_normal() {
672        let six = D38s12::from_bits(6_000_000_000_000);
673        assert_eq!(six.wrapping_div(two()), three());
674    }
675
676    #[test]
677    #[should_panic(expected = "attempt to divide by zero")]
678    fn wrapping_div_by_zero_panics() {
679        let _ = D38s12::ONE.wrapping_div(D38s12::ZERO);
680    }
681
682    #[test]
683    fn wrapping_div_overflow_does_not_panic() {
684        // Verify it does not panic; the exact result is unspecified.
685        let half = D38s12::from_bits(500_000_000_000);
686        let _ = D38s12::MAX.wrapping_div(half);
687    }
688
689    #[test]
690    fn saturating_div_normal() {
691        let six = D38s12::from_bits(6_000_000_000_000);
692        assert_eq!(six.saturating_div(two()), three());
693    }
694
695    #[test]
696    #[should_panic(expected = "attempt to divide by zero")]
697    fn saturating_div_by_zero_panics() {
698        let _ = D38s12::ONE.saturating_div(D38s12::ZERO);
699    }
700
701    #[test]
702    fn saturating_div_overflow_clamps_to_max() {
703        // MAX / 0.5 (both positive) saturates to MAX.
704        let half = D38s12::from_bits(500_000_000_000);
705        assert_eq!(D38s12::MAX.saturating_div(half), D38s12::MAX);
706    }
707
708    #[test]
709    fn saturating_div_negative_overflow_clamps_to_min() {
710        // MAX / -0.5 (mixed sign) saturates to MIN.
711        let neg_half = D38s12::from_bits(-500_000_000_000);
712        assert_eq!(
713            D38s12::MAX.saturating_div(neg_half),
714            D38s12::MIN
715        );
716    }
717
718    #[test]
719    fn overflowing_div_normal() {
720        let six = D38s12::from_bits(6_000_000_000_000);
721        assert_eq!(six.overflowing_div(two()), (three(), false));
722    }
723
724    #[test]
725    fn overflowing_div_overflow_flagged() {
726        let half = D38s12::from_bits(500_000_000_000);
727        let (_, ovf) = D38s12::MAX.overflowing_div(half);
728        assert!(ovf);
729    }
730
731    #[test]
732    #[should_panic(expected = "attempt to divide by zero")]
733    fn overflowing_div_by_zero_panics() {
734        let _ = D38s12::ONE.overflowing_div(D38s12::ZERO);
735    }
736
737    // Rem variants
738
739    #[test]
740    fn checked_rem_normal() {
741        // 5.5 % 2.0 = 1.5
742        let a = D38s12::from_bits(5_500_000_000_000);
743        let expected = D38s12::from_bits(1_500_000_000_000);
744        assert_eq!(a.checked_rem(two()), Some(expected));
745    }
746
747    #[test]
748    fn checked_rem_by_zero_returns_none() {
749        assert_eq!(D38s12::ONE.checked_rem(D38s12::ZERO), None);
750    }
751
752    #[test]
753    fn checked_rem_min_neg_one_lsb_returns_none() {
754        // The raw overflow case is `i128::MIN % -1` (because i128::MIN / -1
755        // overflows). The divisor's raw bits are -1, not the decimal -ONE
756        // (-10^12), which does not trigger this path.
757        let neg_one_lsb = D38s12::from_bits(-1);
758        assert_eq!(D38s12::MIN.checked_rem(neg_one_lsb), None);
759    }
760
761    #[test]
762    fn wrapping_rem_normal() {
763        let a = D38s12::from_bits(5_500_000_000_000);
764        let expected = D38s12::from_bits(1_500_000_000_000);
765        assert_eq!(a.wrapping_rem(two()), expected);
766    }
767
768    #[test]
769    #[should_panic(expected = "attempt to calculate the remainder with a divisor of zero")]
770    fn wrapping_rem_by_zero_panics() {
771        let _ = D38s12::ONE.wrapping_rem(D38s12::ZERO);
772    }
773
774    #[test]
775    fn wrapping_rem_min_neg_one_lsb_returns_zero() {
776        // i128::MIN % -1 wraps to 0 (the overflow case).
777        let neg_one_lsb = D38s12::from_bits(-1);
778        assert_eq!(
779            D38s12::MIN.wrapping_rem(neg_one_lsb),
780            D38s12::ZERO
781        );
782    }
783
784    #[test]
785    fn overflowing_rem_normal() {
786        let a = D38s12::from_bits(5_500_000_000_000);
787        let expected = D38s12::from_bits(1_500_000_000_000);
788        assert_eq!(a.overflowing_rem(two()), (expected, false));
789    }
790
791    #[test]
792    fn overflowing_rem_min_neg_one_lsb_flagged() {
793        let neg_one_lsb = D38s12::from_bits(-1);
794        assert_eq!(
795            D38s12::MIN.overflowing_rem(neg_one_lsb),
796            (D38s12::ZERO, true)
797        );
798    }
799
800    // Cross-scale exercise
801
802    /// Verifies that the variant family compiles and functions correctly at SCALE = 6.
803    #[test]
804    fn variants_at_scale_6() {
805        type D6 = D38<6>;
806        let one = D6::ONE;
807        let two_d6 = D6::from_bits(2_000_000); // 2.0 at SCALE=6
808        let one_lsb = D6::from_bits(1);
809
810        assert_eq!(one.checked_add(one), Some(two_d6));
811        // MAX + 1 LSB overflows / wraps to MIN under two's-complement.
812        assert_eq!(D6::MAX.checked_add(one_lsb), None);
813        assert_eq!(D6::MAX.saturating_add(one_lsb), D6::MAX);
814        assert_eq!(D6::MAX.wrapping_add(one_lsb), D6::MIN);
815        assert_eq!(
816            D6::MAX.overflowing_add(one_lsb),
817            (D6::MIN, true)
818        );
819
820        assert_eq!(D6::MIN.checked_neg(), None);
821        assert_eq!(D6::MIN.wrapping_neg(), D6::MIN);
822        assert_eq!(D6::MIN.saturating_neg(), D6::MAX);
823    }
824
825    /// Verifies that `checked_*` matches the base operator when no overflow occurs.
826    #[test]
827    fn checked_matches_op_in_range() {
828        let a = D38s12::from_bits(7_500_000_000_000); // 7.5
829        let b = two();
830        assert_eq!(a.checked_add(b), Some(a + b));
831        assert_eq!(a.checked_sub(b), Some(a - b));
832        assert_eq!(a.checked_mul(b), Some(a * b));
833        assert_eq!(a.checked_div(b), Some(a / b));
834        assert_eq!(a.checked_rem(b), Some(a % b));
835    }
836
837    /// Verifies that the `overflowing_*` flag agrees with `checked_*` returning `None`.
838    #[test]
839    fn overflowing_flag_matches_checked_none() {
840        // Add: MAX + ONE
841        let (_, ovf) = D38s12::MAX.overflowing_add(D38s12::ONE);
842        assert_eq!(ovf, D38s12::MAX.checked_add(D38s12::ONE).is_none());
843
844        // Sub: MIN - ONE
845        let (_, ovf) = D38s12::MIN.overflowing_sub(D38s12::ONE);
846        assert_eq!(ovf, D38s12::MIN.checked_sub(D38s12::ONE).is_none());
847
848        // Mul: MAX * 2
849        let (_, ovf) = D38s12::MAX.overflowing_mul(two());
850        assert_eq!(ovf, D38s12::MAX.checked_mul(two()).is_none());
851
852        // Neg: MIN
853        let (_, ovf) = D38s12::MIN.overflowing_neg();
854        assert_eq!(ovf, D38s12::MIN.checked_neg().is_none());
855
856        // Rem: MIN % (-1 LSB) -- the raw i128::MIN % -1 case.
857        let neg_one_lsb = D38s12::from_bits(-1);
858        let (_, ovf) = D38s12::MIN.overflowing_rem(neg_one_lsb);
859        assert_eq!(
860            ovf,
861            D38s12::MIN.checked_rem(neg_one_lsb).is_none()
862        );
863    }
864
865    /// Verifies that `saturating_add`, `saturating_sub`, and `saturating_mul`
866    /// never panic and always return a value within `[MIN, MAX]`.
867    #[test]
868    fn saturating_never_escapes_bounds() {
869        let extremes = [
870            D38s12::MIN,
871            D38s12::from_bits(-1),
872            D38s12::ZERO,
873            D38s12::ONE,
874            D38s12::MAX,
875        ];
876        for &a in &extremes {
877            for &b in &extremes {
878                let s_add = a.saturating_add(b);
879                let s_sub = a.saturating_sub(b);
880                let s_mul = a.saturating_mul(b);
881                assert!(s_add >= D38s12::MIN && s_add <= D38s12::MAX);
882                assert!(s_sub >= D38s12::MIN && s_sub <= D38s12::MAX);
883                assert!(s_mul >= D38s12::MIN && s_mul <= D38s12::MAX);
884            }
885        }
886    }
887}