use decimal_scaled::{
D38, D38s0, D38s12, D38s2, D38s37, D38s9, D9s0, D9s2, D9s4, D9s8, D18s0, D18s17,
D18s6, D18s9, RoundingMode,
};
use decimal_scaled::DecimalArithmetic;
#[test]
fn zero_one_max_min_storage_patterns() {
assert_eq!(D38s0::ZERO.to_bits(), 0);
assert_eq!(D38s0::ONE.to_bits(), 1);
assert_eq!(D38s12::ONE.to_bits(), 1_000_000_000_000);
assert_eq!(D38s37::ONE.to_bits(), 10_i128.pow(37));
assert_eq!(D38s12::MAX.to_bits(), i128::MAX);
assert_eq!(D38s12::MIN.to_bits(), i128::MIN);
assert_eq!(D9s0::ONE.to_bits(), 1);
assert_eq!(D9s8::ONE.to_bits(), 10_i32.pow(8));
assert_eq!(D9s0::MAX.to_bits(), i32::MAX);
assert_eq!(D9s0::MIN.to_bits(), i32::MIN);
assert_eq!(D18s17::ONE.to_bits(), 10_i64.pow(17));
assert_eq!(D18s0::MAX.to_bits(), i64::MAX);
}
#[test]
fn multiplier_at_scale_extremes() {
assert_eq!(D38s0::multiplier(), 1);
assert_eq!(D38s37::multiplier(), 10_i128.pow(37));
assert_eq!(D9s8::multiplier(), 10_i32.pow(8));
assert_eq!(D18s17::multiplier(), 10_i64.pow(17));
}
#[test]
fn max_scale_per_width() {
assert_eq!(<D9s0 as DecimalArithmetic>::MAX_SCALE, 8);
assert_eq!(<D18s0 as DecimalArithmetic>::MAX_SCALE, 17);
assert_eq!(<D38s0 as DecimalArithmetic>::MAX_SCALE, 37);
}
#[test]
fn add_at_max_boundary() {
let one_lsb = D38s12::from_bits(1);
assert_eq!(D38s12::MAX.checked_add(one_lsb), None);
assert_eq!(D38s12::MAX.saturating_add(one_lsb), D38s12::MAX);
assert_eq!(D38s12::MAX.wrapping_add(one_lsb), D38s12::MIN);
assert_eq!(D38s12::MAX.overflowing_add(one_lsb), (D38s12::MIN, true));
let near_max = D38s12::from_bits(i128::MAX - 1);
assert_eq!(near_max.checked_add(one_lsb), Some(D38s12::MAX));
}
#[test]
fn sub_at_min_boundary() {
let one_lsb = D38s12::from_bits(1);
assert_eq!(D38s12::MIN.checked_sub(one_lsb), None);
assert_eq!(D38s12::MIN.saturating_sub(one_lsb), D38s12::MIN);
assert_eq!(D38s12::MIN.wrapping_sub(one_lsb), D38s12::MAX);
}
#[test]
fn neg_of_min_overflows() {
assert_eq!(D38s12::MIN.checked_neg(), None);
assert_eq!(D38s12::MIN.saturating_neg(), D38s12::MAX);
assert_eq!(D38s12::MIN.wrapping_neg(), D38s12::MIN);
assert_eq!(D38s12::MIN.overflowing_neg(), (D38s12::MIN, true));
assert_eq!(D38s12::MAX.checked_neg(), Some(D38s12::from_bits(i128::MIN + 1)));
}
#[test]
fn checked_div_by_zero_is_none() {
assert_eq!(D38s12::ONE.checked_div(D38s12::ZERO), None);
assert_eq!(D9s0::ONE.checked_div(D9s0::ZERO), None);
assert_eq!(D18s0::ONE.checked_div(D18s0::ZERO), None);
}
#[test]
fn checked_mul_overflow_at_max() {
let two = D38s12::from_int(2);
assert_eq!(D38s12::MAX.checked_mul(two), None);
assert_eq!(D38s12::MAX.saturating_mul(two), D38s12::MAX);
assert_eq!(D38s12::MIN.saturating_mul(two), D38s12::MIN);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn add_overflow_panics_in_debug() {
let _ = D38s12::MAX + D38s12::from_bits(1);
}
#[test]
#[should_panic]
fn div_by_zero_operator_panics() {
let _ = D38s12::ONE / D38s12::ZERO;
}
#[test]
fn rescale_up_is_lossless_to_max_scale() {
let v = D38s0::from_bits(1);
let up: D38<37> = v.rescale::<37>();
assert_eq!(up.to_bits(), 10_i128.pow(37));
}
#[test]
fn rescale_same_scale_is_identity() {
let v = D38s12::from_bits(123_456_789_012);
let same: D38s12 = v.rescale::<12>();
assert_eq!(same.to_bits(), 123_456_789_012);
}
#[test]
fn rescale_down_every_mode_at_exact_half() {
let half_tie = D38::<1>::from_bits(15); let modes_and_bits = [
(RoundingMode::HalfToEven, 2), (RoundingMode::HalfAwayFromZero, 2), (RoundingMode::HalfTowardZero, 1), (RoundingMode::Trunc, 1), (RoundingMode::Floor, 1), (RoundingMode::Ceiling, 2), ];
for (mode, expected) in modes_and_bits {
let r: D38<0> = half_tie.rescale_with::<0>(mode);
assert_eq!(r.to_bits(), expected, "mode {mode:?}");
}
let other_tie = D38::<1>::from_bits(25);
assert_eq!(other_tie.rescale_with::<0>(RoundingMode::HalfToEven).to_bits(), 2);
}
#[test]
fn rescale_down_negative_tie_is_sign_symmetric() {
let neg_tie = D38::<1>::from_bits(-15); assert_eq!(neg_tie.rescale_with::<0>(RoundingMode::HalfAwayFromZero).to_bits(), -2);
assert_eq!(neg_tie.rescale_with::<0>(RoundingMode::HalfTowardZero).to_bits(), -1);
assert_eq!(neg_tie.rescale_with::<0>(RoundingMode::Floor).to_bits(), -2);
assert_eq!(neg_tie.rescale_with::<0>(RoundingMode::Ceiling).to_bits(), -1);
}
#[test]
#[should_panic]
fn rescale_up_overflow_panics() {
let _ = D38s12::MAX.rescale::<38>();
}
#[test]
fn floor_ceil_round_trunc_fract_signs() {
let neg = D38::<1>::from_bits(-25);
assert_eq!(neg.floor().to_bits(), -30); assert_eq!(neg.ceil().to_bits(), -20); assert_eq!(neg.round().to_bits(), -30); assert_eq!(neg.trunc().to_bits(), -20); assert_eq!(neg.fract().to_bits(), -5);
let exact = D38::<1>::from_bits(40); assert_eq!(exact.floor(), exact);
assert_eq!(exact.ceil(), exact);
assert_eq!(exact.round(), exact);
assert_eq!(exact.trunc(), exact);
assert_eq!(exact.fract(), D38::<1>::ZERO);
}
#[test]
fn signum_abs_at_extremes() {
assert_eq!(D38s12::ZERO.signum(), D38s12::ZERO);
assert_eq!(D38s12::ONE.signum(), D38s12::ONE);
assert_eq!((-D38s12::ONE).signum(), -D38s12::ONE);
assert_eq!(D38s12::MAX.signum(), D38s12::ONE);
assert_eq!(D38s12::MIN.signum(), -D38s12::ONE);
assert_eq!(D38s12::MAX.abs(), D38s12::MAX);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn abs_of_min_panics_in_debug() {
let _ = D38s12::MIN.abs();
}
#[test]
fn is_positive_is_negative_is_zero_partition() {
for v in [
D38s12::ZERO,
D38s12::ONE,
-D38s12::ONE,
D38s12::MAX,
D38s12::MIN,
D38s12::from_bits(1),
D38s12::from_bits(-1),
] {
let p = v.is_positive() as u8;
let n = v.is_negative() as u8;
let z = v.is_zero() as u8;
assert_eq!(p + n + z, 1, "value {v:?} must be in exactly one sign class");
}
}
#[test]
fn cross_width_narrowing_at_boundary() {
let at_edge = D38s0::from_bits(i64::MAX as i128);
let narrowed: Result<D18s0, _> = at_edge.try_into();
assert!(narrowed.is_ok());
let past_edge = D38s0::from_bits(i64::MAX as i128 + 1);
let fail: Result<D18s0, _> = past_edge.try_into();
assert!(fail.is_err());
let small = D9s0::from_bits(i32::MIN);
let wide: D38s0 = small.into();
assert_eq!(wide.to_bits(), i32::MIN as i128);
}
#[test]
fn try_from_i128_overflow_boundary() {
let max_int = i128::MAX / 10_i128.pow(12);
assert!(D38s12::try_from(max_int).is_ok());
assert!(D38s12::try_from(max_int + 1).is_err());
}
#[test]
fn to_int_lossy_saturates_and_rounds() {
let v = D38::<1>::from_bits(25); assert_eq!(v.to_int_with(RoundingMode::HalfToEven), 2);
assert_eq!(v.to_int_with(RoundingMode::HalfAwayFromZero), 3);
let huge = D38s0::MAX;
assert_eq!(huge.to_int(), i64::MAX);
let tiny = D38s0::MIN;
assert_eq!(tiny.to_int(), i64::MIN);
}
#[test]
fn additive_and_multiplicative_identities() {
macro_rules! check {
($t:ty) => {{
let one = <$t>::ONE;
let zero = <$t>::ZERO;
let v = <$t>::from_bits(7);
assert_eq!(v + zero, v);
assert_eq!(v - zero, v);
assert_eq!(v * one, v);
assert_eq!(v / one, v);
assert_eq!(zero - v, -v);
assert_eq!(v - v, zero);
}};
}
check!(D9s8);
check!(D18s17);
check!(D38s12);
check!(D38s37);
}
#[test]
fn from_bits_to_bits_round_trips_at_extremes() {
for raw in [0_i128, 1, -1, i128::MAX, i128::MIN, i128::MAX - 1, i128::MIN + 1] {
assert_eq!(D38s12::from_bits(raw).to_bits(), raw);
}
for raw in [0_i32, 1, -1, i32::MAX, i32::MIN] {
assert_eq!(D9s0::from_bits(raw).to_bits(), raw);
}
}
#[test]
fn cross_width_widen_then_narrow_round_trips() {
let v32 = D9s2::from_bits(-12_345);
let wide: D38s2 = v32.into();
let back: D9s2 = wide.try_into().unwrap();
assert_eq!(back, v32);
let v64 = D18s9::from_bits(i64::MIN + 1);
let wide: D38s9 = v64.into();
let back: D18s9 = wide.try_into().unwrap();
assert_eq!(back, v64);
}
#[test]
fn overflow_variants_consistency_across_widths() {
macro_rules! check {
($t:ty) => {{
let max = <$t>::MAX;
let one = <$t>::from_bits(1);
let (wrapped, did) = max.overflowing_add(one);
assert_eq!(wrapped, max.wrapping_add(one));
assert_eq!(did, max.checked_add(one).is_none());
let a = <$t>::from_bits(10);
let b = <$t>::from_bits(20);
assert_eq!(a.checked_add(b), Some(a.wrapping_add(b)));
assert_eq!(a.overflowing_add(b), (a.wrapping_add(b), false));
}};
}
check!(D9s4);
check!(D18s9);
check!(D38s12);
}
#[test]
fn rounding_methods_on_every_width() {
macro_rules! check {
($t:ty, $half:expr) => {{
let v = <$t>::from_bits($half);
let m = <$t>::multiplier();
assert_eq!(v.floor().to_bits(), 2 * m);
assert_eq!(v.ceil().to_bits(), 3 * m);
assert_eq!(v.round().to_bits(), 3 * m);
assert_eq!(v.trunc().to_bits(), 2 * m);
assert_eq!(v.fract(), v - v.trunc());
let n = -v;
assert_eq!(n.fract(), n - n.trunc());
}};
}
check!(D9s2, 250);
check!(D18s6, 2_500_000);
check!(D38s12, 2_500_000_000_000);
}
#[test]
fn div_euclid_rem_euclid_invariant() {
for &(a, b) in &[
(7_i128, 3_i128),
(-7, 3),
(7, -3),
(-7, -3),
(123_456_789, 1_000),
] {
let da = D38s0::from_bits(a);
let db = D38s0::from_bits(b);
let q = da.div_euclid(db);
let r = da.rem_euclid(db);
assert!(!r.is_negative(), "rem_euclid({a},{b}) negative");
let q_int = q.to_bits() / D38s0::multiplier();
assert_eq!(b * q_int + r.to_bits(), a, "euclid identity ({a},{b})");
}
}
#[test]
fn midpoint_is_overflow_free_at_extremes() {
assert_eq!(D38s0::MAX.midpoint(D38s0::MAX), D38s0::MAX);
assert_eq!(D38s0::MIN.midpoint(D38s0::MIN), D38s0::MIN);
let mid = D38s0::MAX.midpoint(D38s0::MIN);
assert_eq!(mid.to_bits(), -1);
}
#[test]
fn from_str_round_trips_display_at_scale_extremes() {
use core::str::FromStr;
let s = "1.2345678901234567890123456789012345678"; let v = D38s37::from_str(s).unwrap();
assert_eq!(format!("{v}"), s);
let v0 = D38s0::from_str("-42").unwrap();
assert_eq!(format!("{v0}"), "-42");
assert_eq!(v0.to_bits(), -42);
}
#[test]
fn bitwise_storage_semantics() {
let a = D38s12::from_bits(0b1100);
let b = D38s12::from_bits(0b1010);
assert_eq!((a & b).to_bits(), 0b1000);
assert_eq!((a | b).to_bits(), 0b1110);
assert_eq!((a ^ b).to_bits(), 0b0110);
assert_eq!((!D38s12::ZERO).to_bits(), -1);
assert_eq!((D38s12::from_bits(-8) >> 1u32).to_bits(), -4);
assert_eq!(
D38s12::from_bits(-8).unsigned_shr(1).to_bits(),
i128::MAX - 3
);
}
#[test]
fn float_shape_predicates_are_total() {
for v in [D38s12::ZERO, D38s12::ONE, D38s12::MAX, D38s12::MIN, -D38s12::ONE] {
assert!(!v.is_nan());
assert!(!v.is_infinite());
assert!(v.is_finite());
assert_eq!(v.is_normal(), !v.is_zero());
}
}
#[cfg(feature = "wide")]
#[test]
fn wide_tier_boundaries() {
use decimal_scaled::{D76, D76s0, D153s0, D307s0};
let one = D76s0::ONE;
assert_eq!(D76::<0>::from_bits(one.to_bits()), one);
let v = D76s0::from_int(7i128);
assert_eq!(v + D76s0::ZERO, v);
assert_eq!(v * D76s0::ONE, v);
assert_eq!(v - v, D76s0::ZERO);
assert_eq!(D76s0::MAX.checked_add(D76s0::ONE), None);
assert_eq!(D153s0::MAX.checked_add(D153s0::ONE), None);
assert_eq!(D307s0::MAX.checked_add(D307s0::ONE), None);
let mid: D76s0 = D38s0::from_bits(123_456).into();
let back: D38s0 = mid.try_into().unwrap();
assert_eq!(back.to_bits(), 123_456);
}