Skip to main content

decimal_scaled/
arithmetic.rs

1//! Arithmetic operator overloads for [`D38`].
2//!
3//! All operators work directly on the raw `i128` storage value.
4//! Addition, subtraction, and negation require no rescaling because the
5//! scale factor commutes with those operations. Multiplication and division
6//! each require one rescaling step to keep the result in `value * 10^SCALE`
7//! form; remainder requires none because both operands share the same scale.
8//!
9//! # Overflow policy
10//!
11//! Default operator semantics match Rust's `i128` arithmetic: **panics on
12//! overflow in debug builds; wraps two's-complement in release builds.**
13//! This is the standard Rust integer arithmetic contract. Explicit-overflow
14//! variants (`checked_*`, `wrapping_*`, `saturating_*`, `overflowing_*`)
15//! are not provided in this module.
16//!
17//! # Mul / Div algorithm
18//!
19//! `Mul` / `MulAssign` use a 256-bit widening intermediate followed by a
20//! Moller-Granlund 2011 magic-number divide (see the `mg_divide` module).
21//! This replaces a naive `(self.0 * rhs.0) / multiplier()` approach that
22//! would silently overflow `i128` at operand magnitudes beyond
23//! `sqrt(i128::MAX)`. With the widening approach the operand range covers
24//! the full `i128` storage range; the only overflow possible is on the
25//! final `i128` quotient.
26//!
27//! `Div` / `DivAssign` widen the numerator `a * 10^SCALE` to 256 bits via
28//! the same schoolbook multiply, then divide by `b` using a hand-rolled
29//! binary long-divide. MG-style magic does not apply because the divisor is
30//! variable rather than a known power of ten.
31//!
32//! Both paths preserve panic-debug / wrap-release semantics for the final
33//! `i128` result. The intermediate 256-bit calculation never observably
34//! overflows.
35
36use core::ops::{Div, DivAssign, Mul, MulAssign};
37
38use crate::core_type::D38;
39
40impl<const SCALE: u32> Mul for D38<SCALE> {
41    type Output = Self;
42
43    /// Multiply two values, rescaling the result back to `value * 10^S` form.
44    ///
45    /// The raw product `a.0 * b.0` has units `10^(2S)`, so it must be
46    /// divided by `10^S` to restore the canonical scale. This is done via
47    /// a 256-bit widening intermediate and a Moller-Granlund magic-number
48    /// divide (see the `mg_divide` module), which avoids the intermediate
49    /// overflow that would occur with a naive `i128` multiply at large
50    /// operand magnitudes.
51    ///
52    /// # Panics
53    ///
54    /// Panics in debug builds when the final rescaled quotient overflows
55    /// `i128::MAX`. In release builds the result wraps two's-complement.
56    ///
57    /// # Precision
58    ///
59    /// Strict: all arithmetic is integer-only; result is bit-exact.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use decimal_scaled::D38s12;
65    ///
66    /// let a = D38s12::from_bits(1_500_000_000_000); // 1.5
67    /// let b = D38s12::from_bits(2_000_000_000_000); // 2.0
68    /// assert_eq!((a * b).to_bits(), 3_000_000_000_000); // 3.0
69    /// ```
70    #[inline]
71    fn mul(self, rhs: Self) -> Self {
72        match crate::mg_divide::mul_div_pow10::<SCALE>(self.0, rhs.0) {
73            Some(q) => Self(q),
74            None => Self(panic_or_wrap_mul::<SCALE>(self.0, rhs.0)),
75        }
76    }
77}
78
79impl<const SCALE: u32> MulAssign for D38<SCALE> {
80    /// Multiply `self` by `rhs` in place.
81    ///
82    /// # Precision
83    ///
84    /// Strict: all arithmetic is integer-only; result is bit-exact.
85    #[inline]
86    fn mul_assign(&mut self, rhs: Self) {
87        *self = *self * rhs;
88    }
89}
90
91impl<const SCALE: u32> Div for D38<SCALE> {
92    type Output = Self;
93
94    /// Divide `self` by `rhs`, rescaling the numerator to keep the result
95    /// in `value * 10^S` form.
96    ///
97    /// The numerator `self.0` is widened to 256 bits and multiplied by
98    /// `10^SCALE` before dividing by `rhs.0`. This avoids the intermediate
99    /// overflow that would occur with a naive `(self.0 * 10^S) / rhs.0`
100    /// approach at large dividend magnitudes.
101    ///
102    /// # Panics
103    ///
104    /// Panics on division by zero (matching `i128 /`). Also panics in debug
105    /// builds when the final quotient overflows `i128`; wraps in release
106    /// builds.
107    ///
108    /// # Precision
109    ///
110    /// Strict: all arithmetic is integer-only; result is bit-exact.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use decimal_scaled::D38s12;
116    ///
117    /// let a = D38s12::from_bits(3_000_000_000_000); // 3.0
118    /// let b = D38s12::from_bits(2_000_000_000_000); // 2.0
119    /// assert_eq!((a / b).to_bits(), 1_500_000_000_000); // 1.5
120    /// ```
121    #[inline]
122    fn div(self, rhs: Self) -> Self {
123        // Match the panic message from `i128 /`.
124        assert!(rhs.0 != 0, "attempt to divide by zero");
125        match crate::mg_divide::div_pow10_div::<SCALE>(self.0, rhs.0) {
126            Some(q) => Self(q),
127            None => Self(panic_or_wrap_div::<SCALE>(self.0, rhs.0)),
128        }
129    }
130}
131
132impl<const SCALE: u32> DivAssign for D38<SCALE> {
133    /// Divide `self` by `rhs` in place.
134    ///
135    /// # Precision
136    ///
137    /// Strict: all arithmetic is integer-only; result is bit-exact.
138    #[inline]
139    fn div_assign(&mut self, rhs: Self) {
140        *self = *self / rhs;
141    }
142}
143
144impl<const SCALE: u32> D38<SCALE> {
145    /// Multiply two values of the same scale, rounding the
146    /// scale-narrowing divide by `10^SCALE` according to `mode`.
147    ///
148    /// The default `Mul` operator delegates to this with the
149    /// crate-default rounding mode; call `mul_with` directly when
150    /// you need a non-default rounding rule (e.g.
151    /// `HalfAwayFromZero` for commercial rounding, `Floor`/`Ceiling`
152    /// for one-sided bracketing).
153    ///
154    /// # Panics
155    ///
156    /// Panics in debug builds when the rescaled quotient overflows
157    /// `i128`. Wraps two's-complement in release builds.
158    ///
159    /// # Precision
160    ///
161    /// Strict: integer-only arithmetic. Within 0.5 ULP for the half-*
162    /// family; directed rounding otherwise.
163    #[inline]
164    #[must_use]
165    pub fn mul_with(self, rhs: Self, mode: crate::rounding::RoundingMode) -> Self {
166        match crate::mg_divide::mul_div_pow10_with::<SCALE>(self.0, rhs.0, mode) {
167            Some(q) => Self(q),
168            None => Self(panic_or_wrap_mul::<SCALE>(self.0, rhs.0)),
169        }
170    }
171
172    /// Divide two values of the same scale, rounding the final
173    /// divide step according to `mode`.
174    ///
175    /// The default `Div` operator delegates to this with the
176    /// crate-default rounding mode.
177    ///
178    /// # Panics
179    ///
180    /// Panics on division by zero (matching `i128 /`). Panics in debug
181    /// builds when the quotient overflows `i128`; wraps in release.
182    ///
183    /// # Precision
184    ///
185    /// Strict: integer-only arithmetic. Within 0.5 ULP for the half-*
186    /// family; directed rounding otherwise.
187    #[inline]
188    #[must_use]
189    pub fn div_with(self, rhs: Self, mode: crate::rounding::RoundingMode) -> Self {
190        assert!(rhs.0 != 0, "attempt to divide by zero");
191        match crate::mg_divide::div_pow10_div_with::<SCALE>(self.0, rhs.0, mode) {
192            Some(q) => Self(q),
193            None => Self(panic_or_wrap_div::<SCALE>(self.0, rhs.0)),
194        }
195    }
196}
197
198// Overflow fallback helpers for Mul and Div.
199//
200// The widening multiply/divide paths return `None` when the final `i128`
201// quotient overflows. These helpers reproduce Rust's standard integer
202// overflow contract: panic in debug builds, wrap two's-complement in
203// release builds. The wrapping form re-derives the result from the
204// original operands using `wrapping_*` intrinsics so it matches the naive
205// form's behavior at operand magnitudes where the naive form was correct.
206
207/// Emit a debug panic or return the wrapping result for a Mul overflow.
208///
209/// # Precision
210///
211/// Strict: all arithmetic is integer-only; result is bit-exact.
212#[inline(always)]
213#[allow(clippy::arithmetic_side_effects)]
214fn panic_or_wrap_mul<const SCALE: u32>(a: i128, b: i128) -> i128 {
215    #[cfg(debug_assertions)]
216    {
217        let _ = (a, b);
218        panic!("attempt to multiply with overflow");
219    }
220    #[cfg(not(debug_assertions))]
221    {
222        a.wrapping_mul(b).wrapping_div(D38::<SCALE>::multiplier())
223    }
224}
225
226/// Emit a debug panic or return the wrapping result for a Div overflow.
227///
228/// # Precision
229///
230/// Strict: all arithmetic is integer-only; result is bit-exact.
231#[inline(always)]
232#[allow(clippy::arithmetic_side_effects)]
233fn panic_or_wrap_div<const SCALE: u32>(a: i128, b: i128) -> i128 {
234    #[cfg(debug_assertions)]
235    {
236        let _ = (a, b);
237        panic!("attempt to divide with overflow");
238    }
239    #[cfg(not(debug_assertions))]
240    {
241        a.wrapping_mul(D38::<SCALE>::multiplier()).wrapping_div(b)
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use crate::core_type::D38s12;
248
249    /// ZERO + ZERO == ZERO.
250    #[test]
251    fn add_zero_to_zero_is_zero() {
252        assert_eq!(D38s12::ZERO + D38s12::ZERO, D38s12::ZERO);
253    }
254
255    /// ZERO - ZERO == ZERO.
256    #[test]
257    fn sub_zero_from_zero_is_zero() {
258        assert_eq!(D38s12::ZERO - D38s12::ZERO, D38s12::ZERO);
259    }
260
261    /// -ZERO == ZERO.
262    #[test]
263    fn neg_zero_is_zero() {
264        assert_eq!(-D38s12::ZERO, D38s12::ZERO);
265    }
266
267    /// AddAssign mutates in place.
268    #[test]
269    fn add_assign_zero() {
270        let mut v = D38s12::ZERO;
271        v += D38s12::ZERO;
272        assert_eq!(v, D38s12::ZERO);
273    }
274
275    /// SubAssign mutates in place.
276    #[test]
277    fn sub_assign_zero() {
278        let mut v = D38s12::ZERO;
279        v -= D38s12::ZERO;
280        assert_eq!(v, D38s12::ZERO);
281    }
282
283    /// Canonical claim: `(a + b) - b == a` for representative values.
284    /// At SCALE = 12, `a = 1.5 mm` is bits `1_500_000_000_000`, `b =
285    /// 0.25 mm` is bits `250_000_000_000`.
286    #[test]
287    fn add_sub_round_trip_canonical_claim() {
288        let a = D38s12::from_bits(1_500_000_000_000);
289        let b = D38s12::from_bits(250_000_000_000);
290        assert_eq!((a + b) - b, a);
291    }
292
293    /// Round-trip with a negative `a` to exercise sign handling.
294    #[test]
295    fn add_sub_round_trip_negative() {
296        let a = D38s12::from_bits(-7_321_654_987_000);
297        let b = D38s12::from_bits(42_000_000_000_000);
298        assert_eq!((a + b) - b, a);
299    }
300
301    /// `ONE + ONE` is the scaled bit-pattern `2 * 10^12`.
302    #[test]
303    fn one_plus_one_is_two_in_scaled_bits() {
304        let two = D38s12::ONE + D38s12::ONE;
305        // 2 * 10^12 = 2_000_000_000_000
306        assert_eq!(two.to_bits(), 2_000_000_000_000);
307    }
308
309    /// `-ONE + ONE == ZERO` -- additive inverse property.
310    #[test]
311    fn neg_one_plus_one_is_zero() {
312        assert_eq!(-D38s12::ONE + D38s12::ONE, D38s12::ZERO);
313    }
314
315    /// Default policy: overflow panics in debug builds. Locks the
316    /// debug-vs-release split documented in module docs. The matching
317    /// release-build wrap behaviour is delegated to the toolchain's
318    /// `i128 +` semantics; testing it here would require a release-
319    /// only test gate that's overkill for a base-ops slice.
320    #[test]
321    #[cfg(debug_assertions)]
322    #[should_panic(expected = "overflow")]
323    fn add_overflow_panics_in_debug() {
324        let _ = D38s12::MAX + D38s12::ONE;
325    }
326
327    /// Default policy: underflow panics in debug builds.
328    #[test]
329    #[cfg(debug_assertions)]
330    #[should_panic(expected = "overflow")]
331    fn sub_underflow_panics_in_debug() {
332        let _ = D38s12::MIN - D38s12::ONE;
333    }
334
335    /// Default policy: `-MIN` panics in debug builds (i128::MIN has
336    /// no positive counterpart in two's-complement).
337    #[test]
338    #[cfg(debug_assertions)]
339    #[should_panic(expected = "overflow")]
340    fn neg_min_panics_in_debug() {
341        let _ = -D38s12::MIN;
342    }
343
344    /// AddAssign with non-zero values.
345    #[test]
346    fn add_assign_accumulates() {
347        let mut v = D38s12::from_bits(100);
348        v += D38s12::from_bits(250);
349        assert_eq!(v.to_bits(), 350);
350        v += D38s12::from_bits(-50);
351        assert_eq!(v.to_bits(), 300);
352    }
353
354    /// SubAssign with non-zero values.
355    #[test]
356    fn sub_assign_accumulates() {
357        let mut v = D38s12::from_bits(1000);
358        v -= D38s12::from_bits(250);
359        assert_eq!(v.to_bits(), 750);
360    }
361
362    // ── Mul / Div / Rem ──
363
364    /// `ONE * ONE == ONE` -- multiplicative identity.
365    #[test]
366    fn mul_one_one_is_one() {
367        assert_eq!(D38s12::ONE * D38s12::ONE, D38s12::ONE);
368    }
369
370    /// `ONE / ONE == ONE`.
371    #[test]
372    fn div_one_one_is_one() {
373        assert_eq!(D38s12::ONE / D38s12::ONE, D38s12::ONE);
374    }
375
376    /// `ZERO % ONE == ZERO`.
377    #[test]
378    fn rem_zero_one_is_zero() {
379        assert_eq!(D38s12::ZERO % D38s12::ONE, D38s12::ZERO);
380    }
381
382    /// `ZERO * x == ZERO` for representative non-trivial `x`.
383    #[test]
384    fn mul_zero_is_zero() {
385        let x = D38s12::from_bits(1_500_000_000_000); // 1.5
386        assert_eq!(D38s12::ZERO * x, D38s12::ZERO);
387        assert_eq!(x * D38s12::ZERO, D38s12::ZERO);
388    }
389
390    /// `ONE * x == x` for representative `x` (left and right identity).
391    #[test]
392    fn mul_one_is_identity() {
393        let x = D38s12::from_bits(1_500_000_000_000); // 1.5
394        assert_eq!(D38s12::ONE * x, x);
395        assert_eq!(x * D38s12::ONE, x);
396
397        let y = D38s12::from_bits(-7_321_654_987_000); // -7.321...
398        assert_eq!(D38s12::ONE * y, y);
399        assert_eq!(y * D38s12::ONE, y);
400    }
401
402    /// `x / ONE == x`.
403    #[test]
404    fn div_one_is_identity() {
405        let x = D38s12::from_bits(1_500_000_000_000);
406        assert_eq!(x / D38s12::ONE, x);
407
408        let y = D38s12::from_bits(-7_321_654_987_000);
409        assert_eq!(y / D38s12::ONE, y);
410    }
411
412    /// `x / x == ONE` for non-zero x.
413    #[test]
414    fn div_self_is_one() {
415        let x = D38s12::from_bits(1_500_000_000_000); // 1.5
416        assert_eq!(x / x, D38s12::ONE);
417
418        let y = D38s12::from_bits(-7_321_654_987_000);
419        assert_eq!(y / y, D38s12::ONE);
420
421        // ONE / ONE already covered; a smaller value to exercise the
422        // promotion path.
423        let small = D38s12::from_bits(1); // 1 LSB
424        assert_eq!(small / small, D38s12::ONE);
425    }
426
427    /// `(x * 7) % x == 0` -- multiple-of property.
428    #[test]
429    fn rem_multiple_is_zero() {
430        let x = D38s12::from_bits(3_500_000_000_000); // 3.5
431        let seven = D38s12::ONE + D38s12::ONE + D38s12::ONE + D38s12::ONE
432            + D38s12::ONE + D38s12::ONE + D38s12::ONE; // 7
433        assert_eq!((x * seven) % x, D38s12::ZERO);
434    }
435
436    /// `x % x == ZERO` for non-zero x.
437    #[test]
438    fn rem_self_is_zero() {
439        let x = D38s12::from_bits(1_500_000_000_000);
440        assert_eq!(x % x, D38s12::ZERO);
441
442        let y = D38s12::from_bits(-7_321_654_987_000);
443        assert_eq!(y % y, D38s12::ZERO);
444    }
445
446    /// **Headline claim**: `1.1 + 2.2 == 3.3` exactly. This is the
447    /// distinguishing property versus binary floats (`f64`'s
448    /// `1.1 + 2.2 == 3.3000000000000003`). Uses known scaled
449    /// bit-patterns rather than the (not-yet-shipped) `FromStr`.
450    #[test]
451    fn one_point_one_plus_two_point_two_equals_three_point_three() {
452        let one_point_one = D38s12::from_bits(1_100_000_000_000); // 1.1
453        let two_point_two = D38s12::from_bits(2_200_000_000_000); // 2.2
454        let three_point_three = D38s12::from_bits(3_300_000_000_000); // 3.3
455        assert_eq!(one_point_one + two_point_two, three_point_three);
456    }
457
458    /// `(a * b) / b == a` round-trip for representative non-trivial values.
459    /// At SCALE = 12, picking moderate operands keeps `a.0 * b.0` well
460    /// inside the i128 boundary.
461    #[test]
462    fn mul_round_trip_canonical_claim() {
463        // a = 1.5, b = 2.5 -> a * b = 3.75; (3.75 / 2.5) == 1.5
464        let a = D38s12::from_bits(1_500_000_000_000);
465        let b = D38s12::from_bits(2_500_000_000_000);
466        let product = a * b;
467        assert_eq!(product, D38s12::from_bits(3_750_000_000_000));
468        assert_eq!(product / b, a);
469
470        // Negative-operand round-trip.
471        let c = D38s12::from_bits(-7_321_654_987_000);
472        let d = D38s12::from_bits(13_000_000_000); // 0.013
473        let cd = c * d;
474        assert_eq!(cd / d, c);
475    }
476
477    /// In-place MulAssign matches `Mul`.
478    #[test]
479    fn mul_assign_matches_mul() {
480        let a = D38s12::from_bits(1_500_000_000_000);
481        let b = D38s12::from_bits(2_500_000_000_000);
482        let mut x = a;
483        x *= b;
484        assert_eq!(x, a * b);
485    }
486
487    /// In-place DivAssign matches `Div`.
488    #[test]
489    fn div_assign_matches_div() {
490        let a = D38s12::from_bits(3_750_000_000_000);
491        let b = D38s12::from_bits(2_500_000_000_000);
492        let mut x = a;
493        x /= b;
494        assert_eq!(x, a / b);
495    }
496
497    /// In-place RemAssign matches `Rem`.
498    #[test]
499    fn rem_assign_matches_rem() {
500        let a = D38s12::from_bits(7_500_000_000_000);
501        let b = D38s12::from_bits(2_000_000_000_000); // 2.0
502        let mut x = a;
503        x %= b;
504        assert_eq!(x, a % b);
505    }
506
507    /// `Mul` is commutative under canonical equality.
508    #[test]
509    fn mul_is_commutative() {
510        let a = D38s12::from_bits(1_500_000_000_000);
511        let b = D38s12::from_bits(2_500_000_000_000);
512        assert_eq!(a * b, b * a);
513    }
514
515    /// `Mul` rescales correctly: 0.5 * 0.5 == 0.25 (bit-exact).
516    #[test]
517    fn mul_subunit_rescales_exactly() {
518        let half = D38s12::from_bits(500_000_000_000); // 0.5
519        let quarter = D38s12::from_bits(250_000_000_000); // 0.25
520        assert_eq!(half * half, quarter);
521    }
522
523    /// `Div` rescales correctly: 0.5 / 2 == 0.25.
524    #[test]
525    fn div_rescales_exactly() {
526        let half = D38s12::from_bits(500_000_000_000); // 0.5
527        let two = D38s12::from_bits(2_000_000_000_000); // 2.0
528        let quarter = D38s12::from_bits(250_000_000_000); // 0.25
529        assert_eq!(half / two, quarter);
530    }
531
532    /// `Rem` matches `i128 %` truncated-toward-zero semantics.
533    /// 5.5 % 2.0 == 1.5 (since 5.5 = 2 * 2.0 + 1.5).
534    #[test]
535    fn rem_truncates_toward_zero() {
536        let a = D38s12::from_bits(5_500_000_000_000);
537        let b = D38s12::from_bits(2_000_000_000_000);
538        let expected = D38s12::from_bits(1_500_000_000_000);
539        assert_eq!(a % b, expected);
540
541        // Negative dividend keeps the sign of the dividend (matches i128 %).
542        let neg = D38s12::from_bits(-5_500_000_000_000);
543        let neg_expected = D38s12::from_bits(-1_500_000_000_000);
544        assert_eq!(neg % b, neg_expected);
545    }
546
547    /// Default policy: Mul overflow panics in debug. The product
548    /// `MAX * 2` overflows the FINAL i128 quotient (256-bit
549    /// intermediate doesn't matter -- the result still can't fit).
550    #[test]
551    #[cfg(debug_assertions)]
552    #[should_panic(expected = "overflow")]
553    fn mul_overflow_panics_in_debug() {
554        let two = D38s12::from_bits(2_000_000_000_000);
555        let _ = D38s12::MAX * two;
556    }
557
558    /// Widening multiply correctness: at operand magnitudes above the
559    /// naive form's `sqrt(i128::MAX)` ~= 1.3e19 boundary, the widening
560    /// multiply produces the correct result.
561    ///
562    /// Operands chosen around +/- 5e22. Expected result hand-computed:
563    /// `(5e22 * 3e22) / 10^12 = 1.5e33`, which fits comfortably inside
564    /// the final i128 range (i128::MAX ~= 1.7e38).
565    #[test]
566    fn mul_wide_operands_match_widened_form() {
567        let a = D38s12::from_bits(50_000_000_000_000_000_000_000);
568        let b = D38s12::from_bits(30_000_000_000_000_000_000_000);
569        let expected = D38s12::from_bits(1_500_000_000_000_000_000_000_000_000_000_000);
570        assert_eq!(a * b, expected);
571        // Symmetric.
572        assert_eq!(b * a, expected);
573    }
574
575    /// Signed round-trip at wide operand magnitudes: `(a * b) / b == a`.
576    #[test]
577    fn mul_div_wide_round_trip() {
578        let a = D38s12::from_bits(50_000_000_000_000_000_000_000);
579        let b = D38s12::from_bits(30_000_000_000_000_000_000_000);
580        let prod = a * b;
581        // Round-trip: prod / b should recover a.
582        assert_eq!(prod / b, a);
583    }
584
585    /// Sign handling at wide operand magnitudes: mixed and same signs.
586    #[test]
587    fn mul_wide_negative_signs() {
588        let a = D38s12::from_bits(50_000_000_000_000_000_000_000);
589        let b = D38s12::from_bits(30_000_000_000_000_000_000_000);
590        let neg_a = -a;
591        let neg_b = -b;
592        let pos_prod = a * b;
593        let neg_prod = -pos_prod;
594        assert_eq!(neg_a * b, neg_prod);
595        assert_eq!(a * neg_b, neg_prod);
596        assert_eq!(neg_a * neg_b, pos_prod);
597    }
598
599    /// Widening divide correctness at large dividend magnitudes.
600    #[test]
601    fn div_wide_dividend_correct() {
602        // a = 10^22 raw (~10^10 in scaled value at SCALE=12)
603        let a = D38s12::from_bits(10_i128.pow(22));
604        // b = 2 raw (sub-LSB; effectively divides by 2 * 10^-12)
605        let b = D38s12::from_bits(2);
606        // Expected: (a.0 * 10^12) / b.0 = (10^34) / 2 = 5e33.
607        let expected = D38s12::from_bits(5 * 10_i128.pow(33));
608        assert_eq!(a / b, expected);
609    }
610
611    /// Widening divide round-trip: forces the numerator widening path
612    /// because `a * 10^12` exceeds `i128::MAX`.
613    #[test]
614    fn div_wide_round_trip_exact() {
615        // a = 10^27 raw: a * 10^12 = 10^39 > i128::MAX (1.7e38).
616        // Divide by b = 100 raw: q = 10^39 / 100 = 10^37, which fits i128.
617        let a = D38s12::from_bits(10_i128.pow(27));
618        let b = D38s12::from_bits(100);
619        let q = a / b;
620        // q = (10^27 * 10^12) / 100 = 10^37 raw.
621        let expected = D38s12::from_bits(10_i128.pow(37));
622        assert_eq!(q, expected);
623    }
624
625    /// Div at SCALE = 0: scale-narrowing step is `a / b`, rounded per
626    /// the crate-default mode (HalfToEven by default).
627    #[test]
628    fn div_scale_zero_matches_i128_div() {
629        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN {
630            return;
631        }
632        type D0 = crate::core_type::D38<0>;
633        let a = D0::from_bits(15);
634        let b = D0::from_bits(4);
635        // 15 / 4 = 3.75 -> 4 under HalfToEven (no tie at .75).
636        assert_eq!(a / b, D0::from_bits(4));
637        assert_eq!((-a) / b, D0::from_bits(-4));
638        // Exact divide is unchanged.
639        let c = D0::from_bits(16);
640        assert_eq!(c / b, D0::from_bits(4));
641    }
642
643    /// Mul at SCALE = 0: reduces to plain `i128 *`.
644    #[test]
645    fn mul_scale_zero_matches_i128_mul() {
646        type D0 = crate::core_type::D38<0>;
647        let a = D0::from_bits(7);
648        let b = D0::from_bits(11);
649        assert_eq!(a * b, D0::from_bits(77));
650        assert_eq!((-a) * b, D0::from_bits(-77));
651    }
652
653    /// Default policy: division by zero panics.
654    #[test]
655    #[should_panic]
656    fn div_by_zero_panics() {
657        let _ = D38s12::ONE / D38s12::ZERO;
658    }
659
660    /// Default policy: remainder with zero divisor panics.
661    #[test]
662    #[should_panic]
663    fn rem_by_zero_panics() {
664        let _ = D38s12::ONE % D38s12::ZERO;
665    }
666
667    // ── Math methods ──
668
669    // ── abs ──
670
671    /// `abs(0) == 0`.
672    #[test]
673    fn abs_zero_is_zero() {
674        assert_eq!(D38s12::ZERO.abs(), D38s12::ZERO);
675    }
676
677    /// `abs(positive) == positive`.
678    #[test]
679    fn abs_positive_is_self() {
680        let x = D38s12::from_bits(1_500_000_000_000); // 1.5
681        assert_eq!(x.abs(), x);
682    }
683
684    /// `abs(negative) == positive(magnitude)`.
685    #[test]
686    fn abs_negative_is_positive() {
687        let neg = D38s12::from_bits(-1_500_000_000_000);
688        let pos = D38s12::from_bits(1_500_000_000_000);
689        assert_eq!(neg.abs(), pos);
690    }
691
692    /// `abs(MIN)` panics in debug builds (no positive counterpart in
693    /// two's-complement). Locks the panic-debug policy.
694    #[test]
695    #[cfg(debug_assertions)]
696    #[should_panic(expected = "overflow")]
697    fn abs_min_panics_in_debug() {
698        let _ = D38s12::MIN.abs();
699    }
700
701    // ── signum ──
702
703    /// `signum(0) == ZERO` (no sign for zero).
704    #[test]
705    fn signum_zero_is_zero() {
706        assert_eq!(D38s12::ZERO.signum(), D38s12::ZERO);
707    }
708
709    /// `signum(positive) == ONE`.
710    #[test]
711    fn signum_positive_is_one() {
712        let x = D38s12::from_bits(1_500_000_000_000);
713        assert_eq!(x.signum(), D38s12::ONE);
714
715        // Smallest positive (1 LSB).
716        let tiny = D38s12::from_bits(1);
717        assert_eq!(tiny.signum(), D38s12::ONE);
718    }
719
720    /// `signum(negative) == -ONE`.
721    #[test]
722    fn signum_negative_is_neg_one() {
723        let x = D38s12::from_bits(-1_500_000_000_000);
724        assert_eq!(x.signum(), -D38s12::ONE);
725
726        let tiny_neg = D38s12::from_bits(-1);
727        assert_eq!(tiny_neg.signum(), -D38s12::ONE);
728    }
729
730    // ── floor ──
731
732    /// `floor(2.5) == 2.0` (positive fractional rounds down).
733    #[test]
734    fn floor_positive_fractional_rounds_down() {
735        let x = D38s12::from_bits(2_500_000_000_000);
736        let expected = D38s12::from_bits(2_000_000_000_000);
737        assert_eq!(x.floor(), expected);
738    }
739
740    /// `floor(-2.5) == -3.0` (negative fractional rounds toward
741    /// negative infinity, NOT toward zero -- this is the key sign
742    /// distinction from `trunc`).
743    #[test]
744    fn floor_negative_fractional_rounds_down_toward_neg_inf() {
745        let x = D38s12::from_bits(-2_500_000_000_000);
746        let expected = D38s12::from_bits(-3_000_000_000_000);
747        assert_eq!(x.floor(), expected);
748
749        // Smaller fractional part: -0.5 -> -1.0
750        let small_neg = D38s12::from_bits(-500_000_000_000);
751        let small_expected = D38s12::from_bits(-1_000_000_000_000);
752        assert_eq!(small_neg.floor(), small_expected);
753    }
754
755    /// `floor(integer) == integer` (already at an integer boundary).
756    #[test]
757    fn floor_integer_unchanged() {
758        let two = D38s12::from_bits(2_000_000_000_000);
759        assert_eq!(two.floor(), two);
760
761        let neg_two = D38s12::from_bits(-2_000_000_000_000);
762        assert_eq!(neg_two.floor(), neg_two);
763
764        assert_eq!(D38s12::ZERO.floor(), D38s12::ZERO);
765    }
766
767    // ── ceil ──
768
769    /// `ceil(2.5) == 3.0`.
770    #[test]
771    fn ceil_positive_fractional_rounds_up() {
772        let x = D38s12::from_bits(2_500_000_000_000);
773        let expected = D38s12::from_bits(3_000_000_000_000);
774        assert_eq!(x.ceil(), expected);
775    }
776
777    /// `ceil(-2.5) == -2.0`. Sign distinction from `floor`: ceiling
778    /// rounds toward positive infinity.
779    #[test]
780    fn ceil_negative_fractional_rounds_up_toward_pos_inf() {
781        let x = D38s12::from_bits(-2_500_000_000_000);
782        let expected = D38s12::from_bits(-2_000_000_000_000);
783        assert_eq!(x.ceil(), expected);
784
785        // -0.5 -> 0
786        let small_neg = D38s12::from_bits(-500_000_000_000);
787        assert_eq!(small_neg.ceil(), D38s12::ZERO);
788    }
789
790    /// `ceil(integer) == integer`.
791    #[test]
792    fn ceil_integer_unchanged() {
793        let two = D38s12::from_bits(2_000_000_000_000);
794        assert_eq!(two.ceil(), two);
795
796        let neg_two = D38s12::from_bits(-2_000_000_000_000);
797        assert_eq!(neg_two.ceil(), neg_two);
798
799        assert_eq!(D38s12::ZERO.ceil(), D38s12::ZERO);
800    }
801
802    // ── round ──
803
804    /// Half-away-from-zero locked policy. Property test asserting:
805    /// - 2.5 -> 3.0 (positive half rounds up)
806    /// - 2.4 -> 2.0
807    /// - 2.6 -> 3.0
808    /// - -2.5 -> -3.0 (negative half rounds away from zero, i.e. down)
809    /// - -2.4 -> -2.0
810    /// - -2.6 -> -3.0
811    #[test]
812    fn round_half_away_from_zero() {
813        // Positive halves
814        let two_point_five = D38s12::from_bits(2_500_000_000_000);
815        assert_eq!(two_point_five.round(), D38s12::from_bits(3_000_000_000_000));
816
817        let two_point_four = D38s12::from_bits(2_400_000_000_000);
818        assert_eq!(two_point_four.round(), D38s12::from_bits(2_000_000_000_000));
819
820        let two_point_six = D38s12::from_bits(2_600_000_000_000);
821        assert_eq!(two_point_six.round(), D38s12::from_bits(3_000_000_000_000));
822
823        // Negative halves -- away from zero == toward neg infinity
824        let neg_two_point_five = D38s12::from_bits(-2_500_000_000_000);
825        assert_eq!(neg_two_point_five.round(), D38s12::from_bits(-3_000_000_000_000));
826
827        let neg_two_point_four = D38s12::from_bits(-2_400_000_000_000);
828        assert_eq!(neg_two_point_four.round(), D38s12::from_bits(-2_000_000_000_000));
829
830        let neg_two_point_six = D38s12::from_bits(-2_600_000_000_000);
831        assert_eq!(neg_two_point_six.round(), D38s12::from_bits(-3_000_000_000_000));
832
833        // Zero
834        assert_eq!(D38s12::ZERO.round(), D38s12::ZERO);
835    }
836
837    // ── trunc / fract ──
838
839    /// `trunc` drops the fractional part (rounds toward zero), unlike
840    /// `floor` which rounds toward negative infinity.
841    #[test]
842    fn trunc_drops_fractional() {
843        // Positive
844        let x = D38s12::from_bits(2_500_000_000_000);
845        assert_eq!(x.trunc(), D38s12::from_bits(2_000_000_000_000));
846
847        // Negative -- key sign distinction: trunc(-2.5) == -2.0
848        // (floor(-2.5) would be -3.0)
849        let neg = D38s12::from_bits(-2_500_000_000_000);
850        assert_eq!(neg.trunc(), D38s12::from_bits(-2_000_000_000_000));
851
852        // Zero
853        assert_eq!(D38s12::ZERO.trunc(), D38s12::ZERO);
854
855        // Already-integer values
856        let two = D38s12::from_bits(2_000_000_000_000);
857        assert_eq!(two.trunc(), two);
858    }
859
860    /// `fract` keeps only the fractional part. Sign matches the sign
861    /// of `self` (because `trunc` rounds toward zero).
862    #[test]
863    fn fract_keeps_only_fractional() {
864        let x = D38s12::from_bits(2_500_000_000_000);
865        assert_eq!(x.fract(), D38s12::from_bits(500_000_000_000));
866
867        // Negative: fract preserves dividend sign
868        let neg = D38s12::from_bits(-2_500_000_000_000);
869        assert_eq!(neg.fract(), D38s12::from_bits(-500_000_000_000));
870
871        // Integer values have zero fract
872        let two = D38s12::from_bits(2_000_000_000_000);
873        assert_eq!(two.fract(), D38s12::ZERO);
874
875        assert_eq!(D38s12::ZERO.fract(), D38s12::ZERO);
876    }
877
878    /// Identity: `trunc(x) + fract(x) == x` for any `x`.
879    #[test]
880    fn trunc_plus_fract_equals_self() {
881        let cases = [
882            D38s12::from_bits(2_500_000_000_000),
883            D38s12::from_bits(-2_500_000_000_000),
884            D38s12::from_bits(7_321_654_987_000),
885            D38s12::from_bits(-7_321_654_987_000),
886            D38s12::ZERO,
887            D38s12::ONE,
888            -D38s12::ONE,
889            D38s12::from_bits(1), // sub-LSB fractional
890            D38s12::from_bits(-1),
891        ];
892        for x in cases {
893            assert_eq!(x.trunc() + x.fract(), x, "failed for {:?}", x);
894        }
895    }
896
897    // ── min / max / clamp ──
898
899    /// Basic min/max/clamp on representative values.
900    #[test]
901    fn min_max_clamp_basic() {
902        let a = D38s12::from_bits(1_000_000_000_000); // 1.0
903        let b = D38s12::from_bits(2_000_000_000_000); // 2.0
904        let c = D38s12::from_bits(3_000_000_000_000); // 3.0
905
906        assert_eq!(a.min(b), a);
907        assert_eq!(b.min(a), a);
908        assert_eq!(a.max(b), b);
909        assert_eq!(b.max(a), b);
910
911        // clamp inside range -- pass through
912        assert_eq!(b.clamp(a, c), b);
913        // clamp below lo
914        assert_eq!(D38s12::ZERO.clamp(a, c), a);
915        // clamp above hi
916        let four = D38s12::from_bits(4_000_000_000_000);
917        assert_eq!(four.clamp(a, c), c);
918
919        // Negative values
920        let neg_a = -a;
921        let neg_b = -b;
922        assert_eq!(neg_a.min(neg_b), neg_b); // -2.0 < -1.0
923        assert_eq!(neg_a.max(neg_b), neg_a);
924    }
925
926    // ── recip ──
927
928    /// `recip(2.0) == 0.5`, `recip(0.5) == 2.0`.
929    #[test]
930    fn recip_inverts_known_values() {
931        let two = D38s12::from_bits(2_000_000_000_000);
932        let half = D38s12::from_bits(500_000_000_000);
933        assert_eq!(two.recip(), half);
934        assert_eq!(half.recip(), two);
935
936        // recip of ONE is ONE
937        assert_eq!(D38s12::ONE.recip(), D38s12::ONE);
938
939        // recip of -ONE is -ONE
940        assert_eq!((-D38s12::ONE).recip(), -D38s12::ONE);
941    }
942
943    /// `recip(ZERO)` panics (division by zero).
944    #[test]
945    #[should_panic]
946    fn recip_zero_panics() {
947        let _ = D38s12::ZERO.recip();
948    }
949
950    // ── copysign ──
951
952    /// Magnitude of self, sign of `sign` arg.
953    #[test]
954    fn copysign_basic() {
955        let pos = D38s12::from_bits(1_500_000_000_000);
956        let neg = D38s12::from_bits(-1_500_000_000_000);
957
958        // copysign(pos, pos) == pos
959        assert_eq!(pos.copysign(pos), pos);
960        // copysign(pos, neg) == neg
961        assert_eq!(pos.copysign(neg), neg);
962        // copysign(neg, pos) == pos
963        assert_eq!(neg.copysign(pos), pos);
964        // copysign(neg, neg) == neg
965        assert_eq!(neg.copysign(neg), neg);
966    }
967
968    /// `copysign(x, ZERO)` -- zero is treated as positive (no negative
969    /// zero in i128). This locks the v1 policy.
970    #[test]
971    fn copysign_zero() {
972        let neg = D38s12::from_bits(-1_500_000_000_000);
973        let pos = D38s12::from_bits(1_500_000_000_000);
974
975        // sign == ZERO -> positive magnitude
976        assert_eq!(neg.copysign(D38s12::ZERO), pos);
977        assert_eq!(pos.copysign(D38s12::ZERO), pos);
978
979        // self == ZERO -> result == ZERO regardless of sign
980        assert_eq!(D38s12::ZERO.copysign(neg), D38s12::ZERO);
981        assert_eq!(D38s12::ZERO.copysign(pos), D38s12::ZERO);
982    }
983
984    // ── div_euclid / rem_euclid ──
985
986    /// Positive operands match plain integer division.
987    /// 5.0 / 2.0 = 2.5; div_euclid -> floor = 2.0; rem_euclid -> 1.0.
988    #[test]
989    fn div_euclid_positive() {
990        let a = D38s12::from_bits(5_000_000_000_000); // 5.0
991        let b = D38s12::from_bits(2_000_000_000_000); // 2.0
992
993        let q = a.div_euclid(b);
994        assert_eq!(q, D38s12::from_bits(2_000_000_000_000)); // 2
995
996        let r = a.rem_euclid(b);
997        assert_eq!(r, D38s12::from_bits(1_000_000_000_000)); // 1
998
999        // Identity: q*b + r == a
1000        assert_eq!(q * b + r, a);
1001    }
1002
1003    /// Negative dividend: -5.0 div_euclid 2.0 -> -3.0 (Euclidean, with
1004    /// non-negative remainder).
1005    #[test]
1006    fn div_euclid_negative_dividend() {
1007        let a = D38s12::from_bits(-5_000_000_000_000); // -5.0
1008        let b = D38s12::from_bits(2_000_000_000_000); // 2.0
1009
1010        let q = a.div_euclid(b);
1011        // -5 = -3*2 + 1, so quotient = -3, rem = 1
1012        assert_eq!(q, D38s12::from_bits(-3_000_000_000_000));
1013
1014        let r = a.rem_euclid(b);
1015        assert_eq!(r, D38s12::from_bits(1_000_000_000_000));
1016
1017        // Identity: q*b + r == a
1018        assert_eq!(q * b + r, a);
1019    }
1020
1021    /// Negative divisor: 5.0 div_euclid -2.0 -> -2.0 (Euclidean keeps
1022    /// remainder non-negative).
1023    #[test]
1024    fn div_euclid_negative_divisor() {
1025        let a = D38s12::from_bits(5_000_000_000_000); // 5.0
1026        let b = D38s12::from_bits(-2_000_000_000_000); // -2.0
1027
1028        let q = a.div_euclid(b);
1029        assert_eq!(q, D38s12::from_bits(-2_000_000_000_000)); // -2
1030
1031        let r = a.rem_euclid(b);
1032        assert_eq!(r, D38s12::from_bits(1_000_000_000_000)); // 1 (non-negative!)
1033
1034        // Identity: q*b + r == a
1035        assert_eq!(q * b + r, a);
1036    }
1037
1038    /// Property: `(a.div_euclid(b)) * b + a.rem_euclid(b) == a` for
1039    /// representative sign combinations.
1040    #[test]
1041    fn rem_euclid_consistency_with_div_euclid() {
1042        let cases: &[(i128, i128)] = &[
1043            (5_000_000_000_000, 2_000_000_000_000),
1044            (-5_000_000_000_000, 2_000_000_000_000),
1045            (5_000_000_000_000, -2_000_000_000_000),
1046            (-5_000_000_000_000, -2_000_000_000_000),
1047            (7_321_654_987_000, 13_000_000_000),
1048            (-7_321_654_987_000, 13_000_000_000),
1049        ];
1050        for (a_bits, b_bits) in cases {
1051            let a = D38s12::from_bits(*a_bits);
1052            let b = D38s12::from_bits(*b_bits);
1053            let q = a.div_euclid(b);
1054            let r = a.rem_euclid(b);
1055            assert_eq!(q * b + r, a, "failed for a={}, b={}", a_bits, b_bits);
1056            // Remainder must be non-negative (Euclidean property)
1057            assert!(r.0 >= 0, "rem_euclid returned negative for a={}, b={}: {}",
1058                    a_bits, b_bits, r.0);
1059        }
1060    }
1061
1062    // ── div_floor / div_ceil ──
1063
1064    /// `div_floor` rounds toward negative infinity. Positive operands
1065    /// match plain truncating div.
1066    #[test]
1067    fn div_floor_basic() {
1068        // 5.0 / 2.0 -> floor(2.5) = 2.0
1069        let a = D38s12::from_bits(5_000_000_000_000);
1070        let b = D38s12::from_bits(2_000_000_000_000);
1071        assert_eq!(a.div_floor(b), D38s12::from_bits(2_000_000_000_000));
1072
1073        // -5.0 / 2.0 -> floor(-2.5) = -3.0
1074        let neg_a = D38s12::from_bits(-5_000_000_000_000);
1075        assert_eq!(neg_a.div_floor(b), D38s12::from_bits(-3_000_000_000_000));
1076
1077        // -5.0 / -2.0 -> floor(2.5) = 2.0 (sign distinction from div_euclid)
1078        let neg_b = D38s12::from_bits(-2_000_000_000_000);
1079        assert_eq!(neg_a.div_floor(neg_b), D38s12::from_bits(2_000_000_000_000));
1080
1081        // 5.0 / -2.0 -> floor(-2.5) = -3.0
1082        // (div_euclid here would be -2 because rem must be >= 0.)
1083        assert_eq!(a.div_floor(neg_b), D38s12::from_bits(-3_000_000_000_000));
1084    }
1085
1086    /// `div_ceil` rounds toward positive infinity.
1087    #[test]
1088    fn div_ceil_basic() {
1089        // 5.0 / 2.0 -> ceil(2.5) = 3.0
1090        let a = D38s12::from_bits(5_000_000_000_000);
1091        let b = D38s12::from_bits(2_000_000_000_000);
1092        assert_eq!(a.div_ceil(b), D38s12::from_bits(3_000_000_000_000));
1093
1094        // -5.0 / 2.0 -> ceil(-2.5) = -2.0
1095        let neg_a = D38s12::from_bits(-5_000_000_000_000);
1096        assert_eq!(neg_a.div_ceil(b), D38s12::from_bits(-2_000_000_000_000));
1097
1098        // 4.0 / 2.0 -> exact -> 2.0
1099        let four = D38s12::from_bits(4_000_000_000_000);
1100        assert_eq!(four.div_ceil(b), D38s12::from_bits(2_000_000_000_000));
1101    }
1102
1103    // ── abs_diff ──
1104
1105    /// `abs_diff` is commutative and non-negative.
1106    #[test]
1107    fn abs_diff_commutative() {
1108        let a = D38s12::from_bits(5_000_000_000_000); // 5.0
1109        let b = D38s12::from_bits(2_000_000_000_000); // 2.0
1110        let expected = D38s12::from_bits(3_000_000_000_000); // 3.0
1111
1112        assert_eq!(a.abs_diff(b), expected);
1113        assert_eq!(b.abs_diff(a), expected);
1114
1115        // Negative operands
1116        let neg_a = -a;
1117        let neg_b = -b;
1118        // |(-5) - (-2)| = |-3| = 3
1119        assert_eq!(neg_a.abs_diff(neg_b), expected);
1120        assert_eq!(neg_b.abs_diff(neg_a), expected);
1121
1122        // Mixed sign: |5 - (-2)| = 7
1123        let seven = D38s12::from_bits(7_000_000_000_000);
1124        assert_eq!(a.abs_diff(neg_b), seven);
1125        assert_eq!(neg_b.abs_diff(a), seven);
1126    }
1127
1128    /// `abs_diff(x, x) == 0` and `abs_diff(x, 0) == abs(x)`.
1129    #[test]
1130    fn abs_diff_zero() {
1131        let x = D38s12::from_bits(1_500_000_000_000);
1132        assert_eq!(x.abs_diff(x), D38s12::ZERO);
1133        assert_eq!(x.abs_diff(D38s12::ZERO), x.abs());
1134
1135        let neg = -x;
1136        assert_eq!(neg.abs_diff(D38s12::ZERO), x);
1137    }
1138
1139    // ── midpoint ──
1140
1141    /// Midpoint of two representative values.
1142    #[test]
1143    fn midpoint_basic() {
1144        let a = D38s12::from_bits(1_000_000_000_000); // 1.0
1145        let b = D38s12::from_bits(3_000_000_000_000); // 3.0
1146        assert_eq!(a.midpoint(b), D38s12::from_bits(2_000_000_000_000)); // 2.0
1147
1148        // Negative
1149        let neg_a = -a;
1150        let neg_b = -b;
1151        assert_eq!(neg_a.midpoint(neg_b), D38s12::from_bits(-2_000_000_000_000));
1152
1153        // Mixed sign: midpoint(-1, 1) == 0
1154        assert_eq!(neg_a.midpoint(a), D38s12::ZERO);
1155    }
1156
1157    /// Midpoint near MAX must not overflow (the whole point of using
1158    /// `i128::midpoint` over `(a + b) / 2`).
1159    #[test]
1160    fn midpoint_no_overflow_at_max() {
1161        // (MAX + MAX) / 2 == MAX, but a naive (a+b)/2 would overflow.
1162        // i128::midpoint handles this without intermediate overflow.
1163        assert_eq!(D38s12::MAX.midpoint(D38s12::MAX), D38s12::MAX);
1164        assert_eq!(D38s12::MIN.midpoint(D38s12::MIN), D38s12::MIN);
1165        // midpoint(MIN, MAX) -- delegates to i128::midpoint. The
1166        // Rust 1.95 stabilised implementation rounds the average
1167        // toward zero for signed integers (so MIN + MAX = -1 averages
1168        // to 0, not -1). Just assert it doesn't overflow / panic.
1169        let mid = D38s12::MIN.midpoint(D38s12::MAX);
1170        assert!(mid.0 == 0 || mid.0 == -1,
1171                "midpoint(MIN, MAX) should be 0 or -1, got {}", mid.0);
1172    }
1173
1174    // ── Float-shape compat predicates ──
1175
1176    #[test]
1177    fn is_nan_always_false() {
1178        assert!(!D38s12::ZERO.is_nan());
1179        assert!(!D38s12::ONE.is_nan());
1180        assert!(!D38s12::MAX.is_nan());
1181        assert!(!D38s12::MIN.is_nan());
1182    }
1183
1184    #[test]
1185    fn is_infinite_always_false() {
1186        assert!(!D38s12::ZERO.is_infinite());
1187        assert!(!D38s12::MAX.is_infinite());
1188        assert!(!D38s12::MIN.is_infinite());
1189    }
1190
1191    #[test]
1192    fn is_finite_always_true() {
1193        assert!(D38s12::ZERO.is_finite());
1194        assert!(D38s12::ONE.is_finite());
1195        assert!(D38s12::MAX.is_finite());
1196        assert!(D38s12::MIN.is_finite());
1197    }
1198
1199    #[test]
1200    fn is_normal_zero_is_false() {
1201        assert!(!D38s12::ZERO.is_normal());
1202    }
1203
1204    #[test]
1205    fn is_normal_nonzero_is_true() {
1206        assert!(D38s12::ONE.is_normal());
1207        assert!((-D38s12::ONE).is_normal());
1208        assert!(D38s12::from_bits(1).is_normal()); // smallest positive
1209        assert!(D38s12::from_bits(-1).is_normal()); // smallest negative
1210        assert!(D38s12::MAX.is_normal());
1211        assert!(D38s12::MIN.is_normal());
1212    }
1213
1214    /// is_zero / is_positive / is_negative resolve in the foundation
1215    /// slice (cheap predicates).
1216    #[test]
1217    fn is_zero_predicates() {
1218        assert!(D38s12::ZERO.is_zero());
1219        assert!(!D38s12::ZERO.is_positive());
1220        assert!(!D38s12::ZERO.is_negative());
1221
1222        assert!(!D38s12::from_bits(1).is_zero());
1223        assert!(D38s12::from_bits(1).is_positive());
1224        assert!(!D38s12::from_bits(1).is_negative());
1225
1226        assert!(!D38s12::from_bits(-1).is_zero());
1227        assert!(!D38s12::from_bits(-1).is_positive());
1228        assert!(D38s12::from_bits(-1).is_negative());
1229    }
1230}