decimal_scaled/num_traits_impls.rs
1//! `num-traits` 0.2 trait implementations for [`I128`].
2//!
3//! Allows generic numeric code (nalgebra, ndarray, statrs, and other
4//! crates that accept "any number type") to use `I128<SCALE>` as a
5//! scalar. Crates that provide generic numeric algorithms almost
6//! universally bound on [`num_traits`] traits rather than defining
7//! their own numeric interfaces.
8//!
9//! # Trait coverage
10//!
11//! - [`num_traits::Zero`] / [`num_traits::One`] — additive and
12//! multiplicative identities.
13//! - [`num_traits::Num`] — umbrella numeric trait combining
14//! `Zero + One + PartialEq + Add + Sub + Mul + Div + Rem` with a
15//! `from_str_radix` constructor.
16//! - [`num_traits::Bounded`] — `min_value()` / `max_value()` for
17//! generic clamping code.
18//! - [`num_traits::Signed`] — `abs`, `signum`, `is_positive`,
19//! `is_negative`, `abs_sub`.
20//! - [`num_traits::FromPrimitive`] / [`num_traits::ToPrimitive`] —
21//! fallible conversions to and from the primitive numeric types.
22//! - [`num_traits::CheckedAdd`] / [`num_traits::CheckedSub`] /
23//! [`num_traits::CheckedMul`] / [`num_traits::CheckedDiv`] /
24//! [`num_traits::CheckedRem`] / [`num_traits::CheckedNeg`] —
25//! overflow-safe variants returning `Option<Self>`.
26//!
27//! # `from_str_radix`
28//!
29//! [`num_traits::Num::from_str_radix`] delegates to
30//! [`core::str::FromStr`] for `radix == 10` and rejects every other
31//! radix. The compile-time signature is stable regardless of whether
32//! the underlying `FromStr` implementation is complete.
33//!
34//! # `CheckedMul` / `CheckedDiv`
35//!
36//! Both traits delegate to the inherent [`I128::checked_mul`] and
37//! [`I128::checked_div`] methods. The trait and inherent paths are
38//! bit-identical. `CheckedAdd`, `CheckedSub`, `CheckedRem`, and
39//! `CheckedNeg` operate directly on the raw `i128` storage and
40//! delegate to `i128`'s own checked intrinsics; no rescaling is
41//! needed for those operations.
42
43use num_traits::{
44 Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub,
45 FromPrimitive, Num, NumCast, One, Signed, ToPrimitive, Zero,
46};
47
48use crate::core_type::{I128, ParseDecimalError};
49
50// ---------------------------------------------------------------------------
51// Zero / One
52// ---------------------------------------------------------------------------
53
54impl<const SCALE: u32> Zero for I128<SCALE> {
55 /// Returns the additive identity for `I128<SCALE>`.
56 ///
57 /// Equivalent to [`I128::ZERO`].
58 ///
59 /// # Precision
60 ///
61 /// Strict: all arithmetic is integer-only; result is bit-exact.
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use decimal_scaled::I128s12;
67 /// use num_traits::Zero;
68 ///
69 /// assert_eq!(I128s12::zero(), I128s12::ZERO);
70 /// ```
71 #[inline]
72 fn zero() -> Self {
73 Self::ZERO
74 }
75
76 /// Returns `true` if `self` equals the additive identity.
77 ///
78 /// # Precision
79 ///
80 /// Strict: all arithmetic is integer-only; result is bit-exact.
81 ///
82 /// # Examples
83 ///
84 /// ```
85 /// use decimal_scaled::I128s12;
86 /// use num_traits::Zero;
87 ///
88 /// assert!(I128s12::ZERO.is_zero());
89 /// assert!(!I128s12::ONE.is_zero());
90 /// ```
91 #[inline]
92 fn is_zero(&self) -> bool {
93 self.0 == 0
94 }
95}
96
97impl<const SCALE: u32> One for I128<SCALE> {
98 /// Returns the multiplicative identity for `I128<SCALE>`.
99 ///
100 /// Equivalent to [`I128::ONE`].
101 ///
102 /// # Precision
103 ///
104 /// Strict: all arithmetic is integer-only; result is bit-exact.
105 ///
106 /// # Examples
107 ///
108 /// ```
109 /// use decimal_scaled::I128s12;
110 /// use num_traits::One;
111 ///
112 /// assert_eq!(I128s12::one(), I128s12::ONE);
113 /// ```
114 #[inline]
115 fn one() -> Self {
116 Self::ONE
117 }
118
119 /// Returns `true` if `self` equals the multiplicative identity.
120 ///
121 /// Provided explicitly rather than relying on the default so that
122 /// the check is a single integer comparison instead of going through
123 /// the multiplication chain.
124 ///
125 /// # Precision
126 ///
127 /// Strict: all arithmetic is integer-only; result is bit-exact.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use decimal_scaled::I128s12;
133 /// use num_traits::One;
134 ///
135 /// assert!(I128s12::ONE.is_one());
136 /// assert!(!I128s12::ZERO.is_one());
137 /// ```
138 #[inline]
139 fn is_one(&self) -> bool {
140 self.0 == Self::multiplier()
141 }
142}
143
144// ---------------------------------------------------------------------------
145// Num
146// ---------------------------------------------------------------------------
147
148impl<const SCALE: u32> Num for I128<SCALE> {
149 type FromStrRadixErr = ParseDecimalError;
150
151 /// Parses a decimal string in the given radix.
152 ///
153 /// Only `radix == 10` is supported; `I128<SCALE>` is a base-10 type
154 /// by construction. Any other radix returns `ParseDecimalError::InvalidChar`
155 /// without attempting to parse the string.
156 ///
157 /// For `radix == 10` the call delegates to the [`core::str::FromStr`]
158 /// implementation on `I128<SCALE>`.
159 ///
160 /// # Precision
161 ///
162 /// Strict: all arithmetic is integer-only; result is bit-exact.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// use decimal_scaled::I128s12;
168 /// use num_traits::Num;
169 ///
170 /// let v = I128s12::from_str_radix("1", 10).expect("parse 1");
171 /// assert_eq!(v, I128s12::ONE);
172 ///
173 /// assert!(I128s12::from_str_radix("1", 16).is_err());
174 /// ```
175 fn from_str_radix(s: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
176 if radix != 10 {
177 return Err(ParseDecimalError::InvalidChar);
178 }
179 s.parse::<Self>()
180 }
181}
182
183// ---------------------------------------------------------------------------
184// Bounded
185// ---------------------------------------------------------------------------
186
187impl<const SCALE: u32> Bounded for I128<SCALE> {
188 /// Returns the smallest representable value, equal to [`I128::MIN`].
189 ///
190 /// The raw storage is `i128::MIN`.
191 ///
192 /// # Precision
193 ///
194 /// Strict: all arithmetic is integer-only; result is bit-exact.
195 ///
196 /// # Examples
197 ///
198 /// ```
199 /// use decimal_scaled::I128s12;
200 /// use num_traits::Bounded;
201 ///
202 /// assert_eq!(I128s12::min_value(), I128s12::MIN);
203 /// ```
204 #[inline]
205 fn min_value() -> Self {
206 Self::MIN
207 }
208
209 /// Returns the largest representable value, equal to [`I128::MAX`].
210 ///
211 /// The raw storage is `i128::MAX`.
212 ///
213 /// # Precision
214 ///
215 /// Strict: all arithmetic is integer-only; result is bit-exact.
216 ///
217 /// # Examples
218 ///
219 /// ```
220 /// use decimal_scaled::I128s12;
221 /// use num_traits::Bounded;
222 ///
223 /// assert_eq!(I128s12::max_value(), I128s12::MAX);
224 /// ```
225 #[inline]
226 fn max_value() -> Self {
227 Self::MAX
228 }
229}
230
231// ---------------------------------------------------------------------------
232// Signed
233// ---------------------------------------------------------------------------
234
235impl<const SCALE: u32> Signed for I128<SCALE> {
236 /// Returns the absolute value of `self`.
237 ///
238 /// Delegates to the inherent [`I128::abs`] method.
239 ///
240 /// # Panics
241 ///
242 /// Panics in debug mode when called on `I128::MIN` because the
243 /// positive counterpart of `i128::MIN` is not representable. In
244 /// release mode the result wraps.
245 ///
246 /// # Precision
247 ///
248 /// Strict: all arithmetic is integer-only; result is bit-exact.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use decimal_scaled::I128s12;
254 /// use num_traits::Signed;
255 ///
256 /// let pos = I128s12::from_bits(1_500_000_000_000);
257 /// let neg = I128s12::from_bits(-1_500_000_000_000);
258 /// assert_eq!(neg.abs(), pos);
259 /// ```
260 #[inline]
261 fn abs(&self) -> Self {
262 I128::abs(*self)
263 }
264
265 /// Returns the sign of `self` as a scaled `I128`: `-ONE`, `ZERO`, or `+ONE`.
266 ///
267 /// Delegates to the inherent [`I128::signum`] method.
268 ///
269 /// # Precision
270 ///
271 /// Strict: all arithmetic is integer-only; result is bit-exact.
272 ///
273 /// # Examples
274 ///
275 /// ```
276 /// use decimal_scaled::I128s12;
277 /// use num_traits::Signed;
278 ///
279 /// assert_eq!(I128s12::from_bits(5).signum(), I128s12::ONE);
280 /// assert_eq!(I128s12::from_bits(-5).signum(), -I128s12::ONE);
281 /// assert_eq!(I128s12::ZERO.signum(), I128s12::ZERO);
282 /// ```
283 #[inline]
284 fn signum(&self) -> Self {
285 I128::signum(*self)
286 }
287
288 /// Returns `true` if `self` is strictly greater than zero.
289 ///
290 /// # Precision
291 ///
292 /// Strict: all arithmetic is integer-only; result is bit-exact.
293 ///
294 /// # Examples
295 ///
296 /// ```
297 /// use decimal_scaled::I128s12;
298 /// use num_traits::Signed;
299 ///
300 /// assert!(I128s12::ONE.is_positive());
301 /// assert!(!I128s12::ZERO.is_positive());
302 /// assert!(!(-I128s12::ONE).is_positive());
303 /// ```
304 #[inline]
305 fn is_positive(&self) -> bool {
306 self.0 > 0
307 }
308
309 /// Returns `true` if `self` is strictly less than zero.
310 ///
311 /// # Precision
312 ///
313 /// Strict: all arithmetic is integer-only; result is bit-exact.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// use decimal_scaled::I128s12;
319 /// use num_traits::Signed;
320 ///
321 /// assert!((-I128s12::ONE).is_negative());
322 /// assert!(!I128s12::ZERO.is_negative());
323 /// assert!(!I128s12::ONE.is_negative());
324 /// ```
325 #[inline]
326 fn is_negative(&self) -> bool {
327 self.0 < 0
328 }
329
330 /// Returns `self - other` when `self > other`, otherwise `ZERO`.
331 ///
332 /// Matches the `num_traits::Signed::abs_sub` contract: the result is
333 /// never negative. This is not the same as `(self - other).abs()`.
334 ///
335 /// # Precision
336 ///
337 /// Strict: all arithmetic is integer-only; result is bit-exact.
338 ///
339 /// # Examples
340 ///
341 /// ```
342 /// use decimal_scaled::I128s12;
343 /// use num_traits::Signed;
344 ///
345 /// let two = I128s12::from_bits(2_000_000_000_000);
346 /// let five = I128s12::from_bits(5_000_000_000_000);
347 /// let three = I128s12::from_bits(3_000_000_000_000);
348 ///
349 /// assert_eq!(five.abs_sub(&two), three);
350 /// assert_eq!(two.abs_sub(&five), I128s12::ZERO);
351 /// assert_eq!(five.abs_sub(&five), I128s12::ZERO);
352 /// ```
353 #[inline]
354 fn abs_sub(&self, other: &Self) -> Self {
355 if *self <= *other {
356 Self::ZERO
357 } else {
358 *self - *other
359 }
360 }
361}
362
363// ---------------------------------------------------------------------------
364// FromPrimitive / ToPrimitive
365// ---------------------------------------------------------------------------
366
367impl<const SCALE: u32> FromPrimitive for I128<SCALE> {
368 /// Constructs a `I128<SCALE>` from an `i64` integer value.
369 ///
370 /// Scales `n` by `10^SCALE`. Returns `None` when the multiplication
371 /// overflows `i128`, which can occur at pathologically large scale
372 /// values. At `SCALE <= 18` the call always succeeds for any `i64`.
373 ///
374 /// # Precision
375 ///
376 /// Strict: all arithmetic is integer-only; result is bit-exact.
377 ///
378 /// # Examples
379 ///
380 /// ```
381 /// use decimal_scaled::I128s12;
382 /// use num_traits::FromPrimitive;
383 ///
384 /// assert_eq!(I128s12::from_i64(0), Some(I128s12::ZERO));
385 /// assert_eq!(I128s12::from_i64(1), Some(I128s12::ONE));
386 /// assert_eq!(I128s12::from_i64(-42), Some(I128s12::from_bits(-42_000_000_000_000)));
387 /// ```
388 #[inline]
389 fn from_i64(n: i64) -> Option<Self> {
390 (n as i128)
391 .checked_mul(Self::multiplier())
392 .map(Self)
393 }
394
395 /// Constructs a `I128<SCALE>` from a `u64` integer value.
396 ///
397 /// Scales `n` by `10^SCALE`. Returns `None` on overflow. At
398 /// `SCALE <= 18` the call always succeeds for any `u64`.
399 ///
400 /// # Precision
401 ///
402 /// Strict: all arithmetic is integer-only; result is bit-exact.
403 ///
404 /// # Examples
405 ///
406 /// ```
407 /// use decimal_scaled::I128s12;
408 /// use num_traits::FromPrimitive;
409 ///
410 /// assert_eq!(I128s12::from_u64(0), Some(I128s12::ZERO));
411 /// assert_eq!(I128s12::from_u64(42), Some(I128s12::from_bits(42_000_000_000_000)));
412 /// ```
413 #[inline]
414 fn from_u64(n: u64) -> Option<Self> {
415 (n as i128)
416 .checked_mul(Self::multiplier())
417 .map(Self)
418 }
419
420 /// Constructs a `I128<SCALE>` from an `i128` integer value.
421 ///
422 /// Returns `None` when scaling overflows. Delegates to the existing
423 /// [`TryFrom<i128>`] implementation.
424 ///
425 /// # Precision
426 ///
427 /// Strict: all arithmetic is integer-only; result is bit-exact.
428 ///
429 /// # Examples
430 ///
431 /// ```
432 /// use decimal_scaled::I128s12;
433 /// use num_traits::FromPrimitive;
434 ///
435 /// assert_eq!(I128s12::from_i128(7), Some(I128s12::from_bits(7_000_000_000_000)));
436 /// assert_eq!(I128s12::from_i128(i128::MAX), None);
437 /// ```
438 #[inline]
439 fn from_i128(n: i128) -> Option<Self> {
440 Self::try_from(n).ok()
441 }
442
443 /// Constructs a `I128<SCALE>` from a `u128` integer value.
444 ///
445 /// Returns `None` when `n > i128::MAX` or when scaling overflows.
446 /// Delegates to the existing [`TryFrom<u128>`] implementation.
447 ///
448 /// # Precision
449 ///
450 /// Strict: all arithmetic is integer-only; result is bit-exact.
451 ///
452 /// # Examples
453 ///
454 /// ```
455 /// use decimal_scaled::I128s12;
456 /// use num_traits::FromPrimitive;
457 ///
458 /// assert_eq!(I128s12::from_u128(99), Some(I128s12::from_bits(99_000_000_000_000)));
459 /// assert_eq!(I128s12::from_u128(u128::MAX), None);
460 /// ```
461 #[inline]
462 fn from_u128(n: u128) -> Option<Self> {
463 Self::try_from(n).ok()
464 }
465
466 /// Constructs a `I128<SCALE>` from an `f32` value.
467 ///
468 /// Returns `None` for `NaN`, infinities, or finite values whose
469 /// scaled representation overflows `i128`. Delegates to the existing
470 /// [`TryFrom<f32>`] implementation.
471 ///
472 /// # Precision
473 ///
474 /// Lossy: involves f32 or f64 at some point; result may lose precision.
475 ///
476 /// # Examples
477 ///
478 /// ```
479 /// use decimal_scaled::I128s12;
480 /// use num_traits::FromPrimitive;
481 ///
482 /// assert_eq!(I128s12::from_f32(0.0), Some(I128s12::ZERO));
483 /// assert_eq!(I128s12::from_f32(1.0), Some(I128s12::ONE));
484 /// assert_eq!(I128s12::from_f32(f32::NAN), None);
485 /// assert_eq!(I128s12::from_f32(f32::INFINITY), None);
486 /// ```
487 #[inline]
488 fn from_f32(n: f32) -> Option<Self> {
489 Self::try_from(n).ok()
490 }
491
492 /// Constructs a `I128<SCALE>` from an `f64` value.
493 ///
494 /// Returns `None` for `NaN`, infinities, or finite values whose
495 /// scaled representation overflows `i128`. Delegates to the existing
496 /// [`TryFrom<f64>`] implementation.
497 ///
498 /// # Precision
499 ///
500 /// Lossy: involves f32 or f64 at some point; result may lose precision.
501 ///
502 /// # Examples
503 ///
504 /// ```
505 /// use decimal_scaled::I128s12;
506 /// use num_traits::FromPrimitive;
507 ///
508 /// assert_eq!(I128s12::from_f64(0.0), Some(I128s12::ZERO));
509 /// assert_eq!(I128s12::from_f64(1.0), Some(I128s12::ONE));
510 /// assert_eq!(I128s12::from_f64(f64::NAN), None);
511 /// assert_eq!(I128s12::from_f64(1e30), None);
512 /// ```
513 #[inline]
514 fn from_f64(n: f64) -> Option<Self> {
515 Self::try_from(n).ok()
516 }
517}
518
519impl<const SCALE: u32> ToPrimitive for I128<SCALE> {
520 /// Converts `self` to `i64` by truncating the fractional part toward zero.
521 ///
522 /// Returns `None` when the integer part of `self` falls outside
523 /// `i64`'s range. Unlike `to_int_lossy`, which saturates, this method
524 /// is contractually fallible.
525 ///
526 /// # Precision
527 ///
528 /// Strict: all arithmetic is integer-only; result is bit-exact.
529 ///
530 /// # Examples
531 ///
532 /// ```
533 /// use decimal_scaled::I128s12;
534 /// use num_traits::ToPrimitive;
535 ///
536 /// let v = I128s12::from_bits(2_500_000_000_000); // 2.5
537 /// assert_eq!(v.to_i64(), Some(2));
538 /// assert_eq!(I128s12::MAX.to_i64(), None);
539 /// ```
540 #[inline]
541 fn to_i64(&self) -> Option<i64> {
542 let raw = self.0 / Self::multiplier();
543 i64::try_from(raw).ok()
544 }
545
546 /// Converts `self` to `u64` by truncating the fractional part toward zero.
547 ///
548 /// Returns `None` for negative values and for values whose integer part
549 /// exceeds `u64::MAX`.
550 ///
551 /// # Precision
552 ///
553 /// Strict: all arithmetic is integer-only; result is bit-exact.
554 ///
555 /// # Examples
556 ///
557 /// ```
558 /// use decimal_scaled::I128s12;
559 /// use num_traits::ToPrimitive;
560 ///
561 /// let v = I128s12::from_bits(42_000_000_000_000);
562 /// assert_eq!(v.to_u64(), Some(42));
563 /// assert_eq!((-I128s12::ONE).to_u64(), None);
564 /// ```
565 #[inline]
566 fn to_u64(&self) -> Option<u64> {
567 if self.0 < 0 {
568 return None;
569 }
570 let raw = self.0 / Self::multiplier();
571 u64::try_from(raw).ok()
572 }
573
574 /// Converts `self` to `i128` by truncating the fractional part toward zero.
575 ///
576 /// Always returns `Some` because `self.0 / 10^SCALE` fits in `i128`
577 /// by construction.
578 ///
579 /// # Precision
580 ///
581 /// Strict: all arithmetic is integer-only; result is bit-exact.
582 ///
583 /// # Examples
584 ///
585 /// ```
586 /// use decimal_scaled::I128s12;
587 /// use num_traits::ToPrimitive;
588 ///
589 /// let v = I128s12::from_bits(42_000_000_000_000);
590 /// assert_eq!(v.to_i128(), Some(42));
591 /// assert!(I128s12::MAX.to_i128().is_some());
592 /// ```
593 #[inline]
594 fn to_i128(&self) -> Option<i128> {
595 Some(self.0 / Self::multiplier())
596 }
597
598 /// Converts `self` to `u128` by truncating the fractional part toward zero.
599 ///
600 /// Returns `None` for negative values.
601 ///
602 /// # Precision
603 ///
604 /// Strict: all arithmetic is integer-only; result is bit-exact.
605 ///
606 /// # Examples
607 ///
608 /// ```
609 /// use decimal_scaled::I128s12;
610 /// use num_traits::ToPrimitive;
611 ///
612 /// assert_eq!(I128s12::ZERO.to_u128(), Some(0));
613 /// assert_eq!(I128s12::from_bits(-1).to_u128(), None);
614 /// ```
615 #[inline]
616 fn to_u128(&self) -> Option<u128> {
617 if self.0 < 0 {
618 return None;
619 }
620 u128::try_from(self.0 / Self::multiplier()).ok()
621 }
622
623 /// Converts `self` to `f32`.
624 ///
625 /// Uses the [`I128::to_f32_lossy`] helper. Always returns `Some`; the
626 /// result may be `+/-inf` for very large magnitudes.
627 ///
628 /// # Precision
629 ///
630 /// Lossy: involves f32 or f64 at some point; result may lose precision.
631 ///
632 /// # Examples
633 ///
634 /// ```
635 /// use decimal_scaled::I128s12;
636 /// use num_traits::ToPrimitive;
637 ///
638 /// let v = I128s12::from_bits(1_500_000_000_000); // 1.5
639 /// assert_eq!(v.to_f32(), Some(1.5_f32));
640 /// ```
641 #[inline]
642 fn to_f32(&self) -> Option<f32> {
643 Some((*self).to_f32_lossy())
644 }
645
646 /// Converts `self` to `f64`.
647 ///
648 /// Uses the [`I128::to_f64_lossy`] helper. Always returns `Some`; the
649 /// result may be `+/-inf` for very large magnitudes.
650 ///
651 /// # Precision
652 ///
653 /// Lossy: involves f32 or f64 at some point; result may lose precision.
654 ///
655 /// # Examples
656 ///
657 /// ```
658 /// use decimal_scaled::I128s12;
659 /// use num_traits::ToPrimitive;
660 ///
661 /// let v = I128s12::from_bits(1_500_000_000_000); // 1.5
662 /// assert_eq!(v.to_f64(), Some(1.5_f64));
663 /// ```
664 #[inline]
665 fn to_f64(&self) -> Option<f64> {
666 Some((*self).to_f64_lossy())
667 }
668}
669
670// ---------------------------------------------------------------------------
671// NumCast
672// ---------------------------------------------------------------------------
673//
674// `NumCast` is the generic-cast bridge in the `num-traits` ecosystem:
675// any `T: ToPrimitive` can be cast to a `NumCast` type, with the
676// implementation choosing which `to_X` / `from_X` pair to dispatch
677// through.
678//
679// Dispatch strategy: prefer the `f64` path for inputs that carry
680// fractional information (e.g. `1.5_f64`), and fall back to the
681// lossless `i128` path for integer inputs. The decision uses a
682// round-trip equality check: if `(int as f64) == n.to_f64()` the
683// input is integer-shaped and the integer path is taken, preserving
684// precision even for `i64`/`u64` values above f64's 53-bit mantissa.
685
686impl<const SCALE: u32> NumCast for I128<SCALE> {
687 /// Casts any `T: ToPrimitive` value to `I128<SCALE>`.
688 ///
689 /// Uses the `f64` conversion path for inputs that carry fractional
690 /// information, and the `i128` path for integer-shaped inputs. The
691 /// integer path is taken when `(to_i128() as f64) == to_f64()`, which
692 /// holds for true integer types even when their magnitude exceeds f64's
693 /// exact-integer range of 2^53. Returns `None` when neither path
694 /// produces a representable value.
695 ///
696 /// # Precision
697 ///
698 /// Lossy: involves f32 or f64 at some point; result may lose precision.
699 ///
700 /// # Examples
701 ///
702 /// ```
703 /// use decimal_scaled::I128s12;
704 /// use num_traits::NumCast;
705 ///
706 /// let from_int: I128s12 = NumCast::from(42_i32).unwrap();
707 /// assert_eq!(from_int, I128s12::from_bits(42_000_000_000_000));
708 ///
709 /// let from_float: I128s12 = NumCast::from(1.5_f64).unwrap();
710 /// assert_eq!(from_float, I128s12::from_f64_lossy(1.5));
711 ///
712 /// let nan: Option<I128s12> = NumCast::from(f64::NAN);
713 /// assert!(nan.is_none());
714 /// ```
715 #[inline]
716 fn from<T: ToPrimitive>(n: T) -> Option<Self> {
717 // Read f64 early so we can distinguish integer vs. fractional inputs.
718 let f = n.to_f64();
719 // Integer fast path: if `n` round-trips through i128 and the f64
720 // value matches, take the integer path. This preserves precision for
721 // i64/u64 values above f64's 2^53 exact-integer boundary.
722 if let Some(int) = n.to_i128() {
723 let take_int_path = match f {
724 None => true,
725 Some(fv) => fv.is_finite() && ((int as f64) == fv),
726 };
727 if take_int_path {
728 return <Self as FromPrimitive>::from_i128(int);
729 }
730 }
731 // Float path — preserves fractional information for f32/f64 inputs.
732 // Returns None for NaN, infinity, or out-of-range values.
733 if let Some(fv) = f {
734 return <Self as FromPrimitive>::from_f64(fv);
735 }
736 None
737 }
738}
739
740// ---------------------------------------------------------------------------
741// Checked* family
742// ---------------------------------------------------------------------------
743//
744// CheckedAdd, CheckedSub, CheckedRem, and CheckedNeg delegate directly to
745// i128's checked intrinsics; no rescaling is required for those operations.
746//
747// CheckedMul and CheckedDiv delegate to the inherent I128::checked_mul and
748// I128::checked_div methods. The trait and inherent paths are bit-identical.
749
750impl<const SCALE: u32> CheckedAdd for I128<SCALE> {
751 /// Adds `rhs` to `self`, returning `None` on overflow.
752 ///
753 /// # Precision
754 ///
755 /// Strict: all arithmetic is integer-only; result is bit-exact.
756 ///
757 /// # Examples
758 ///
759 /// ```
760 /// use decimal_scaled::I128s12;
761 /// use num_traits::CheckedAdd;
762 ///
763 /// let two = I128s12::from_bits(2_000_000_000_000);
764 /// assert_eq!(I128s12::ONE.checked_add(I128s12::ONE), Some(two));
765 /// assert_eq!(I128s12::MAX.checked_add(I128s12::ONE), None);
766 /// ```
767 #[inline]
768 fn checked_add(&self, rhs: &Self) -> Option<Self> {
769 self.0.checked_add(rhs.0).map(Self)
770 }
771}
772
773impl<const SCALE: u32> CheckedSub for I128<SCALE> {
774 /// Subtracts `rhs` from `self`, returning `None` on underflow.
775 ///
776 /// # Precision
777 ///
778 /// Strict: all arithmetic is integer-only; result is bit-exact.
779 ///
780 /// # Examples
781 ///
782 /// ```
783 /// use decimal_scaled::I128s12;
784 /// use num_traits::CheckedSub;
785 ///
786 /// let three = I128s12::from_bits(3_000_000_000_000);
787 /// let two = I128s12::from_bits(2_000_000_000_000);
788 /// assert_eq!(three.checked_sub(two), Some(I128s12::ONE));
789 /// assert_eq!(I128s12::MIN.checked_sub(I128s12::ONE), None);
790 /// ```
791 #[inline]
792 fn checked_sub(&self, rhs: &Self) -> Option<Self> {
793 self.0.checked_sub(rhs.0).map(Self)
794 }
795}
796
797impl<const SCALE: u32> CheckedMul for I128<SCALE> {
798 /// Multiplies `self` by `v`, returning `None` when the result overflows `i128`.
799 ///
800 /// Delegates to the inherent [`I128::checked_mul`] method, which uses a
801 /// 256-bit intermediate product so that only the final result needs to
802 /// fit in `i128`. Returns `None` only when the quotient after rescaling
803 /// overflows. The trait and inherent paths are bit-identical.
804 ///
805 /// # Precision
806 ///
807 /// Strict: all arithmetic is integer-only; result is bit-exact.
808 ///
809 /// # Examples
810 ///
811 /// ```
812 /// use decimal_scaled::I128s12;
813 /// use num_traits::CheckedMul;
814 ///
815 /// let half = I128s12::from_bits(500_000_000_000);
816 /// let quarter = I128s12::from_bits(250_000_000_000);
817 /// assert_eq!(half.checked_mul(half), Some(quarter));
818 ///
819 /// let two = I128s12::from_bits(2_000_000_000_000);
820 /// assert_eq!(I128s12::MAX.checked_mul(two), None);
821 /// ```
822 #[inline]
823 fn checked_mul(&self, v: &Self) -> Option<Self> {
824 (*self).checked_mul(*v)
825 }
826}
827
828impl<const SCALE: u32> CheckedDiv for I128<SCALE> {
829 /// Divides `self` by `v`, returning `None` on division by zero or overflow.
830 ///
831 /// Delegates to the inherent [`I128::checked_div`] method, which uses a
832 /// 256-bit widening divide. Returns `None` on division by zero or when the
833 /// result overflows `i128` (the only case is `I128::MIN / -ONE`). The
834 /// trait and inherent paths are bit-identical.
835 ///
836 /// # Precision
837 ///
838 /// Strict: all arithmetic is integer-only; result is bit-exact.
839 ///
840 /// # Examples
841 ///
842 /// ```
843 /// use decimal_scaled::I128s12;
844 /// use num_traits::CheckedDiv;
845 ///
846 /// let half = I128s12::from_bits(500_000_000_000);
847 /// let two = I128s12::from_bits(2_000_000_000_000);
848 /// let quarter = I128s12::from_bits(250_000_000_000);
849 /// assert_eq!(half.checked_div(two), Some(quarter));
850 /// assert_eq!(I128s12::ONE.checked_div(I128s12::ZERO), None);
851 /// ```
852 #[inline]
853 fn checked_div(&self, v: &Self) -> Option<Self> {
854 (*self).checked_div(*v)
855 }
856}
857
858impl<const SCALE: u32> CheckedRem for I128<SCALE> {
859 /// Computes `self % rhs`, returning `None` when `rhs` is zero.
860 ///
861 /// Because both operands share the same `SCALE`, no rescaling is needed.
862 /// Delegates directly to `i128::checked_rem`.
863 ///
864 /// # Precision
865 ///
866 /// Strict: all arithmetic is integer-only; result is bit-exact.
867 ///
868 /// # Examples
869 ///
870 /// ```
871 /// use decimal_scaled::I128s12;
872 /// use num_traits::CheckedRem;
873 ///
874 /// let a = I128s12::from_bits(5_500_000_000_000); // 5.5
875 /// let b = I128s12::from_bits(2_000_000_000_000); // 2.0
876 /// let expected = I128s12::from_bits(1_500_000_000_000); // 1.5
877 /// assert_eq!(a.checked_rem(b), Some(expected));
878 /// assert_eq!(a.checked_rem(I128s12::ZERO), None);
879 /// ```
880 #[inline]
881 fn checked_rem(&self, rhs: &Self) -> Option<Self> {
882 self.0.checked_rem(rhs.0).map(Self)
883 }
884}
885
886impl<const SCALE: u32> CheckedNeg for I128<SCALE> {
887 /// Negates `self`, returning `None` for `I128::MIN`.
888 ///
889 /// `i128::MIN` has no positive counterpart in two's-complement, so
890 /// negating it overflows. All other values succeed.
891 ///
892 /// # Precision
893 ///
894 /// Strict: all arithmetic is integer-only; result is bit-exact.
895 ///
896 /// # Examples
897 ///
898 /// ```
899 /// use decimal_scaled::I128s12;
900 /// use num_traits::CheckedNeg;
901 ///
902 /// assert_eq!(I128s12::ONE.checked_neg(), Some(-I128s12::ONE));
903 /// assert_eq!(I128s12::ZERO.checked_neg(), Some(I128s12::ZERO));
904 /// assert_eq!(I128s12::MIN.checked_neg(), None);
905 /// ```
906 #[inline]
907 fn checked_neg(&self) -> Option<Self> {
908 self.0.checked_neg().map(Self)
909 }
910}
911
912#[cfg(test)]
913mod tests {
914 use super::*;
915 use crate::core_type::{I128, I128s12};
916
917 // ---------------------------------------------------------------------------
918 // Zero / One
919 // ---------------------------------------------------------------------------
920
921 #[test]
922 fn zero_is_zero_const() {
923 assert_eq!(<I128s12 as Zero>::zero(), I128s12::ZERO);
924 }
925
926 #[test]
927 fn zero_is_zero_predicate() {
928 assert!(<I128s12 as Zero>::is_zero(&I128s12::ZERO));
929 assert!(!<I128s12 as Zero>::is_zero(&I128s12::ONE));
930 assert!(!<I128s12 as Zero>::is_zero(&I128s12::from_bits(1)));
931 }
932
933 #[test]
934 fn one_is_one_const() {
935 assert_eq!(<I128s12 as One>::one(), I128s12::ONE);
936 }
937
938 #[test]
939 fn one_is_one_predicate() {
940 assert!(<I128s12 as One>::is_one(&I128s12::ONE));
941 assert!(!<I128s12 as One>::is_one(&I128s12::ZERO));
942 // A non-canonical raw value (1 LSB) is not "one".
943 assert!(!<I128s12 as One>::is_one(&I128s12::from_bits(1)));
944 }
945
946 // ---------------------------------------------------------------------------
947 // Bounded
948 // ---------------------------------------------------------------------------
949
950 #[test]
951 fn bounded_min_max() {
952 assert_eq!(<I128s12 as Bounded>::min_value(), I128s12::MIN);
953 assert_eq!(<I128s12 as Bounded>::max_value(), I128s12::MAX);
954 }
955
956 // ---------------------------------------------------------------------------
957 // Signed
958 // ---------------------------------------------------------------------------
959
960 #[test]
961 fn signed_abs_basic() {
962 let pos = I128s12::from_bits(1_500_000_000_000);
963 let neg = I128s12::from_bits(-1_500_000_000_000);
964 assert_eq!(<I128s12 as Signed>::abs(&pos), pos);
965 assert_eq!(<I128s12 as Signed>::abs(&neg), pos);
966 assert_eq!(<I128s12 as Signed>::abs(&I128s12::ZERO), I128s12::ZERO);
967 }
968
969 #[test]
970 fn signed_signum_basic() {
971 let pos = I128s12::from_bits(1_500_000_000_000);
972 let neg = I128s12::from_bits(-1_500_000_000_000);
973 assert_eq!(<I128s12 as Signed>::signum(&pos), I128s12::ONE);
974 assert_eq!(<I128s12 as Signed>::signum(&neg), -I128s12::ONE);
975 assert_eq!(<I128s12 as Signed>::signum(&I128s12::ZERO), I128s12::ZERO);
976 }
977
978 #[test]
979 fn signed_is_positive_negative() {
980 let pos = I128s12::from_bits(1_500_000_000_000);
981 let neg = I128s12::from_bits(-1_500_000_000_000);
982 assert!(<I128s12 as Signed>::is_positive(&pos));
983 assert!(!<I128s12 as Signed>::is_positive(&neg));
984 assert!(!<I128s12 as Signed>::is_positive(&I128s12::ZERO));
985
986 assert!(!<I128s12 as Signed>::is_negative(&pos));
987 assert!(<I128s12 as Signed>::is_negative(&neg));
988 assert!(!<I128s12 as Signed>::is_negative(&I128s12::ZERO));
989 }
990
991 /// `abs_sub(a, b)` clamps to zero when `a <= b`.
992 #[test]
993 fn signed_abs_sub_clamps_to_zero() {
994 let two = I128s12::from_bits(2_000_000_000_000);
995 let five = I128s12::from_bits(5_000_000_000_000);
996
997 // 5 - 2 = 3 (positive case)
998 let three = I128s12::from_bits(3_000_000_000_000);
999 assert_eq!(<I128s12 as Signed>::abs_sub(&five, &two), three);
1000
1001 // 2 - 5 clamps to ZERO (a <= b)
1002 assert_eq!(<I128s12 as Signed>::abs_sub(&two, &five), I128s12::ZERO);
1003
1004 // 5 - 5 = ZERO (equal inputs)
1005 assert_eq!(<I128s12 as Signed>::abs_sub(&five, &five), I128s12::ZERO);
1006 }
1007
1008 // ---------------------------------------------------------------------------
1009 // FromPrimitive
1010 // ---------------------------------------------------------------------------
1011
1012 #[test]
1013 fn from_primitive_i64_in_range() {
1014 assert_eq!(
1015 <I128s12 as FromPrimitive>::from_i64(0),
1016 Some(I128s12::ZERO)
1017 );
1018 assert_eq!(
1019 <I128s12 as FromPrimitive>::from_i64(1),
1020 Some(I128s12::ONE)
1021 );
1022 assert_eq!(
1023 <I128s12 as FromPrimitive>::from_i64(42),
1024 Some(I128s12::from_bits(42_000_000_000_000))
1025 );
1026 assert_eq!(
1027 <I128s12 as FromPrimitive>::from_i64(-42),
1028 Some(I128s12::from_bits(-42_000_000_000_000))
1029 );
1030 }
1031
1032 #[test]
1033 fn from_primitive_u64_in_range() {
1034 assert_eq!(
1035 <I128s12 as FromPrimitive>::from_u64(0),
1036 Some(I128s12::ZERO)
1037 );
1038 assert_eq!(
1039 <I128s12 as FromPrimitive>::from_u64(42),
1040 Some(I128s12::from_bits(42_000_000_000_000))
1041 );
1042 // u64::MAX * 10^12 fits in i128, so this succeeds.
1043 let large = <I128s12 as FromPrimitive>::from_u64(u64::MAX);
1044 assert!(large.is_some());
1045 }
1046
1047 #[test]
1048 fn from_primitive_i128_overflow_returns_none() {
1049 // i128::MAX cannot be scaled by 10^12 — TryFrom returns Err,
1050 // FromPrimitive surfaces that as None.
1051 assert_eq!(<I128s12 as FromPrimitive>::from_i128(i128::MAX), None);
1052 assert_eq!(<I128s12 as FromPrimitive>::from_i128(i128::MIN), None);
1053
1054 // Small values succeed.
1055 assert_eq!(
1056 <I128s12 as FromPrimitive>::from_i128(7),
1057 Some(I128s12::from_bits(7_000_000_000_000))
1058 );
1059 }
1060
1061 #[test]
1062 fn from_primitive_u128_overflow_returns_none() {
1063 // u128::MAX > i128::MAX — the first try_from step fails.
1064 assert_eq!(<I128s12 as FromPrimitive>::from_u128(u128::MAX), None);
1065
1066 // Small values succeed.
1067 assert_eq!(
1068 <I128s12 as FromPrimitive>::from_u128(99),
1069 Some(I128s12::from_bits(99_000_000_000_000))
1070 );
1071 }
1072
1073 #[test]
1074 fn from_primitive_f32_basic() {
1075 assert_eq!(
1076 <I128s12 as FromPrimitive>::from_f32(0.0),
1077 Some(I128s12::ZERO)
1078 );
1079 assert_eq!(
1080 <I128s12 as FromPrimitive>::from_f32(1.0),
1081 Some(I128s12::ONE)
1082 );
1083 // Non-finite inputs return None.
1084 assert_eq!(<I128s12 as FromPrimitive>::from_f32(f32::NAN), None);
1085 assert_eq!(<I128s12 as FromPrimitive>::from_f32(f32::INFINITY), None);
1086 assert_eq!(<I128s12 as FromPrimitive>::from_f32(f32::NEG_INFINITY), None);
1087 }
1088
1089 #[test]
1090 fn from_primitive_f64_basic() {
1091 assert_eq!(
1092 <I128s12 as FromPrimitive>::from_f64(0.0),
1093 Some(I128s12::ZERO)
1094 );
1095 assert_eq!(
1096 <I128s12 as FromPrimitive>::from_f64(1.0),
1097 Some(I128s12::ONE)
1098 );
1099 // Use a value that is not close to any well-known math constant
1100 // so the approx_constant lint stays quiet.
1101 let v = <I128s12 as FromPrimitive>::from_f64(1.234567890123_f64);
1102 assert!(v.is_some());
1103
1104 // Non-finite inputs return None.
1105 assert_eq!(<I128s12 as FromPrimitive>::from_f64(f64::NAN), None);
1106 assert_eq!(<I128s12 as FromPrimitive>::from_f64(f64::INFINITY), None);
1107
1108 // Finite but out-of-range: 1e30 * 10^12 = 1e42 > i128::MAX.
1109 assert_eq!(<I128s12 as FromPrimitive>::from_f64(1e30), None);
1110 }
1111
1112 /// `FromPrimitive` provides default impls for `from_i32`, `from_u32`, etc.
1113 /// via `from_i64` / `from_u64`. Verify the delegation chain works.
1114 #[test]
1115 fn from_primitive_smaller_int_types_via_default_impl() {
1116 assert_eq!(
1117 <I128s12 as FromPrimitive>::from_i32(7),
1118 Some(I128s12::from_bits(7_000_000_000_000))
1119 );
1120 assert_eq!(
1121 <I128s12 as FromPrimitive>::from_i16(-3),
1122 Some(I128s12::from_bits(-3_000_000_000_000))
1123 );
1124 assert_eq!(
1125 <I128s12 as FromPrimitive>::from_i8(0),
1126 Some(I128s12::ZERO)
1127 );
1128 assert_eq!(
1129 <I128s12 as FromPrimitive>::from_u32(7),
1130 Some(I128s12::from_bits(7_000_000_000_000))
1131 );
1132 assert_eq!(
1133 <I128s12 as FromPrimitive>::from_u16(3),
1134 Some(I128s12::from_bits(3_000_000_000_000))
1135 );
1136 assert_eq!(
1137 <I128s12 as FromPrimitive>::from_u8(255),
1138 Some(I128s12::from_bits(255_000_000_000_000))
1139 );
1140 }
1141
1142 // ---------------------------------------------------------------------------
1143 // ToPrimitive
1144 // ---------------------------------------------------------------------------
1145
1146 #[test]
1147 fn to_primitive_i64_in_range() {
1148 let v = I128s12::from_bits(42_000_000_000_000);
1149 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&v), Some(42_i64));
1150
1151 let neg = I128s12::from_bits(-42_000_000_000_000);
1152 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&neg), Some(-42_i64));
1153
1154 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&I128s12::ZERO), Some(0_i64));
1155 }
1156
1157 #[test]
1158 fn to_primitive_i64_truncates_toward_zero() {
1159 // 2.5 truncates to 2
1160 let v = I128s12::from_bits(2_500_000_000_000);
1161 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&v), Some(2_i64));
1162
1163 // -2.5 truncates to -2 (toward zero, not toward -inf)
1164 let neg = I128s12::from_bits(-2_500_000_000_000);
1165 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&neg), Some(-2_i64));
1166 }
1167
1168 #[test]
1169 fn to_primitive_i64_out_of_range_returns_none() {
1170 // I128::MAX integer part ~= 1.7e26, which exceeds i64::MAX.
1171 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&I128s12::MAX), None);
1172 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&I128s12::MIN), None);
1173 }
1174
1175 #[test]
1176 fn to_primitive_u64_negative_returns_none() {
1177 let neg = I128s12::from_bits(-1_000_000_000_000);
1178 assert_eq!(<I128s12 as ToPrimitive>::to_u64(&neg), None);
1179 }
1180
1181 #[test]
1182 fn to_primitive_u64_in_range() {
1183 let v = I128s12::from_bits(42_000_000_000_000);
1184 assert_eq!(<I128s12 as ToPrimitive>::to_u64(&v), Some(42_u64));
1185
1186 assert_eq!(<I128s12 as ToPrimitive>::to_u64(&I128s12::ZERO), Some(0_u64));
1187 }
1188
1189 #[test]
1190 fn to_primitive_i128_always_succeeds() {
1191 // Even MAX and MIN succeed because the integer part is bounded
1192 // by i128::MAX / 10^12, which is well within i128.
1193 assert!(<I128s12 as ToPrimitive>::to_i128(&I128s12::MAX).is_some());
1194 assert!(<I128s12 as ToPrimitive>::to_i128(&I128s12::MIN).is_some());
1195 assert_eq!(
1196 <I128s12 as ToPrimitive>::to_i128(&I128s12::ZERO),
1197 Some(0_i128)
1198 );
1199 assert_eq!(
1200 <I128s12 as ToPrimitive>::to_i128(&I128s12::from_bits(42_000_000_000_000)),
1201 Some(42_i128)
1202 );
1203 }
1204
1205 #[test]
1206 fn to_primitive_u128_negative_returns_none() {
1207 assert_eq!(
1208 <I128s12 as ToPrimitive>::to_u128(&I128s12::from_bits(-1)),
1209 None
1210 );
1211 }
1212
1213 #[test]
1214 fn to_primitive_u128_in_range() {
1215 assert_eq!(
1216 <I128s12 as ToPrimitive>::to_u128(&I128s12::ZERO),
1217 Some(0_u128)
1218 );
1219 assert_eq!(
1220 <I128s12 as ToPrimitive>::to_u128(&I128s12::from_bits(99_000_000_000_000)),
1221 Some(99_u128)
1222 );
1223 }
1224
1225 #[test]
1226 fn to_primitive_f64_round_trip_within_lsb() {
1227 let lsb = 1.0 / (I128s12::multiplier() as f64);
1228 // Use a value not close to any well-known math constant.
1229 let v = I128s12::from_f64_lossy(1.234567890123_f64);
1230 let back = <I128s12 as ToPrimitive>::to_f64(&v).expect("to_f64 always returns Some");
1231 assert!(
1232 (back - 1.234567890123_f64).abs() <= lsb * 2.0,
1233 "round-trip exceeded 2 LSB: back = {back}, lsb = {lsb}"
1234 );
1235 }
1236
1237 #[test]
1238 fn to_primitive_f32_matches_to_f32_lossy() {
1239 let v = I128s12::from_bits(1_500_000_000_000);
1240 assert_eq!(
1241 <I128s12 as ToPrimitive>::to_f32(&v),
1242 Some(v.to_f32_lossy())
1243 );
1244 }
1245
1246 /// `ToPrimitive` provides default impls for `to_i32`, `to_u32`, etc.
1247 /// via `to_i64` / `to_u64`. Verify the delegation chain works.
1248 #[test]
1249 fn to_primitive_smaller_int_types_via_default_impl() {
1250 let v = I128s12::from_bits(42_000_000_000_000);
1251 assert_eq!(<I128s12 as ToPrimitive>::to_i32(&v), Some(42_i32));
1252 assert_eq!(<I128s12 as ToPrimitive>::to_u32(&v), Some(42_u32));
1253 assert_eq!(<I128s12 as ToPrimitive>::to_i16(&v), Some(42_i16));
1254 assert_eq!(<I128s12 as ToPrimitive>::to_u16(&v), Some(42_u16));
1255 assert_eq!(<I128s12 as ToPrimitive>::to_i8(&v), Some(42_i8));
1256 assert_eq!(<I128s12 as ToPrimitive>::to_u8(&v), Some(42_u8));
1257
1258 // Out-of-range narrowing returns None.
1259 let big = I128s12::from_bits(40_000_000_000_000_000); // 40_000
1260 assert_eq!(<I128s12 as ToPrimitive>::to_i8(&big), None);
1261 assert_eq!(<I128s12 as ToPrimitive>::to_u8(&big), None);
1262 }
1263
1264 // ---------------------------------------------------------------------------
1265 // CheckedAdd / CheckedSub
1266 // ---------------------------------------------------------------------------
1267
1268 #[test]
1269 fn checked_add_basic() {
1270 let one = I128s12::ONE;
1271 let two = I128s12::from_bits(2_000_000_000_000);
1272 assert_eq!(
1273 <I128s12 as CheckedAdd>::checked_add(&one, &one),
1274 Some(two)
1275 );
1276 }
1277
1278 #[test]
1279 fn checked_add_overflow_returns_none() {
1280 // MAX + ONE overflows.
1281 assert_eq!(
1282 <I128s12 as CheckedAdd>::checked_add(&I128s12::MAX, &I128s12::ONE),
1283 None
1284 );
1285 // MAX + ZERO is fine.
1286 assert_eq!(
1287 <I128s12 as CheckedAdd>::checked_add(&I128s12::MAX, &I128s12::ZERO),
1288 Some(I128s12::MAX)
1289 );
1290 }
1291
1292 #[test]
1293 fn checked_sub_basic() {
1294 let three = I128s12::from_bits(3_000_000_000_000);
1295 let two = I128s12::from_bits(2_000_000_000_000);
1296 assert_eq!(
1297 <I128s12 as CheckedSub>::checked_sub(&three, &two),
1298 Some(I128s12::ONE)
1299 );
1300 }
1301
1302 #[test]
1303 fn checked_sub_underflow_returns_none() {
1304 // MIN - ONE underflows.
1305 assert_eq!(
1306 <I128s12 as CheckedSub>::checked_sub(&I128s12::MIN, &I128s12::ONE),
1307 None
1308 );
1309 }
1310
1311 // ---------------------------------------------------------------------------
1312 // CheckedMul / CheckedDiv / CheckedRem
1313 // ---------------------------------------------------------------------------
1314
1315 #[test]
1316 fn checked_mul_basic() {
1317 let half = I128s12::from_bits(500_000_000_000); // 0.5
1318 let quarter = I128s12::from_bits(250_000_000_000); // 0.25
1319 assert_eq!(
1320 <I128s12 as CheckedMul>::checked_mul(&half, &half),
1321 Some(quarter)
1322 );
1323 }
1324
1325 #[test]
1326 fn checked_mul_overflow_returns_none() {
1327 // MAX * 2 overflows.
1328 let two = I128s12::from_bits(2_000_000_000_000);
1329 assert_eq!(
1330 <I128s12 as CheckedMul>::checked_mul(&I128s12::MAX, &two),
1331 None
1332 );
1333 }
1334
1335 #[test]
1336 fn checked_div_basic() {
1337 let half = I128s12::from_bits(500_000_000_000); // 0.5
1338 let quarter = I128s12::from_bits(250_000_000_000); // 0.25
1339 let two = I128s12::from_bits(2_000_000_000_000); // 2.0
1340 // 0.5 / 2.0 == 0.25
1341 assert_eq!(
1342 <I128s12 as CheckedDiv>::checked_div(&half, &two),
1343 Some(quarter)
1344 );
1345 }
1346
1347 #[test]
1348 fn checked_div_by_zero_returns_none() {
1349 assert_eq!(
1350 <I128s12 as CheckedDiv>::checked_div(&I128s12::ONE, &I128s12::ZERO),
1351 None
1352 );
1353 }
1354
1355 #[test]
1356 fn checked_div_overflow_returns_none() {
1357 // The only true checked_div overflow is MIN / -ONE (negating i128::MIN
1358 // overflows in two's-complement).
1359 let neg_one = -I128s12::ONE;
1360 assert_eq!(
1361 <I128s12 as CheckedDiv>::checked_div(&I128s12::MIN, &neg_one),
1362 None
1363 );
1364 // MAX / ONE returns Some(MAX) via the widening path.
1365 assert_eq!(
1366 <I128s12 as CheckedDiv>::checked_div(&I128s12::MAX, &I128s12::ONE),
1367 Some(I128s12::MAX)
1368 );
1369 }
1370
1371 #[test]
1372 fn checked_rem_basic() {
1373 let a = I128s12::from_bits(5_500_000_000_000); // 5.5
1374 let b = I128s12::from_bits(2_000_000_000_000); // 2.0
1375 let expected = I128s12::from_bits(1_500_000_000_000); // 1.5
1376 assert_eq!(
1377 <I128s12 as CheckedRem>::checked_rem(&a, &b),
1378 Some(expected)
1379 );
1380 }
1381
1382 #[test]
1383 fn checked_rem_by_zero_returns_none() {
1384 assert_eq!(
1385 <I128s12 as CheckedRem>::checked_rem(&I128s12::ONE, &I128s12::ZERO),
1386 None
1387 );
1388 }
1389
1390 // ---------------------------------------------------------------------------
1391 // CheckedNeg
1392 // ---------------------------------------------------------------------------
1393
1394 #[test]
1395 fn checked_neg_basic() {
1396 let one = I128s12::ONE;
1397 let neg_one = -I128s12::ONE;
1398 assert_eq!(
1399 <I128s12 as CheckedNeg>::checked_neg(&one),
1400 Some(neg_one)
1401 );
1402 assert_eq!(
1403 <I128s12 as CheckedNeg>::checked_neg(&I128s12::ZERO),
1404 Some(I128s12::ZERO)
1405 );
1406 }
1407
1408 #[test]
1409 fn checked_neg_min_returns_none() {
1410 // i128::MIN has no positive counterpart, so checked_neg returns None.
1411 assert_eq!(
1412 <I128s12 as CheckedNeg>::checked_neg(&I128s12::MIN),
1413 None
1414 );
1415 }
1416
1417 // ---------------------------------------------------------------------------
1418 // CheckedMul / CheckedDiv trait-vs-inherent alignment
1419 // ---------------------------------------------------------------------------
1420 //
1421 // Assert that the num-traits trait impls and the inherent methods
1422 // produce bit-identical results for 256 deterministic pairs plus
1423 // boundary cases. A failure here means the two paths diverged.
1424
1425 /// Generates a deterministic sequence of `i128` values using a
1426 /// linear congruential generator seeded from `seed`.
1427 fn lcg_i128_seq(seed: i128, n: usize) -> Vec<i128> {
1428 // LCG constants from Knuth TAOCP Vol 2 (applied in i128 with wrapping).
1429 let mut state: i128 = seed;
1430 let mut out = Vec::with_capacity(n);
1431 for _ in 0..n {
1432 state = state
1433 .wrapping_mul(6_364_136_223_846_793_005_i128)
1434 .wrapping_add(1_442_695_040_888_963_407_i128);
1435 out.push(state);
1436 }
1437 out
1438 }
1439
1440 /// For 256 deterministic pairs, `<I128 as CheckedMul>::checked_mul`
1441 /// must equal `I128::checked_mul` (the inherent method).
1442 #[test]
1443 fn checked_mul_trait_matches_inherent_256_pairs() {
1444 let seeds = lcg_i128_seq(0x1234_5678_9ABC_DEF0, 512);
1445 for pair in seeds.chunks_exact(2) {
1446 let a = I128s12::from_bits(pair[0]);
1447 let b = I128s12::from_bits(pair[1]);
1448 let trait_result = <I128s12 as CheckedMul>::checked_mul(&a, &b);
1449 let inherent_result = a.checked_mul(b);
1450 assert_eq!(
1451 trait_result, inherent_result,
1452 "CheckedMul trait != inherent for a={a:?} b={b:?}"
1453 );
1454 }
1455 }
1456
1457 /// For 256 deterministic pairs, `<I128 as CheckedDiv>::checked_div`
1458 /// must equal `I128::checked_div` (the inherent method).
1459 #[test]
1460 fn checked_div_trait_matches_inherent_256_pairs() {
1461 let seeds = lcg_i128_seq(0xDEAD_BEEF_CAFE_0001, 512);
1462 for pair in seeds.chunks_exact(2) {
1463 let a = I128s12::from_bits(pair[0]);
1464 // Avoid divide-by-zero: if the LCG lands on zero, substitute ONE.
1465 // The by-zero case is covered by a dedicated test.
1466 let b_bits = if pair[1] == 0 { I128s12::multiplier() } else { pair[1] };
1467 let b = I128s12::from_bits(b_bits);
1468 let trait_result = <I128s12 as CheckedDiv>::checked_div(&a, &b);
1469 let inherent_result = a.checked_div(b);
1470 assert_eq!(
1471 trait_result, inherent_result,
1472 "CheckedDiv trait != inherent for a={a:?} b={b:?}"
1473 );
1474 }
1475 }
1476
1477 /// Boundary cases for CheckedMul trait-vs-inherent alignment.
1478 #[test]
1479 fn checked_mul_trait_matches_inherent_boundary() {
1480 let cases: &[(I128s12, I128s12)] = &[
1481 (I128s12::MAX, I128s12::ZERO),
1482 (I128s12::MIN, I128s12::ZERO),
1483 (I128s12::MAX, I128s12::ONE),
1484 (I128s12::MIN, I128s12::ONE),
1485 (I128s12::MAX, I128s12::MAX),
1486 (I128s12::MIN, I128s12::MIN),
1487 (I128s12::from_bits(0), I128s12::from_bits(0)),
1488 (I128s12::from_bits(1), I128s12::from_bits(1)),
1489 (I128s12::from_bits(-1), I128s12::from_bits(1)),
1490 (I128s12::from_bits(1), I128s12::from_bits(-1)),
1491 (I128s12::from_bits(-1), I128s12::from_bits(-1)),
1492 ];
1493 for &(a, b) in cases {
1494 let trait_result = <I128s12 as CheckedMul>::checked_mul(&a, &b);
1495 let inherent_result = a.checked_mul(b);
1496 assert_eq!(
1497 trait_result, inherent_result,
1498 "CheckedMul trait != inherent at boundary a={a:?} b={b:?}"
1499 );
1500 }
1501 }
1502
1503 /// Boundary cases for CheckedDiv trait-vs-inherent alignment.
1504 #[test]
1505 fn checked_div_trait_matches_inherent_boundary() {
1506 let neg_one = -I128s12::ONE;
1507 let cases: &[(I128s12, I128s12)] = &[
1508 (I128s12::MAX, I128s12::ONE),
1509 (I128s12::MIN, I128s12::ONE),
1510 (I128s12::MAX, I128s12::MAX),
1511 (I128s12::MIN, I128s12::MIN),
1512 (I128s12::ZERO, I128s12::ONE),
1513 (I128s12::ONE, I128s12::MAX),
1514 // divide by zero — both must return None
1515 (I128s12::ONE, I128s12::ZERO),
1516 (I128s12::MAX, I128s12::ZERO),
1517 // true overflow case: MIN / -ONE
1518 (I128s12::MIN, neg_one),
1519 (I128s12::from_bits(1), I128s12::from_bits(1)),
1520 (I128s12::from_bits(-1), I128s12::from_bits(1)),
1521 (I128s12::from_bits(1), I128s12::from_bits(-1)),
1522 (I128s12::from_bits(-1), I128s12::from_bits(-1)),
1523 ];
1524 for &(a, b) in cases {
1525 let trait_result = <I128s12 as CheckedDiv>::checked_div(&a, &b);
1526 let inherent_result = a.checked_div(b);
1527 assert_eq!(
1528 trait_result, inherent_result,
1529 "CheckedDiv trait != inherent at boundary a={a:?} b={b:?}"
1530 );
1531 }
1532 }
1533
1534 // ---------------------------------------------------------------------------
1535 // Num::from_str_radix
1536 // ---------------------------------------------------------------------------
1537
1538 /// Non-base-10 radix is rejected without delegating to FromStr.
1539 #[test]
1540 fn from_str_radix_non_ten_returns_invalid() {
1541 let result = <I128s12 as Num>::from_str_radix("1", 16);
1542 assert!(result.is_err());
1543
1544 let result_2 = <I128s12 as Num>::from_str_radix("1", 2);
1545 assert!(result_2.is_err());
1546 }
1547
1548 /// Base-10 delegates to the FromStr implementation.
1549 #[test]
1550 fn from_str_radix_base_ten_delegates_to_from_str() {
1551 let parsed = <I128s12 as Num>::from_str_radix("1", 10).expect("parse 1");
1552 assert_eq!(parsed, I128s12::ONE);
1553 }
1554
1555 // ---------------------------------------------------------------------------
1556 // Cross-scale exercise — non-default SCALE
1557 // ---------------------------------------------------------------------------
1558
1559 /// At SCALE = 6 the trait surface works correctly.
1560 #[test]
1561 fn traits_compile_at_scale_6() {
1562 type D6 = I128<6>;
1563 assert_eq!(<D6 as Zero>::zero(), D6::ZERO);
1564 assert_eq!(<D6 as One>::one(), D6::ONE);
1565 assert_eq!(<D6 as Bounded>::min_value(), D6::MIN);
1566 assert_eq!(<D6 as Bounded>::max_value(), D6::MAX);
1567
1568 let v: D6 = <D6 as FromPrimitive>::from_i64(42).unwrap();
1569 assert_eq!(<D6 as ToPrimitive>::to_i64(&v), Some(42_i64));
1570 }
1571
1572 // ---------------------------------------------------------------------------
1573 // NumCast
1574 // ---------------------------------------------------------------------------
1575
1576 /// `NumCast::from` round-trips an in-range `i32` exactly.
1577 #[test]
1578 fn numcast_from_i32() {
1579 let v: I128s12 = <I128s12 as NumCast>::from(42_i32).expect("in-range");
1580 assert_eq!(v, <I128s12 as From<i32>>::from(42_i32));
1581 }
1582
1583 /// `NumCast::from` preserves the fractional part of an `f64` input
1584 /// because the float path runs before the integer truncation path.
1585 #[test]
1586 fn numcast_from_f64_preserves_fractional() {
1587 let v: I128s12 = <I128s12 as NumCast>::from(1.5_f64).expect("in-range");
1588 assert_eq!(v, I128s12::from_f64_lossy(1.5_f64));
1589 }
1590
1591 /// `NumCast::from` returns `None` for `f64::NAN`.
1592 #[test]
1593 fn numcast_from_f64_nan_returns_none() {
1594 assert!(<I128s12 as NumCast>::from(f64::NAN).is_none());
1595 }
1596
1597 /// `NumCast::from` returns `None` for finite out-of-range `f64`.
1598 #[test]
1599 fn numcast_from_f64_out_of_range_returns_none() {
1600 assert!(<I128s12 as NumCast>::from(1e30_f64).is_none());
1601 }
1602
1603 /// `NumCast::from` keeps integer inputs exact for `i64` values above
1604 /// f64's 53-bit mantissa range, validating the integer fast path.
1605 #[test]
1606 fn numcast_from_i64_above_f64_mantissa_is_exact() {
1607 // 2^54 = 18_014_398_509_481_984 — above f64's exact-integer range.
1608 let v: i64 = 1_i64 << 54;
1609 let d: I128s12 = <I128s12 as NumCast>::from(v).expect("in-range");
1610 assert_eq!(<I128s12 as ToPrimitive>::to_i64(&d), Some(v));
1611 }
1612}