use crate::AmountErrorKind;
use crate::Rounding;
use crate::ipow10_i128;
use crate::limbs::{
cmp_twice_rem_u64, dec_div, dec_mul, div_knuth, div_rem_u128_pow10, div_words_by_word,
mul_add_word, parse_decimal_mag_rounded, round_up_by_cmp, upow10,
};
use core::cmp::Ordering;
use core::ops::*;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Decimal128<const DIGITS: u8>(pub i128);
pub type Amount128 = Decimal128<4>;
pub type Rate128 = Decimal128<8>;
#[inline]
const fn i128_sign_mag(v: i128) -> (bool, [u64; 2]) {
let m = v.unsigned_abs();
(v < 0, [m as u64, (m >> 64) as u64])
}
#[inline]
const fn i128_from_sign_mag(neg: bool, mag: [u64; 2]) -> i128 {
let v = ((mag[0] as u128) | ((mag[1] as u128) << 64)) as i128;
if neg { v.wrapping_neg() } else { v }
}
fn i128_rem(a: i128, b: i128) -> i128 {
let neg = a < 0;
let am = a.unsigned_abs();
let bm = b.unsigned_abs();
let r = if bm >> 64 == 0 {
let mut q = [am as u64, (am >> 64) as u64];
div_words_by_word(&mut q, bm as u64) as u128
} else if am < bm {
am } else {
let mut q = [0u64];
let mut r = [0u64; 2];
div_knuth(
&mut q,
&mut r,
&[am as u64, (am >> 64) as u64],
&[bm as u64, (bm >> 64) as u64],
);
((r[1] as u128) << 64) | r[0] as u128
};
if neg { -(r as i128) } else { r as i128 }
}
impl<const DIGITS: u8> Decimal128<DIGITS> {
pub const SCALE: i32 = DIGITS as i32;
pub(crate) const SCALE_U64: u64 = upow10(DIGITS as u32);
pub const SCALE_INT: i128 = Self::SCALE_U64 as i128;
pub const SCALE_INT_HALF: i128 = Self::SCALE_INT / 2;
pub const MAX: Self = Decimal128::<DIGITS>(i128::MAX);
pub const MIN: Self = Decimal128::<DIGITS>(-i128::MAX);
pub const ONE: Self = Decimal128::<DIGITS>(Self::SCALE_INT);
pub const MINUS_ONE: Self = Decimal128::<DIGITS>(-Self::SCALE_INT);
pub const ZERO: Self = Decimal128::<DIGITS>(0);
pub const INT_MIN: i128 = (i128::MIN + 1) / Self::SCALE_INT;
pub const INT_MAX: i128 = i128::MAX / Self::SCALE_INT;
pub const SCALE_F64: f64 = Self::SCALE_INT as f64;
pub const F64_MIN: f64 = (i128::MIN + 1) as f64 / Self::SCALE_F64;
pub const F64_MAX: f64 = i128::MAX as f64 / Self::SCALE_F64;
#[inline]
pub fn new() -> Self {
Decimal128::<DIGITS>(0)
}
#[inline]
pub fn from_f32(val: f32) -> Result<Self, AmountErrorKind> {
Self::from_f64(val as f64)
}
#[inline]
pub fn from_f64(val: f64) -> Result<Self, AmountErrorKind> {
if (Self::F64_MIN..=Self::F64_MAX).contains(&val) {
Ok(Decimal128::<DIGITS>((val * Self::SCALE_F64) as i128))
} else {
Err(AmountErrorKind::Overflow)
}
}
#[inline]
pub const fn from_i128(val: i128) -> Result<Self, AmountErrorKind> {
if (val <= Self::INT_MAX) && (val >= Self::INT_MIN) {
Ok(Decimal128::<DIGITS>(val * Self::SCALE_INT))
} else {
Err(AmountErrorKind::Overflow)
}
}
#[inline]
pub const fn from_i64(val: i64) -> Result<Self, AmountErrorKind> {
Self::from_i128(val as i128)
}
#[inline]
pub fn to_f64(self) -> f64 {
self.0 as f64 / Self::SCALE_F64
}
#[inline]
pub const fn to_i128(self) -> i128 {
self.0 / Self::SCALE_INT
}
#[inline]
pub const fn mantissa(self) -> i128 {
self.0
}
#[inline]
pub const fn to_decimal_parts(self) -> (i128, i32) {
(self.0, -Self::SCALE)
}
pub const fn from_decimal_parts(
mantissa: i128,
exponent: i32,
) -> Result<Self, AmountErrorKind> {
if mantissa == 0 {
return Ok(Decimal128::<DIGITS>(0));
}
let shift = exponent as i64 + Self::SCALE as i64;
let scaled = if shift >= 0 {
let factor = match ipow10_i128(shift) {
Some(f) => f,
None => return Err(AmountErrorKind::Overflow),
};
match mantissa.checked_mul(factor) {
Some(v) => v,
None => return Err(AmountErrorKind::Overflow),
}
} else {
let divisor = match ipow10_i128(-shift) {
Some(d) => d,
None => return Err(AmountErrorKind::Inexact),
};
if mantissa % divisor != 0 {
return Err(AmountErrorKind::Inexact);
}
mantissa / divisor
};
Ok(Decimal128::<DIGITS>(scaled))
}
pub const fn from_decimal_parts_rounded(
mantissa: i128,
exponent: i32,
mode: Rounding,
) -> Result<Self, AmountErrorKind> {
if mantissa == 0 {
return Ok(Decimal128::<DIGITS>(0));
}
let shift = exponent as i64 + Self::SCALE as i64;
let scaled = if shift >= 0 {
let factor = match ipow10_i128(shift) {
Some(f) => f,
None => return Err(AmountErrorKind::Overflow),
};
match mantissa.checked_mul(factor) {
Some(v) => v,
None => return Err(AmountErrorKind::Overflow),
}
} else {
let is_neg = mantissa < 0;
let mag = mantissa.unsigned_abs();
let divisor = match ipow10_i128(-shift) {
Some(d) => d as u128,
None => {
let one = match mode {
Rounding::Up => {
if is_neg {
-1
} else {
1
}
}
_ => 0,
};
return Ok(Decimal128::<DIGITS>(one));
}
};
let quo = mag / divisor;
let rem = mag % divisor;
let half = divisor / 2;
let round_up = match mode {
Rounding::HalfEven => rem > half || (rem == half && !quo.is_multiple_of(2)),
Rounding::HalfUp => rem >= half,
Rounding::HalfDown => rem > half,
Rounding::Down => false,
Rounding::Up => rem != 0,
};
let q = if round_up { quo + 1 } else { quo };
if q > i128::MAX as u128 {
return Err(AmountErrorKind::Overflow);
}
if is_neg { -(q as i128) } else { q as i128 }
};
Ok(Decimal128::<DIGITS>(scaled))
}
pub const fn from_str_rounded(src: &str, mode: Rounding) -> Result<Self, AmountErrorKind> {
let (neg, mag) = match parse_decimal_mag_rounded::<2>(src, DIGITS, mode) {
Ok(v) => v,
Err(e) => return Err(e),
};
if mag[1] >> 63 != 0 {
return Err(AmountErrorKind::Overflow);
}
Ok(Decimal128::<DIGITS>(i128_from_sign_mag(neg, mag)))
}
pub const fn from_str_const(src: &str) -> Self {
match Self::from_str_rounded(src, Rounding::HalfUp) {
Ok(v) => v,
Err(_) => panic!("invalid decimal literal"),
}
}
#[inline]
pub(crate) const fn sign_mag4(self) -> (bool, [u64; 4]) {
let (neg, m) = i128_sign_mag(self.0);
(neg, [m[0], m[1], 0, 0])
}
#[inline]
pub const fn abs(self) -> Self {
Decimal128::<DIGITS>(self.0.abs())
}
#[inline]
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
match self.0.checked_add(rhs.0) {
Some(v) => Some(Decimal128::<DIGITS>(v)),
None => None,
}
}
#[inline]
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
match self.0.checked_sub(rhs.0) {
Some(v) => Some(Decimal128::<DIGITS>(v)),
None => None,
}
}
#[inline]
pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
let (an, am) = i128_sign_mag(self.0);
let (bn, bm) = i128_sign_mag(rhs.0);
match dec_mul::<DIGITS, 2, 4>(an, &am, bn, &bm, Rounding::HalfUp) {
Some((neg, mag)) => Some(Decimal128::<DIGITS>(i128_from_sign_mag(neg, mag))),
None => None,
}
}
#[inline]
pub fn checked_div(self, rhs: Self) -> Option<Self> {
let (an, am) = i128_sign_mag(self.0);
let (bn, bm) = i128_sign_mag(rhs.0);
dec_div::<DIGITS, 2, 3>(an, &am, bn, &bm, Rounding::HalfUp)
.map(|(neg, mag)| Decimal128::<DIGITS>(i128_from_sign_mag(neg, mag)))
}
#[inline]
pub fn recip(self) -> Self {
Self::ONE / self
}
#[inline]
const fn frac_rem(self) -> i128 {
let (_, r) = div_rem_u128_pow10::<DIGITS>(self.0.unsigned_abs());
if self.0 < 0 { -(r as i128) } else { r as i128 }
}
#[inline]
pub const fn trunc(self) -> Self {
Decimal128::<DIGITS>(self.0 - self.frac_rem())
}
pub const fn floor(self) -> Self {
let frac = self.frac_rem();
if self.0 < 0 && frac != 0 {
Decimal128::<DIGITS>(self.0 - frac - Self::SCALE_INT)
} else {
Decimal128::<DIGITS>(self.0 - frac)
}
}
pub const fn ceil(self) -> Self {
let mut frac = self.frac_rem();
if frac != 0 {
if self.0 < 0 {
frac += Self::SCALE_INT
} else {
frac -= Self::SCALE_INT
}
Decimal128::<DIGITS>(self.0 - frac)
} else {
self
}
}
pub const fn round(self) -> Self {
let mut frac = self.frac_rem();
if frac >= Self::SCALE_INT_HALF {
frac -= Self::SCALE_INT
} else if frac <= -Self::SCALE_INT_HALF {
frac += Self::SCALE_INT
}
Decimal128::<DIGITS>(self.0 - frac)
}
pub const fn round_to(self, mode: Rounding) -> Self {
let neg = self.0 < 0;
let (q, rem) = div_rem_u128_pow10::<DIGITS>(self.0.unsigned_abs());
if rem == 0 {
return self;
}
let up = round_up_by_cmp(
cmp_twice_rem_u64(rem, const { upow10(DIGITS as u32) }),
false,
q & 1 != 0,
neg,
mode,
);
let scaled = (q + up as u128) * const { upow10(DIGITS as u32) } as u128;
if scaled > i128::MAX as u128 {
panic!("attempt to round with overflow");
}
let v = scaled as i128;
Decimal128::<DIGITS>(if neg { v.wrapping_neg() } else { v })
}
pub const fn mul_rounded<const RHS_DIGITS: u8>(
self,
rhs: Decimal128<RHS_DIGITS>,
mode: Rounding,
) -> Self {
match self.checked_mul_rounded(rhs, mode) {
Some(v) => v,
None => panic!("attempt to multiply with overflow"),
}
}
#[inline]
pub const fn checked_mul_rounded<const RHS_DIGITS: u8>(
self,
rhs: Decimal128<RHS_DIGITS>,
mode: Rounding,
) -> Option<Self> {
let (an, am) = i128_sign_mag(self.0);
let (bn, bm) = i128_sign_mag(rhs.0);
match dec_mul::<RHS_DIGITS, 2, 4>(an, &am, bn, &bm, mode) {
Some((neg, mag)) => Some(Decimal128::<DIGITS>(i128_from_sign_mag(neg, mag))),
None => None,
}
}
pub fn div_rounded(self, rhs: Self, mode: Rounding) -> Self {
self.div_rounded_to::<DIGITS>(rhs, mode)
}
#[inline]
pub fn checked_div_rounded(self, rhs: Self, mode: Rounding) -> Option<Self> {
self.checked_div_rounded_to::<DIGITS>(rhs, mode)
}
pub fn div_rounded_to<const TO_DIGITS: u8>(
self,
rhs: Self,
mode: Rounding,
) -> Decimal128<TO_DIGITS> {
if rhs.0 == 0 {
panic!("attempt to divide by zero");
}
match self.checked_div_rounded_to::<TO_DIGITS>(rhs, mode) {
Some(v) => v,
None => panic!("attempt to divide with overflow"),
}
}
#[inline]
pub fn checked_div_rounded_to<const TO_DIGITS: u8>(
self,
rhs: Self,
mode: Rounding,
) -> Option<Decimal128<TO_DIGITS>> {
let (an, am) = i128_sign_mag(self.0);
let (bn, bm) = i128_sign_mag(rhs.0);
dec_div::<TO_DIGITS, 2, 3>(an, &am, bn, &bm, mode)
.map(|(neg, mag)| Decimal128::<TO_DIGITS>(i128_from_sign_mag(neg, mag)))
}
pub fn div_int_rounded(self, n: i64, mode: Rounding) -> Self {
match self.checked_div_int_rounded(n, mode) {
Some(v) => v,
None => panic!("attempt to divide by zero"),
}
}
#[inline]
pub fn checked_div_int_rounded(self, n: i64, mode: Rounding) -> Option<Self> {
if n == 0 {
return None;
}
let (an, mut mag) = i128_sign_mag(self.0);
let neg = an != (n < 0);
let d = n.unsigned_abs();
let r = div_words_by_word(&mut mag, d);
if round_up_by_cmp(cmp_twice_rem_u64(r, d), r == 0, mag[0] & 1 != 0, neg, mode) {
mul_add_word(&mut mag, 1, 1);
}
Some(Decimal128::<DIGITS>(i128_from_sign_mag(neg, mag)))
}
#[inline]
pub const fn fract(self) -> Self {
Decimal128::<DIGITS>(self.frac_rem())
}
#[inline]
pub const fn is_positive(self) -> bool {
self.0 > 0
}
#[inline]
pub const fn is_negative(self) -> bool {
self.0 < 0
}
pub const fn signum(self) -> Self {
match (self.0 < 0, self.0 > 0) {
(true, _) => Self::MINUS_ONE,
(false, false) => Self::ZERO,
(_, _) => Self::ONE,
}
}
#[inline]
pub const fn to_bits(self) -> i128 {
self.0
}
#[inline]
pub const fn from_bits(v: i128) -> Self {
Decimal128::<DIGITS>(v)
}
#[inline]
pub const fn to_be_bytes(self) -> [u8; 16] {
self.0.to_be_bytes()
}
#[inline]
pub const fn to_le_bytes(self) -> [u8; 16] {
self.0.to_le_bytes()
}
#[inline]
pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
Decimal128::<DIGITS>(i128::from_be_bytes(bytes))
}
#[inline]
pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
Decimal128::<DIGITS>(i128::from_le_bytes(bytes))
}
pub const fn powi(self, mut exp: u32) -> Self {
let mut base = self;
let mut acc = Self::ONE;
while exp > 1 {
if (exp & 1) == 1 {
acc = base.mul_rounded(acc, Rounding::HalfUp);
}
exp /= 2;
base = base.mul_rounded(base, Rounding::HalfUp);
}
if exp == 1 {
acc = base.mul_rounded(acc, Rounding::HalfUp);
}
acc
}
#[inline]
pub fn clamp(self, min: Self, max: Self) -> Self {
if self.0 < min.0 {
min
} else if self.0 > max.0 {
max
} else {
self
}
}
#[inline]
pub fn min(self, other: Self) -> Self {
if self <= other { self } else { other }
}
#[inline]
pub fn max(self, other: Self) -> Self {
if self >= other { self } else { other }
}
}
impl<const DIGITS: u8> From<i32> for Decimal128<DIGITS> {
#[inline]
fn from(item: i32) -> Self {
Decimal128::<DIGITS>(item as i128 * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> From<i64> for Decimal128<DIGITS> {
#[inline]
fn from(item: i64) -> Self {
Decimal128::<DIGITS>(item as i128 * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> From<i128> for Decimal128<DIGITS> {
#[inline]
fn from(item: i128) -> Self {
match Self::from_i128(item) {
Ok(v) => v,
Err(_) if item < 0 => Self::MIN,
Err(_) => Self::MAX,
}
}
}
impl<const DIGITS: u8> From<f64> for Decimal128<DIGITS> {
#[inline]
fn from(item: f64) -> Self {
if (item < Self::F64_MAX) && (item > Self::F64_MIN) {
Decimal128::<DIGITS>((item * Self::SCALE_F64) as i128)
} else if item < Self::F64_MIN {
Self::MIN
} else {
Self::MAX
}
}
}
impl<const DIGITS: u8> From<f32> for Decimal128<DIGITS> {
#[inline]
fn from(item: f32) -> Self {
Self::from(item as f64)
}
}
impl<const DIGITS: u8> PartialOrd<i64> for Decimal128<DIGITS> {
#[inline]
fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
PartialOrd::partial_cmp(&self.0, &(*other as i128 * Self::SCALE_INT))
}
}
impl<const DIGITS: u8> PartialEq<i64> for Decimal128<DIGITS> {
#[inline]
fn eq(&self, other: &i64) -> bool {
self.0 == *other as i128 * Self::SCALE_INT
}
}
impl<const DIGITS: u8> Neg for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Decimal128::<DIGITS>(-self.0)
}
}
impl<const DIGITS: u8> Add for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Decimal128::<DIGITS>(self.0 + rhs.0)
}
}
impl<const DIGITS: u8> Add<i64> for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn add(self, other: i64) -> Self {
Decimal128::<DIGITS>(self.0 + other as i128 * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Add<i32> for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn add(self, rhs: i32) -> Self {
Decimal128::<DIGITS>(self.0 + (rhs as i128) * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Sub for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Decimal128::<DIGITS>(self.0 - rhs.0)
}
}
impl<const DIGITS: u8> Sub<i64> for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, rhs: i64) -> Self {
Decimal128::<DIGITS>(self.0 - rhs as i128 * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Sub<i32> for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, rhs: i32) -> Self {
Decimal128::<DIGITS>(self.0 - (rhs as i128) * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Mul for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
self.mul_rounded(rhs, Rounding::HalfUp)
}
}
impl<const DIGITS: u8> Mul<i64> for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn mul(self, rhs: i64) -> Self {
Decimal128::<DIGITS>(self.0 * rhs as i128)
}
}
impl<const DIGITS: u8> Mul<i32> for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn mul(self, rhs: i32) -> Self {
Decimal128::<DIGITS>(self.0 * (rhs as i128))
}
}
impl<const DIGITS: u8> Div for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
self.div_rounded(rhs, Rounding::HalfUp)
}
}
impl<const DIGITS: u8> Rem for Decimal128<DIGITS> {
type Output = Self;
#[inline]
fn rem(self, rhs: Self) -> Self {
if rhs.0 == 0 {
panic!("attempt to calculate the remainder with a divisor of zero");
}
Decimal128::<DIGITS>(i128_rem(self.0, rhs.0))
}
}
crate::common::impl_decimal_common!(Decimal128, "Decimal128");
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use core::str::FromStr;
use std::format;
use std::string::ToString;
#[test]
fn test_basic_ops() {
let a = Amount128::from(2);
let b = Amount128::from_f64(0.5).unwrap();
assert_eq!(a.0, 20000);
assert_eq!(b.0, 5000);
assert_eq!((a + b).0, 25000);
assert_eq!((a - b).0, 15000);
assert_eq!((a * b).0, 10000);
assert_eq!((a / b).0, 40000);
assert_eq!((-a).0, -20000);
assert_eq!((a % b).0, 0);
}
#[test]
fn test_mul_rounding_matches_decimal64() {
assert_eq!(
Decimal128::<4>(10001) * Decimal128::<4>(10001),
Decimal128::<4>(10002)
);
assert_eq!(
Decimal128::<4>(11004) * Decimal128::<4>(10015),
Decimal128::<4>(11021)
);
assert_eq!(
Decimal128::<4>(11004) * Decimal128::<4>(-10015),
Decimal128::<4>(-11021)
);
}
#[test]
fn test_div_rounding_matches_decimal64() {
assert_eq!(
Decimal128::<4>(10000) / Decimal128::<4>(110000),
Decimal128::<4>(909)
);
assert_eq!(
Decimal128::<4>(10000) / Decimal128::<4>(130000),
Decimal128::<4>(769)
);
assert_eq!(
Decimal128::<4>(10000) / Decimal128::<4>(-130000),
Decimal128::<4>(-769)
);
assert_eq!(
Decimal128::<4>(-10000) / Decimal128::<4>(130000),
Decimal128::<4>(-769)
);
assert_eq!(
Decimal128::<4>(-10000) / Decimal128::<4>(-130000),
Decimal128::<4>(769)
);
assert_eq!(
Decimal128::<4>(10000) / Decimal128::<4>(180000),
Decimal128::<4>(556)
);
assert_eq!(
Decimal128::<4>(-10000) / Decimal128::<4>(180000),
Decimal128::<4>(-556)
);
}
#[test]
fn test_beyond_64_bit_range() {
let big = Amount128::from(10_000_000_000_000_000i64); assert_eq!(big.0, 100_000_000_000_000_000_000i128);
assert_eq!((big * big).0, 10i128.pow(36));
assert_eq!(
format!("{}", big * big),
"100000000000000000000000000000000"
);
let one = Amount128::from(1);
let r = one / big;
assert_eq!(r.0, 0); let r = big / Amount128::from(3);
assert_eq!(r.0, 33_333_333_333_333_333_333i128);
}
#[test]
fn test_checked_math() {
let max = Amount128::MAX;
assert_eq!(max.checked_add(Amount128::from(1)), None);
assert_eq!(max.checked_mul(Amount128::from(2)), None);
assert_eq!(Amount128::from(2).checked_div(Amount128::ZERO), None);
assert_eq!(
Amount128::from(6).checked_div(Amount128::from(2)),
Some(Amount128::from(3))
);
assert_eq!(
Amount128::from(2).checked_mul(Amount128::from(3)),
Some(Amount128::from(6))
);
}
#[test]
fn test_rounding_modes() {
let a = Amount128::from_f64(1.5).unwrap();
assert_eq!(a.round_to(Rounding::HalfUp), Amount128::from(2));
assert_eq!(a.round_to(Rounding::HalfDown), Amount128::from(1));
assert_eq!(a.round_to(Rounding::HalfEven), Amount128::from(2));
assert_eq!(a.round_to(Rounding::Down), Amount128::from(1));
assert_eq!(a.round_to(Rounding::Up), Amount128::from(2));
let h = Amount128::from_f64(0.5).unwrap();
let x = Amount128::from_bits(10001);
assert_eq!(h.mul_rounded(x, Rounding::HalfUp).0, 5001);
assert_eq!(h.mul_rounded(x, Rounding::HalfDown).0, 5000);
assert_eq!(h.mul_rounded(x, Rounding::HalfEven).0, 5000);
assert_eq!(h.mul_rounded(x, Rounding::Down).0, 5000);
assert_eq!(h.mul_rounded(x, Rounding::Up).0, 5001);
assert_eq!((-h).mul_rounded(x, Rounding::Down).0, -5001);
assert_eq!((-h).mul_rounded(x, Rounding::Up).0, -5000);
assert_eq!((-h).mul_rounded(x, Rounding::HalfUp).0, -5001);
}
#[test]
fn test_div_rounded_modes() {
let one = Amount128::from(1);
let three = Amount128::from(3);
assert_eq!(one.div_rounded(three, Rounding::Down).0, 3333);
assert_eq!(one.div_rounded(three, Rounding::Up).0, 3334);
assert_eq!((-one).div_rounded(three, Rounding::Down).0, -3334);
assert_eq!((-one).div_rounded(three, Rounding::Up).0, -3333);
assert_eq!(one.div_rounded(three, Rounding::HalfUp).0, 3333);
}
#[test]
fn test_from_str_and_display() {
assert_eq!(Amount128::from_str("1.0001").unwrap().0, 10001);
assert_eq!(Amount128::from_str("-1.0001").unwrap().0, -10001);
assert_eq!(Amount128::from_str("1.00005").unwrap().0, 10001);
assert_eq!(
Amount128::from_str_rounded("1.00005", Rounding::HalfEven)
.unwrap()
.0,
10000
);
assert_eq!(Amount128::from_str(""), Err(AmountErrorKind::Empty));
assert_eq!(
Amount128::from_str("1.2.3"),
Err(AmountErrorKind::InvalidDigit)
);
let s = Amount128::MAX.to_string();
assert_eq!(s, "17014118346046923173168730371588410.5727");
assert_eq!(Amount128::from_str(&s).unwrap(), Amount128::MAX);
let s = Amount128::MIN.to_string();
assert_eq!(Amount128::from_str(&s).unwrap(), Amount128::MIN);
assert_eq!(
Amount128::from_str("17014118346046923173168730371588410.5728"),
Err(AmountErrorKind::Overflow)
);
assert_eq!(&format!("{}", Decimal128::<4>(10000)), "1");
assert_eq!(&format!("{:+}", Decimal128::<4>(10000)), "+1");
assert_eq!(&format!("{:4.2}", Decimal128::<4>(10000)), "1.00");
assert_eq!(&format!("{:03}", Decimal128::<4>(10000)), "001");
assert_eq!(&format!("{}", Decimal128::<4>(1)), "0.0001");
assert_eq!(&format!("{}", Decimal128::<4>(-10001)), "-1.0001");
assert_eq!(&format!("{}", Decimal128::<4>(0)), "0");
}
#[test]
fn test_decimal_parts() {
let a = Amount128::from(3);
assert_eq!(a.mantissa(), 30000);
assert_eq!(a.to_decimal_parts(), (30000, -4));
for raw in [0i128, 1, -1, 12345, i128::MAX, -i128::MAX] {
let d = Decimal128::<4>(raw);
let (m, e) = d.to_decimal_parts();
assert_eq!(Amount128::from_decimal_parts(m, e), Ok(d));
}
assert_eq!(
Amount128::from_decimal_parts(1, -5),
Err(AmountErrorKind::Inexact)
);
assert_eq!(
Amount128::from_decimal_parts(12300, -5),
Ok(Decimal128::<4>(1230))
);
assert_eq!(
Amount128::from_decimal_parts_rounded(123456, -5, Rounding::HalfUp),
Ok(Decimal128::<4>(12346))
);
assert_eq!(
Amount128::from_decimal_parts(i128::MAX, 1),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_misc() {
assert_eq!(
Amount128::from_f64(3.7).unwrap().trunc(),
Amount128::from(3)
);
assert_eq!(
Amount128::from_f64(-3.7).unwrap().floor(),
Amount128::from(-4)
);
assert_eq!(Amount128::from_f64(3.2).unwrap().ceil(), Amount128::from(4));
assert_eq!(
Amount128::from_f64(-3.5).unwrap().round(),
Amount128::from(-4)
);
assert_eq!(Amount128::from(10).signum(), Amount128::ONE);
assert_eq!(Amount128::from(-10).signum(), Amount128::MINUS_ONE);
assert_eq!(Amount128::from(0).signum(), Amount128::ZERO);
assert_eq!(Amount128::from(2).recip().0, 5000);
assert_eq!(Amount128::from(2).powi(10), Amount128::from(1024));
assert_eq!(
Amount128::from(5).clamp(Amount128::ZERO, Amount128::ONE),
Amount128::ONE
);
assert!(Amount128::from(1) == 1i64);
assert!(Amount128::from(2) > 1i64);
let v: Amount128 = [1, 2, 3].iter().map(|&x| Amount128::from(x)).sum();
assert_eq!(v, Amount128::from(6));
let bytes = Amount128::from(1).to_le_bytes();
assert_eq!(Amount128::from_le_bytes(bytes), Amount128::from(1));
assert_eq!(Rate128::from_f64(1.12345678).unwrap().0, 112345678);
assert_eq!(
&format!("{}", Rate128::from_f64(1.12345678).unwrap()),
"1.12345678"
);
}
#[test]
fn test_const_eval() {
const A: Amount128 = Amount128::from_str_const("123456789012345.6789");
const B: Amount128 = A.mul_rounded(A, Rounding::HalfUp);
const C: Amount128 = A.round_to(Rounding::HalfEven);
const D: Amount128 = A.trunc();
const E: Amount128 = Amount128::from_str_const("1.01").powi(12);
const F: Option<Amount128> = A.checked_mul(A);
const G: Amount128 = A.fract();
let a = Amount128::from_str("123456789012345.6789").unwrap();
assert_eq!(A, a);
assert_eq!(B, a.mul_rounded(a, Rounding::HalfUp));
assert_eq!(C, a.round_to(Rounding::HalfEven));
assert_eq!(D, a.trunc());
assert_eq!(E, Amount128::from_str("1.01").unwrap().powi(12));
assert_eq!(F, a.checked_mul(a));
assert_eq!(G, a.fract());
assert_eq!(G.0, 6789);
}
#[test]
fn test_rem_wide() {
let mut state = 0x9E3779B97F4A7C15u64;
let mut next = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545F4914F6CDD1D)
};
let mut rnd = move || {
let sh = (next() as u32) % 128;
let neg = next() & 1 != 0;
let m = ((((next() as u128) << 64) | next() as u128) >> sh) as i128;
if neg { m.wrapping_neg() } else { m }
};
for _ in 0..20000 {
let a = rnd();
let b = rnd();
if b == 0 {
continue;
}
assert_eq!(
(Decimal128::<4>(a) % Decimal128::<4>(b)).0,
a % b,
"{a} % {b}"
);
}
assert_eq!(
(Decimal128::<4>(i128::MIN) % Decimal128::<4>(3)).0,
i128::MIN % 3
);
assert_eq!(
(Decimal128::<4>(i128::MIN) % Decimal128::<4>(i128::MIN)).0,
0
);
assert_eq!((Decimal128::<4>(5) % Decimal128::<4>(i128::MIN)).0, 5);
assert_eq!((Decimal128::<4>(-5) % Decimal128::<4>(i128::MIN)).0, -5);
assert_eq!((Decimal128::<4>(-5) % Decimal128::<4>(5)).0, 0);
}
#[test]
#[should_panic(expected = "divisor of zero")]
fn test_rem_by_zero() {
let _ = Amount128::from(1) % Amount128::ZERO;
}
#[test]
fn test_differential_vs_decimal64() {
use crate::Decimal;
use crate::Rounding::*;
let mut state = 0x853C49E6748FEA9Bu64;
let mut next = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545F4914F6CDD1D)
};
for _ in 0..20000 {
let a = (next() as i64) >> 34;
let b = (next() as i64) >> 34;
let da = Decimal::<4>(a);
let db = Decimal::<4>(b);
let wa = Decimal128::<4>(a as i128);
let wb = Decimal128::<4>(b as i128);
assert_eq!((da + db).0 as i128, (wa + wb).0);
assert_eq!((da - db).0 as i128, (wa - wb).0);
assert_eq!((da * db).0 as i128, (wa * wb).0, "mul {a} * {b}");
assert_eq!(format!("{da}"), format!("{wa}"));
assert_eq!(format!("{da:.2}"), format!("{wa:.2}"));
let rb = Decimal::<8>(b);
let wrb = Decimal128::<8>(b as i128);
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
assert_eq!(
da.mul_rounded(db, mode).0 as i128,
wa.mul_rounded(wb, mode).0,
"mul_rounded {a} * {b}"
);
assert_eq!(
da.mul_rounded(rb, mode).0 as i128,
wa.mul_rounded(wrb, mode).0,
"mul_rounded cross-scale {a} * {b}"
);
assert_eq!(
da.round_to(mode).0 as i128,
wa.round_to(mode).0,
"round_to {a}"
);
if b != 0 {
assert_eq!(
da.div_rounded(db, mode).0 as i128,
wa.div_rounded(wb, mode).0,
"div_rounded {a} / {b}"
);
assert_eq!(
da.div_rounded_to::<8>(db, mode).0 as i128,
wa.div_rounded_to::<8>(wb, mode).0,
"div_rounded_to::<8> {a} / {b}"
);
assert_eq!(
da.div_int_rounded(b, mode).0 as i128,
wa.div_int_rounded(b, mode).0,
"div_int_rounded {a} / {b}"
);
}
}
if b != 0 {
assert_eq!((da / db).0 as i128, (wa / wb).0, "div {a} / {b}");
assert_eq!((da % db).0 as i128, (wa % wb).0, "rem {a} % {b}");
assert_eq!(
da.checked_div(db).map(|v| v.0 as i128),
wa.checked_div(wb).map(|v| v.0),
"checked_div {a} / {b}"
);
}
}
}
}