#![crate_name = "fin_decimal"]
#![crate_type = "lib"]
#![no_std]
#![deny(unconditional_recursion)]
#![warn(missing_docs)]
#![warn(clippy::all)]
use core::cmp::*;
use core::default::*;
use core::f64;
use core::fmt::*;
use core::hash::Hash;
use core::marker::*;
use core::ops::*;
use core::option::Option;
use core::result::Result;
use core::*;
mod common;
mod decimal128;
mod decimal256;
mod limbs;
pub use decimal128::{Amount128, Decimal128, Rate128};
pub use decimal256::{Amount256, Decimal256, I256, Rate256};
#[doc(hidden)]
pub mod bench_internals {
pub use crate::limbs::{isqrt_f64, isqrt_newton};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AmountErrorKind {
Empty,
InvalidDigit,
Overflow,
Inexact,
}
impl AmountErrorKind {
pub fn kind(&self) -> &AmountErrorKind {
self
}
#[doc(hidden)]
pub fn __description(&self) -> &str {
match self {
AmountErrorKind::Empty => "cannot parse integer from empty string",
AmountErrorKind::InvalidDigit => "invalid symbol found in string",
AmountErrorKind::Overflow => "number too large to fit in target type",
AmountErrorKind::Inexact => "value cannot be represented exactly at the target scale",
}
}
}
impl fmt::Display for AmountErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
core::fmt::Display::fmt(&self.__description(), f)
}
}
#[inline]
const fn i64_sign_mag(v: i64) -> (bool, [u64; 1]) {
(v < 0, [v.unsigned_abs()])
}
#[inline]
const fn i64_from_sign_mag(neg: bool, mag: [u64; 1]) -> i64 {
let v = mag[0] as i64;
if neg { v.wrapping_neg() } else { v }
}
#[inline]
const fn ipow10(pow: i64) -> i64 {
match (pow <= 0, pow >= 19) {
(true, _) => 1,
(_, true) => 0, (_, _) => limbs::upow10(pow as u32) as i64,
}
}
#[inline]
const fn ipow10_i128(pow: i64) -> Option<i128> {
if pow < 0 || pow > 38 {
return None;
}
if pow <= 19 {
return Some(limbs::upow10(pow as u32) as i128);
}
let mut v = limbs::upow10(19) as i128; let mut i = 19;
while i < pow {
v *= 10;
i += 1;
}
Some(v)
}
#[test]
fn test_ipow10() {
assert_eq!(ipow10(0), 1);
assert_eq!(ipow10(-1), 1);
assert_eq!(ipow10(19), 0);
assert_eq!(ipow10(1), 10);
assert_eq!(ipow10(4), 10000);
let mut p: i64 = 1;
for x in 0..18i64 {
assert_eq!(ipow10(x), p);
p *= 10;
}
}
#[test]
fn test_ipow10_i128() {
assert_eq!(ipow10_i128(-1), None);
let mut p: i128 = 1;
for x in 0..=38i64 {
assert_eq!(ipow10_i128(x), Some(p));
if x < 38 {
p *= 10;
}
}
assert_eq!(ipow10_i128(39), None);
assert_eq!(ipow10_i128(1000), None);
}
pub enum AmountSign {
None,
Negative,
Always,
}
pub fn str_i64(
num: i64,
frac_digit: usize,
precision: Option<usize>,
sign: AmountSign,
buf: &mut [u8],
) -> Option<&str> {
limbs::str_mag(
&[num.unsigned_abs()],
num < 0,
frac_digit,
precision,
sign,
buf,
)
}
#[test]
fn test_istr() {
let mut buf = [0u8; 3 * mem::size_of::<i64>()];
assert_eq!(
str_i64(10000, 4, None, AmountSign::Negative, &mut buf),
Some("1")
);
assert_eq!(
str_i64(10001, 4, None, AmountSign::Negative, &mut buf),
Some("1.0001")
);
assert_eq!(
str_i64(10010, 4, None, AmountSign::Negative, &mut buf),
Some("1.001")
);
assert_eq!(
str_i64(10100, 4, None, AmountSign::Negative, &mut buf),
Some("1.01")
);
assert_eq!(
str_i64(11000, 4, None, AmountSign::Negative, &mut buf),
Some("1.1")
);
assert_eq!(
str_i64(101000000, 8, None, AmountSign::Negative, &mut buf),
Some("1.01")
);
assert_eq!(
str_i64(10000, 4, Some(4), AmountSign::Negative, &mut buf),
Some("1.0000")
);
assert_eq!(
str_i64(10000, 4, Some(5), AmountSign::Negative, &mut buf),
Some("1.00000")
);
assert_eq!(
str_i64(10001, 4, Some(5), AmountSign::Negative, &mut buf),
Some("1.00010")
);
assert_eq!(
str_i64(10000, 4, Some(3), AmountSign::Negative, &mut buf),
Some("1.000")
);
assert_eq!(
str_i64(10000, 4, Some(2), AmountSign::Negative, &mut buf),
Some("1.00")
);
assert_eq!(
str_i64(10100, 4, Some(2), AmountSign::Negative, &mut buf),
Some("1.01")
);
assert_eq!(
str_i64(10050, 4, Some(2), AmountSign::Negative, &mut buf),
Some("1.01")
);
assert_eq!(
str_i64(10050, 4, Some(1), AmountSign::Negative, &mut buf),
Some("1.0")
);
assert_eq!(
str_i64(-10050, 4, Some(1), AmountSign::Negative, &mut buf),
Some("-1.0")
);
assert_eq!(
str_i64(-10050, 4, Some(1), AmountSign::None, &mut buf),
Some("1.0")
);
assert_eq!(
str_i64(-10050, 4, Some(1), AmountSign::Always, &mut buf),
Some("-1.0")
);
assert_eq!(
str_i64(10050, 4, Some(1), AmountSign::Always, &mut buf),
Some("+1.0")
);
let mut small_buf = [0u8; 5];
assert_eq!(
str_i64(10050, 4, Some(3), AmountSign::Negative, &mut small_buf),
Some("1.005")
);
assert_eq!(
str_i64(10050, 4, Some(4), AmountSign::Negative, &mut small_buf),
None
);
assert_eq!(
str_i64(100500, 4, Some(3), AmountSign::Negative, &mut small_buf),
None
);
}
pub fn parse_decimal_i64(src: &str, scale: u8) -> Result<i64, AmountErrorKind> {
parse_decimal_i64_rounded(src, scale, Rounding::HalfUp)
}
pub const fn parse_decimal_i64_rounded(
src: &str,
scale: u8,
mode: Rounding,
) -> Result<i64, AmountErrorKind> {
let (neg, mag) = match limbs::parse_decimal_mag_rounded::<1>(src, scale, mode) {
Ok(v) => v,
Err(e) => return Err(e),
};
if mag[0] > i64::MAX as u64 {
return Err(AmountErrorKind::Overflow);
}
Ok(i64_from_sign_mag(neg, mag))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rounding {
HalfEven,
HalfUp,
HalfDown,
Down,
Up,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EvenSplit<T> {
pub high: T,
pub high_count: u32,
pub low: T,
pub low_count: u32,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Decimal<const DIGITS: u8>(pub i64);
pub type Amount64 = Decimal<4>;
pub type Rate64 = Decimal<8>;
impl<const DIGITS: u8> Decimal<DIGITS> {
pub const SCALE: i32 = DIGITS as i32;
pub const SCALE_INT: i64 = ipow10(DIGITS as i64);
pub const MAX: Self = Decimal::<DIGITS>(i64::MAX);
pub const MIN: Self = Decimal::<DIGITS>(-i64::MAX);
pub const ONE: Self = Decimal::<DIGITS>(Self::SCALE_INT);
pub const MINUS_ONE: Self = Decimal::<DIGITS>(-Self::SCALE_INT);
pub const ZERO: Self = Decimal::<DIGITS>(0);
pub const INT_MIN: i64 = (i64::MIN + 1) / Self::SCALE_INT;
pub const INT_MAX: i64 = i64::MAX / Self::SCALE_INT;
pub const F64_MIN: f64 = (i64::MIN + 1) as f64 / Self::SCALE_INT as f64; pub const F64_MAX: f64 = i64::MAX as f64 / Self::SCALE_INT as f64;
pub const SCALE_INT_HALF: i64 = Self::SCALE_INT / 2;
pub const SCALE_F64: f64 = Self::SCALE_INT as f64;
#[deprecated(
since = "0.3.3",
note = "only served the old round100 implementation (and is 0 for DIGITS < 2); use round_dp"
)]
pub const SCALE_INT_100: i64 = Self::SCALE_INT / 100;
#[deprecated(
since = "0.3.3",
note = "only served the old round100 implementation (and is 0 for DIGITS < 3); use round_dp"
)]
#[allow(deprecated)]
pub const SCALE_INT_HALF_100: i64 = Self::SCALE_INT_100 / 2;
#[inline]
pub fn new() -> Self {
Decimal::<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(Decimal::<DIGITS>((val * Self::SCALE_F64) as i64))
} else {
Err(AmountErrorKind::Overflow)
}
}
#[inline]
pub const fn from_i64(val: i64) -> Result<Self, AmountErrorKind> {
if (val <= Self::INT_MAX) && (val >= Self::INT_MIN) {
Ok(Decimal::<DIGITS>(val * Self::SCALE_INT))
} else {
Err(AmountErrorKind::Overflow)
}
}
#[inline]
pub fn to_f64(self) -> f64 {
self.0 as f64 / Self::SCALE_F64
}
#[inline]
pub const fn to_i64(self) -> i64 {
self.0 / Self::SCALE_INT
}
#[inline]
pub const fn mantissa(self) -> i128 {
self.0 as i128
}
#[inline]
pub const fn to_decimal_parts(self) -> (i128, i32) {
(self.0 as i128, -Self::SCALE)
}
pub const fn from_decimal_parts(
mantissa: i128,
exponent: i32,
) -> Result<Self, AmountErrorKind> {
if mantissa == 0 {
return Ok(Decimal::<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
};
if scaled < i64::MIN as i128 || scaled > i64::MAX as i128 {
return Err(AmountErrorKind::Overflow);
}
Ok(Decimal::<DIGITS>(scaled as i64))
}
pub const fn from_decimal_parts_rounded(
mantissa: i128,
exponent: i32,
mode: Rounding,
) -> Result<Self, AmountErrorKind> {
if mantissa == 0 {
return Ok(Decimal::<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(Decimal::<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 } as i128;
if is_neg { -q } else { q }
};
if scaled < i64::MIN as i128 || scaled > i64::MAX as i128 {
return Err(AmountErrorKind::Overflow);
}
Ok(Decimal::<DIGITS>(scaled as i64))
}
pub const fn from_str_rounded(src: &str, mode: Rounding) -> Result<Self, AmountErrorKind> {
match parse_decimal_i64_rounded(src, DIGITS, mode) {
Ok(v) => Ok(Decimal::<DIGITS>(v)),
Err(e) => Err(e),
}
}
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 const fn abs(self) -> Self {
Decimal::<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(Decimal::<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(Decimal::<DIGITS>(v)),
None => None,
}
}
#[inline]
pub(crate) const fn sign_mag4(self) -> (bool, [u64; 4]) {
(self.0 < 0, [self.0.unsigned_abs(), 0, 0, 0])
}
#[inline]
pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
let (an, am) = i64_sign_mag(self.0);
let (bn, bm) = i64_sign_mag(rhs.0);
match limbs::dec_mul::<DIGITS, 1, 2>(an, &am, bn, &bm, Rounding::HalfUp) {
Some((neg, mag)) => Some(Decimal::<DIGITS>(i64_from_sign_mag(neg, mag))),
None => None,
}
}
#[inline]
pub fn checked_div(self, rhs: Self) -> Option<Self> {
let (an, am) = i64_sign_mag(self.0);
let (bn, bm) = i64_sign_mag(rhs.0);
limbs::dec_div::<DIGITS, 1, 2>(an, &am, bn, &bm, Rounding::HalfUp)
.map(|(neg, mag)| Decimal::<DIGITS>(i64_from_sign_mag(neg, mag)))
}
#[inline]
pub const fn checked_rem(self, rhs: Self) -> Option<Self> {
if rhs.0 == 0 {
None
} else {
Some(Decimal::<DIGITS>(self.0 % rhs.0))
}
}
#[inline]
pub fn recip(self) -> Self {
if self.0 == 0 {
panic!("attempt to divide by zero");
}
match self.checked_recip() {
Some(v) => v,
None => panic!("attempt to divide with overflow"),
}
}
#[inline]
pub fn checked_recip(self) -> Option<Self> {
Self::ONE.checked_div_rounded(self, Rounding::HalfUp)
}
#[inline]
pub fn trunc(self) -> Self {
Decimal::<DIGITS>(self.0 - self.0 % Self::SCALE_INT)
}
#[inline]
pub const fn floor(self) -> Self {
self.round_to(Rounding::Down)
}
#[inline]
pub const fn ceil(self) -> Self {
self.round_to(Rounding::Up)
}
#[inline]
pub const fn round(self) -> Self {
self.round_to(Rounding::HalfUp)
}
#[inline]
pub const fn round100(self) -> Self {
self.round_dp(2, Rounding::HalfUp)
}
pub const fn round_dp(self, dp: u8, mode: Rounding) -> Self {
match self.checked_round_dp(dp, mode) {
Some(v) => v,
None => panic!("attempt to round with overflow"),
}
}
pub const fn checked_round_dp(self, dp: u8, mode: Rounding) -> Option<Self> {
if dp >= DIGITS {
return Some(self);
}
let step = limbs::upow10((DIGITS - dp) as u32);
let neg = self.0 < 0;
let mag = self.0.unsigned_abs();
let q = mag / step;
let r = mag % step;
let up = limbs::round_up_by_cmp(
limbs::cmp_twice_rem_u64(r, step),
r == 0,
q & 1 != 0,
neg,
mode,
);
let scaled = (q + up as u64) * step;
if scaled > i64::MAX as u64 {
return None;
}
Some(Decimal::<DIGITS>(i64_from_sign_mag(neg, [scaled])))
}
pub const fn checked_split_evenly(self, n: u32) -> Option<EvenSplit<Self>> {
if n == 0 {
return None;
}
let neg = self.0 < 0;
let mag = self.0.unsigned_abs();
let q = mag / n as u64;
let r = (mag % n as u64) as u32;
let low = Decimal::<DIGITS>(i64_from_sign_mag(neg, [q]));
let (high, high_count) = if r == 0 {
(low, 0)
} else {
(Decimal::<DIGITS>(i64_from_sign_mag(neg, [q + 1])), r)
};
Some(EvenSplit {
high,
high_count,
low,
low_count: n - high_count,
})
}
pub const fn split_evenly(self, n: u32) -> EvenSplit<Self> {
match self.checked_split_evenly(n) {
Some(v) => v,
None => panic!("attempt to divide by zero"),
}
}
pub const fn round_to(self, mode: Rounding) -> Self {
match self.checked_round_to(mode) {
Some(v) => v,
None => panic!("attempt to round with overflow"),
}
}
pub const fn checked_round_to(self, mode: Rounding) -> Option<Self> {
let frac = self.0 % Decimal::<DIGITS>::SCALE_INT;
if frac == 0 {
return Some(self);
}
let int_part = self.0 - frac;
let mut add_one = false;
let mut sub_one = false;
match mode {
Rounding::HalfEven => {
let half = Decimal::<DIGITS>::SCALE_INT_HALF;
let is_even = (int_part / Decimal::<DIGITS>::SCALE_INT) % 2 == 0;
if frac > half {
add_one = true;
} else if frac < -half {
sub_one = true;
} else if frac == half {
if !is_even {
add_one = true;
}
} else if frac == -half && !is_even {
sub_one = true;
}
}
Rounding::HalfUp => {
let half = Decimal::<DIGITS>::SCALE_INT_HALF;
if frac >= half {
add_one = true;
} else if frac <= -half {
sub_one = true;
}
}
Rounding::HalfDown => {
let half = Decimal::<DIGITS>::SCALE_INT_HALF;
if frac > half {
add_one = true;
} else if frac < -half {
sub_one = true;
}
}
Rounding::Down => {
if frac < 0 {
sub_one = true;
}
}
Rounding::Up => {
if frac > 0 {
add_one = true;
}
}
}
let step = if add_one {
Decimal::<DIGITS>::SCALE_INT
} else if sub_one {
-Decimal::<DIGITS>::SCALE_INT
} else {
0
};
match int_part.checked_add(step) {
Some(res) => Some(Decimal::<DIGITS>(res)),
None => None,
}
}
pub const fn mul_rounded<const RHS_DIGITS: u8>(
self,
rhs: Decimal<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: Decimal<RHS_DIGITS>,
mode: Rounding,
) -> Option<Self> {
let (an, am) = i64_sign_mag(self.0);
let (bn, bm) = i64_sign_mag(rhs.0);
match limbs::dec_mul::<RHS_DIGITS, 1, 2>(an, &am, bn, &bm, mode) {
Some((neg, mag)) => Some(Decimal::<DIGITS>(i64_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,
) -> Decimal<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<Decimal<TO_DIGITS>> {
let (an, am) = i64_sign_mag(self.0);
let (bn, bm) = i64_sign_mag(rhs.0);
limbs::dec_div::<TO_DIGITS, 1, 2>(an, &am, bn, &bm, mode)
.map(|(neg, mag)| Decimal::<TO_DIGITS>(i64_from_sign_mag(neg, mag)))
}
pub const 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 const fn checked_div_int_rounded(self, n: i64, mode: Rounding) -> Option<Self> {
if n == 0 {
return None;
}
let neg = (self.0 < 0) != (n < 0);
let a = self.0.unsigned_abs();
let d = n.unsigned_abs();
let q = a / d;
let r = a % d;
let up = limbs::round_up_by_cmp(
limbs::cmp_twice_rem_u64(r, d),
r == 0,
q & 1 != 0,
neg,
mode,
);
Some(Decimal::<DIGITS>(i64_from_sign_mag(neg, [q + up as u64])))
}
pub const fn checked_sqrt_rounded(self, mode: Rounding) -> Option<Self> {
if self.0 < 0 {
return None;
}
let n = self.0 as u128 * const { limbs::upow10(DIGITS as u32) } as u128;
let (s, rem_zero, rem_gt_root) = limbs::isqrt_u128_with_rem(n);
let up = limbs::sqrt_round_up(mode, rem_zero, rem_gt_root);
let mag = s + up as u64;
if mag >> 63 != 0 {
return None;
}
Some(Decimal::<DIGITS>(mag as i64))
}
pub const fn sqrt_rounded(self, mode: Rounding) -> Self {
if self.0 < 0 {
panic!("argument of integer square root cannot be negative");
}
match self.checked_sqrt_rounded(mode) {
Some(v) => v,
None => panic!("attempt to compute square root with overflow"),
}
}
pub const fn sqrt(self) -> Self {
self.sqrt_rounded(Rounding::HalfUp)
}
#[inline]
pub const fn checked_sqrt(self) -> Option<Self> {
self.checked_sqrt_rounded(Rounding::HalfUp)
}
pub fn checked_mul_div_rounded<const S: u8>(
self,
b: Decimal<S>,
c: Decimal<S>,
mode: Rounding,
) -> Option<Self> {
let (an, am) = i64_sign_mag(self.0);
let (bn, bm) = i64_sign_mag(b.0);
let (cn, cm) = i64_sign_mag(c.0);
limbs::dec_mul_div::<1, 2>(an, &am, bn, &bm, cn, &cm, mode)
.map(|(neg, mag)| Decimal::<DIGITS>(i64_from_sign_mag(neg, mag)))
}
pub fn mul_div_rounded<const S: u8>(
self,
b: Decimal<S>,
c: Decimal<S>,
mode: Rounding,
) -> Self {
if c.0 == 0 {
panic!("attempt to divide by zero");
}
match self.checked_mul_div_rounded(b, c, mode) {
Some(v) => v,
None => panic!("attempt to divide with overflow"),
}
}
#[inline]
pub const fn fract(self) -> Self {
Decimal::<DIGITS>(self.0 % Self::SCALE_INT)
}
#[inline]
pub const fn is_positive(self) -> bool {
self.0 > 0
}
#[inline]
pub const fn is_negative(self) -> bool {
self.0 < 0
}
pub 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) -> u64 {
self.0 as u64
}
#[inline]
pub const fn from_bits(v: u64) -> Self {
Decimal::<DIGITS>(v as i64)
}
#[inline]
pub const fn swap_bytes(self) -> Self {
Decimal::<DIGITS>(self.0.swap_bytes())
}
#[inline]
pub const fn to_be_bytes(self) -> [u8; mem::size_of::<i64>()] {
self.0.to_be().to_ne_bytes()
}
#[inline]
pub const fn to_le_bytes(self) -> [u8; mem::size_of::<i64>()] {
self.0.to_le().to_ne_bytes()
}
#[inline]
pub fn to_ne_bytes(self) -> [u8; mem::size_of::<i64>()] {
#[repr(C)]
union Bytes<const DIGITS: u8> {
val: core::mem::ManuallyDrop<Decimal<DIGITS>>,
bytes: [u8; mem::size_of::<i64>()],
}
unsafe {
Bytes {
val: core::mem::ManuallyDrop::new(self),
}
.bytes
}
}
#[inline]
pub fn from_ne_bytes(bytes: [u8; mem::size_of::<i64>()]) -> Self {
#[repr(C)]
union Bytes<const DIGITS: u8> {
val: core::mem::ManuallyDrop<Decimal<DIGITS>>,
bytes: [u8; mem::size_of::<i64>()],
}
unsafe { core::mem::ManuallyDrop::into_inner(Bytes { bytes }.val) }
}
#[inline]
pub fn from_be_bytes(bytes: [u8; mem::size_of::<i64>()]) -> Self {
Self::from_bits(u64::from_be_bytes(bytes))
}
#[inline]
pub fn from_le_bytes(bytes: [u8; mem::size_of::<i64>()]) -> Self {
Self::from_bits(u64::from_le_bytes(bytes))
}
#[inline]
pub const fn powi(self, exp: u32) -> Self {
match self.checked_powi(exp) {
Some(v) => v,
None => panic!("attempt to multiply with overflow"),
}
}
pub const fn checked_powi(self, mut exp: u32) -> Option<Self> {
let mut base = self;
let mut acc = Self::ONE;
while exp > 1 {
if (exp & 1) == 1 {
acc = match base.checked_mul_rounded(acc, Rounding::HalfUp) {
Some(v) => v,
None => return None,
};
}
exp /= 2;
base = match base.checked_mul_rounded(base, Rounding::HalfUp) {
Some(v) => v,
None => return None,
};
}
if exp == 1 {
acc = match base.checked_mul_rounded(acc, Rounding::HalfUp) {
Some(v) => v,
None => return None,
};
}
Some(acc)
}
#[inline]
pub fn clamp(self, min: Self, max: Self) -> Self {
if self.0 < min.0 {
Decimal::<DIGITS>(min.0)
} else if self.0 > max.0 {
Decimal::<DIGITS>(max.0)
} 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> PartialOrd<i64> for Decimal<DIGITS> {
#[inline]
fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
PartialOrd::partial_cmp(&self.0, &(*other * Self::SCALE_INT))
}
}
impl<const DIGITS: u8> PartialOrd<f64> for Decimal<DIGITS> {
#[inline]
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
PartialOrd::partial_cmp(&(self.0 as f64), &(*other * Self::SCALE_F64))
}
}
impl<const DIGITS: u8> PartialOrd<Decimal<DIGITS>> for i64 {
#[inline]
fn partial_cmp(&self, other: &Decimal<DIGITS>) -> Option<Ordering> {
PartialOrd::partial_cmp(&(self * Decimal::<DIGITS>::SCALE_INT), &other.0)
}
}
impl<const DIGITS: u8> PartialEq<i64> for Decimal<DIGITS> {
#[inline]
fn eq(&self, other: &i64) -> bool {
self.0 == *other * Self::SCALE_INT
}
}
impl<const DIGITS: u8> PartialEq<f64> for Decimal<DIGITS> {
#[inline]
fn eq(&self, other: &f64) -> bool {
self.0 as f64 == *other * Self::SCALE_F64
}
}
impl<const DIGITS: u8> PartialEq<Decimal<DIGITS>> for i64 {
#[inline]
fn eq(&self, other: &Decimal<DIGITS>) -> bool {
*self * Decimal::<DIGITS>::SCALE_INT == other.0
}
}
impl<const DIGITS: u8> PartialEq<Decimal<DIGITS>> for f64 {
#[inline]
fn eq(&self, other: &Decimal<DIGITS>) -> bool {
*self * Decimal::<DIGITS>::SCALE_F64 == other.0 as f64
}
}
impl<const DIGITS: u8> From<i32> for Decimal<DIGITS> {
#[inline]
fn from(item: i32) -> Self {
Decimal::<DIGITS>(item as i64 * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> From<i64> for Decimal<DIGITS> {
#[inline]
fn from(item: i64) -> Self {
Decimal::<DIGITS>(item * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> From<f64> for Decimal<DIGITS> {
#[inline]
fn from(item: f64) -> Self {
if (item < Self::F64_MAX) && (item > Self::F64_MIN) {
Decimal::<DIGITS>((item * Self::SCALE_F64) as i64)
} else if item < Self::F64_MIN {
Self::MIN
} else {
Self::MAX
}
}
}
impl<const DIGITS: u8> From<f32> for Decimal<DIGITS> {
#[inline]
fn from(item: f32) -> Self {
Self::from(item as f64)
}
}
impl<const DIGITS: u8> Neg for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Decimal::<DIGITS>(-self.0)
}
}
impl<const DIGITS: u8> Add for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Decimal::<DIGITS>(self.0 + rhs.0)
}
}
impl<const DIGITS: u8> Add<i64> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn add(self, other: i64) -> Self {
Decimal::<DIGITS>(self.0 + other * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Add<f64> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn add(self, other: f64) -> Self {
Decimal::<DIGITS>(self.0 + (other * Self::SCALE_F64) as i64)
}
}
impl<const DIGITS: u8> Add<i32> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn add(self, rhs: i32) -> Self {
Decimal::<DIGITS>(self.0 + (rhs as i64) * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Add<Decimal<DIGITS>> for i64 {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn add(self, other: Decimal<DIGITS>) -> Self {
let mut quo = other.0 / Decimal::<DIGITS>::SCALE_INT;
let rem = other.0 % Decimal::<DIGITS>::SCALE_INT;
if rem >= Decimal::<DIGITS>::SCALE_INT_HALF {
quo += 1; } else if rem <= -Decimal::<DIGITS>::SCALE_INT_HALF {
quo -= 1;
}
self + quo
}
}
impl<const DIGITS: u8> Add<Decimal<DIGITS>> for f64 {
type Output = Self;
#[inline]
fn add(self, other: Decimal<DIGITS>) -> Self {
self + other.to_f64()
}
}
impl<const DIGITS: u8> Sub for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Decimal::<DIGITS>(self.0 - rhs.0)
}
}
impl<const DIGITS: u8> Sub<i64> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, rhs: i64) -> Self {
Decimal::<DIGITS>(self.0 - rhs * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Sub<i32> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, rhs: i32) -> Self {
Decimal::<DIGITS>(self.0 - (rhs as i64) * Self::SCALE_INT)
}
}
impl<const DIGITS: u8> Sub<f64> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn sub(self, other: f64) -> Self {
Decimal::<DIGITS>(self.0 - (other * Self::SCALE_F64) as i64)
}
}
impl<const DIGITS: u8> Sub<Decimal<DIGITS>> for i64 {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn sub(self, other: Decimal<DIGITS>) -> Self {
let mut quo = other.0 / Decimal::<DIGITS>::SCALE_INT;
let rem = other.0 % Decimal::<DIGITS>::SCALE_INT;
if rem >= Decimal::<DIGITS>::SCALE_INT_HALF {
quo += 1; } else if rem <= -Decimal::<DIGITS>::SCALE_INT_HALF {
quo -= 1;
}
self - quo
}
}
impl<const DIGITS: u8> Sub<Decimal<DIGITS>> for f64 {
type Output = Self;
#[inline]
fn sub(self, other: Decimal<DIGITS>) -> Self {
self - other.to_f64()
}
}
impl<const DIGITS: u8> Mul for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn mul(self, rhs: Decimal<DIGITS>) -> Self::Output {
self.mul_rounded(rhs, Rounding::HalfUp)
}
}
impl<const DIGITS: u8> Mul<i64> for Decimal<DIGITS> {
type Output = Decimal<DIGITS>;
#[inline]
fn mul(self, rhs: i64) -> Decimal<DIGITS> {
Decimal::<DIGITS>(self.0 * rhs)
}
}
impl<const DIGITS: u8> Mul<i32> for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn mul(self, rhs: i32) -> Self {
Decimal::<DIGITS>(self.0 * (rhs as i64))
}
}
impl<const DIGITS: u8> Mul<Decimal<DIGITS>> for i64 {
type Output = i64;
#[inline]
fn mul(self, rhs: Decimal<DIGITS>) -> i64 {
(Decimal::<DIGITS>(self) * rhs).0
}
}
impl<const DIGITS: u8> Div for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
self.div_rounded(rhs, Rounding::HalfUp)
}
}
impl<const DIGITS: u8> Rem for Decimal<DIGITS> {
type Output = Self;
#[inline]
fn rem(self, rhs: Self) -> Self {
match self.checked_rem(rhs) {
Some(v) => v,
None => panic!("attempt to calculate the remainder with a divisor of zero"),
}
}
}
crate::common::impl_decimal_common!(Decimal, "Decimal");
#[cfg(test)]
mod tests {
extern crate std;
use crate::Amount64;
use crate::AmountErrorKind;
use crate::Decimal;
use crate::Rate64;
use crate::Rounding;
use core::str::FromStr;
use std::format;
#[test]
fn test_decimal_parts_roundtrip() {
let a = Amount64::from(3);
assert_eq!(a.mantissa(), 30000);
assert_eq!(a.to_decimal_parts(), (30000, -4));
assert_eq!(Amount64::SCALE, 4);
assert_eq!(Decimal::<8>::SCALE, 8);
assert_eq!(Decimal::<0>::SCALE, 0);
for raw in [0i64, 1, -1, 12345, -98765, i64::MAX, -i64::MAX] {
let d = Decimal::<4>(raw);
let (m, e) = d.to_decimal_parts();
assert_eq!(Amount64::from_decimal_parts(m, e), Ok(d));
}
}
#[test]
fn test_from_decimal_parts_scaling() {
assert_eq!(
Amount64::from_decimal_parts(123, -2),
Ok(Decimal::<4>(12300))
);
assert_eq!(Amount64::from_decimal_parts(7, 0), Ok(Decimal::<4>(70000)));
assert_eq!(
Amount64::from_decimal_parts(5, 2),
Ok(Decimal::<4>(5_000_000))
);
assert_eq!(Amount64::from_decimal_parts(0, -100), Ok(Decimal::<4>(0)));
assert_eq!(Amount64::from_decimal_parts(0, 100), Ok(Decimal::<4>(0)));
assert_eq!(
Amount64::from_decimal_parts(1_230_000, -6),
Ok(Decimal::<4>(12300))
);
}
#[test]
fn test_from_decimal_parts_inexact() {
assert_eq!(
Amount64::from_decimal_parts(1, -5),
Err(AmountErrorKind::Inexact)
);
assert_eq!(
Amount64::from_decimal_parts(12345, -5),
Err(AmountErrorKind::Inexact)
);
assert_eq!(
Amount64::from_decimal_parts(1, -1000),
Err(AmountErrorKind::Inexact)
);
assert_eq!(
Amount64::from_decimal_parts(12300, -5),
Ok(Decimal::<4>(1230)) );
assert_eq!(
Amount64::from_decimal_parts(120_000, -5),
Ok(Decimal::<4>(12000)) );
assert_eq!(
Amount64::from_decimal_parts(50_000_000, -10),
Ok(Decimal::<4>(50)) );
}
#[test]
fn test_from_decimal_parts_rounded() {
use crate::Rounding::*;
for mode in [HalfEven, HalfUp, HalfDown, Up] {
assert_eq!(
Amount64::from_decimal_parts_rounded(123456, -5, mode),
Ok(Decimal::<4>(12346))
);
}
assert_eq!(
Amount64::from_decimal_parts_rounded(123456, -5, Down),
Ok(Decimal::<4>(12345))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(123455, -5, HalfUp),
Ok(Decimal::<4>(12346))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(123455, -5, HalfDown),
Ok(Decimal::<4>(12345))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(123455, -5, HalfEven),
Ok(Decimal::<4>(12346)) );
assert_eq!(
Amount64::from_decimal_parts_rounded(123465, -5, HalfEven),
Ok(Decimal::<4>(12346)) );
assert_eq!(
Amount64::from_decimal_parts_rounded(-123456, -5, HalfUp),
Ok(Decimal::<4>(-12346))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(-123456, -5, Down),
Ok(Decimal::<4>(-12345))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(-123451, -5, Up),
Ok(Decimal::<4>(-12346))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(1_230_000, -6, Down),
Ok(Decimal::<4>(12300))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(1, -1000, HalfUp),
Ok(Decimal::<4>(0))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(1, -1000, Up),
Ok(Decimal::<4>(1))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(-1, -1000, Up),
Ok(Decimal::<4>(-1))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(i128::MAX, 0, HalfUp),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_parse_rounded() {
use crate::Rounding::*;
use crate::parse_decimal_i64_rounded;
assert_eq!(Amount64::from_str("1.23455"), Ok(Decimal::<4>(12346)));
assert_eq!(parse_decimal_i64_rounded("1.23455", 4, HalfEven), Ok(12346));
assert_eq!(parse_decimal_i64_rounded("1.23465", 4, HalfEven), Ok(12346));
assert_eq!(
parse_decimal_i64_rounded("1.234551", 4, HalfEven),
Ok(12346)
);
assert_eq!(parse_decimal_i64_rounded("1.23455", 4, HalfDown), Ok(12345));
assert_eq!(
parse_decimal_i64_rounded("1.234551", 4, HalfDown),
Ok(12346)
);
assert_eq!(parse_decimal_i64_rounded("1.23450000", 4, Up), Ok(12345));
assert_eq!(parse_decimal_i64_rounded("1.2345", 4, Up), Ok(12345));
assert_eq!(parse_decimal_i64_rounded("1.23451", 4, Up), Ok(12346));
assert_eq!(parse_decimal_i64_rounded("-1.23451", 4, Up), Ok(-12346));
assert_eq!(parse_decimal_i64_rounded("1.23459", 4, Down), Ok(12345));
assert_eq!(parse_decimal_i64_rounded("0.99995", 4, HalfUp), Ok(10000));
}
#[test]
fn test_from_decimal_parts_overflow() {
assert_eq!(
Amount64::from_decimal_parts(i128::MAX, 0),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
Amount64::from_decimal_parts(1, 1000),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
Amount64::from_decimal_parts(i64::MAX as i128 + 1, -4),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_add() {
assert_eq!(
Decimal::<4>(10000) + Decimal::<4>(20000),
Decimal::<4>(30000)
);
assert_eq!(
Decimal::<4>(10000) + Decimal::<4>(20001),
Decimal::<4>(30001)
);
assert_eq!(
Decimal::<4>(10000) + Decimal::<4>(-20000),
Decimal::<4>(-10000)
);
assert_eq!(
Decimal::<4>(9223372036854775806) + Decimal::<4>(1),
Decimal::<4>(9223372036854775807)
);
}
#[test]
fn test_trunc() {
assert_eq!(Decimal::<4>(10000).trunc(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(10001).trunc(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(19999).trunc(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(199999).trunc(), Decimal::<4>(190000));
assert_eq!(Decimal::<4>(-10000).trunc(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-10001).trunc(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-19999).trunc(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-199999).trunc(), Decimal::<4>(-190000));
}
#[test]
fn test_floor() {
assert_eq!(Decimal::<4>(10000).floor(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(10001).floor(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(19999).floor(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(199999).floor(), Decimal::<4>(190000));
assert_eq!(Decimal::<4>(-10000).floor(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-10001).floor(), Decimal::<4>(-20000));
assert_eq!(Decimal::<4>(-19999).floor(), Decimal::<4>(-20000));
assert_eq!(Decimal::<4>(-199999).floor(), Decimal::<4>(-200000));
}
#[test]
fn test_round() {
assert_eq!(Decimal::<4>(10000).round(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(10001).round(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(19999).round(), Decimal::<4>(20000));
assert_eq!(Decimal::<4>(199999).round(), Decimal::<4>(200000));
assert_eq!(Decimal::<4>(-10000).round(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-10001).round(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-19999).round(), Decimal::<4>(-20000));
assert_eq!(Decimal::<4>(-199999).round(), Decimal::<4>(-200000));
}
#[test]
fn test_round100() {
assert_eq!(Decimal::<4>(10000).round100(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(10001).round100(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(19999).round100(), Decimal::<4>(20000));
assert_eq!(Decimal::<4>(199999).round100(), Decimal::<4>(200000));
assert_eq!(Decimal::<4>(-10000).round100(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-10001).round100(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-19999).round100(), Decimal::<4>(-20000));
assert_eq!(Decimal::<4>(-199999).round100(), Decimal::<4>(-200000));
assert_eq!(Decimal::<4>(-10050).round100(), Decimal::<4>(-10100));
assert_eq!(Decimal::<4>(10050).round100(), Decimal::<4>(10100));
assert_eq!(Decimal::<4>(10049).round100(), Decimal::<4>(10000));
}
#[test]
fn test_sub() {
assert_eq!(
Decimal::<4>(10000) - Decimal::<4>(20000),
Decimal::<4>(-10000)
);
assert_eq!(
Decimal::<4>(10000) - Decimal::<4>(20001),
Decimal::<4>(-10001)
);
assert_eq!(
Decimal::<4>(10000) - Decimal::<4>(-20000),
Decimal::<4>(30000)
);
assert_eq!(
Decimal::<4>(9223372036854775807) - Decimal::<4>(1),
Decimal::<4>(9223372036854775806)
);
}
#[test]
fn test_add_int() {
assert_eq!(Decimal::<4>(10000) + 2, Decimal::<4>(30000));
assert_eq!(Decimal::<4>(10001) + 2, Decimal::<4>(30001));
assert_eq!(Decimal::<4>(10000) + (-2), Decimal::<4>(-10000));
assert_eq!(
Decimal::<4>(9223372036854765806) + 1,
Decimal::<4>(9223372036854775806)
);
}
#[test]
fn test_mul() {
assert_eq!(
Decimal::<4>(10000) * Decimal::<4>(10000),
Decimal::<4>(10000)
);
assert_eq!(
Decimal::<4>(10000) * Decimal::<4>(-11111),
Decimal::<4>(-11111)
);
assert_eq!(
Decimal::<4>(10001) * Decimal::<4>(10001),
Decimal::<4>(10002)
);
assert_eq!(
Decimal::<4>(9223372036854775807) * Decimal::<4>(10000),
Decimal::<4>(9223372036854775807)
);
assert_eq!(
Decimal::<4>(11004) * Decimal::<4>(10015),
Decimal::<4>(11021)
);
assert_eq!(
Decimal::<4>(11004) * Decimal::<4>(-10015),
Decimal::<4>(-11021)
);
}
#[test]
fn test_div() {
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(10000),
Decimal::<4>(10000)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(20000),
Decimal::<4>(5000)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(5000),
Decimal::<4>(20000)
);
assert_eq!(
Decimal::<4>(10001) / Decimal::<4>(10001),
Decimal::<4>(10000)
);
assert_eq!(
Decimal::<4>(9223372036854775807) / Decimal::<4>(10000),
Decimal::<4>(9223372036854775807)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(110000),
Decimal::<4>(909)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(110000),
Decimal::<4>(909)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(130000),
Decimal::<4>(769)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(-130000),
Decimal::<4>(-769)
);
assert_eq!(
Decimal::<4>(-10000) / Decimal::<4>(-130000),
Decimal::<4>(769)
);
assert_eq!(
Decimal::<4>(-10000) / Decimal::<4>(130000),
Decimal::<4>(-769)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(180000),
Decimal::<4>(556)
);
assert_eq!(
Decimal::<4>(10000) / Decimal::<4>(-180000),
Decimal::<4>(-556)
);
assert_eq!(
Decimal::<4>(-10000) / Decimal::<4>(180000),
Decimal::<4>(-556)
);
assert_eq!(
Decimal::<4>(-10000) / Decimal::<4>(-180000),
Decimal::<4>(556)
);
}
#[test]
fn test_recip() {
assert_eq!(Decimal::<4>(10000).recip(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(9223372036854775807).recip(), Decimal::<4>(0));
assert_eq!(Decimal::<4>(110000).recip(), Decimal::<4>(909));
assert_eq!(Decimal::<4>(-110000).recip(), Decimal::<4>(-909));
assert_eq!(Decimal::<4>(130000).recip(), Decimal::<4>(769));
assert_eq!(Decimal::<4>(-130000).recip(), Decimal::<4>(-769));
assert_eq!(Decimal::<4>(180000).recip(), Decimal::<4>(556));
assert_eq!(Decimal::<4>(-180000).recip(), Decimal::<4>(-556));
}
#[test]
fn test_misc() {
let a = Amount64::from_f32(1.0).unwrap();
let b = Amount64::from_f32(0.5).unwrap();
assert_eq!(a.0, 10000);
assert_eq!(b.0, 5000);
let c = a / b;
assert_eq!(c.0, 20000);
let d = a * b;
assert_eq!(d.0, 5000);
let e = b - 1i64;
assert_eq!(e.0, -5000);
}
#[test]
fn test_from_str() {
assert_eq!(Amount64::from_str(""), Err(AmountErrorKind::Empty));
assert_eq!(Amount64::from_str("+"), Err(AmountErrorKind::Empty));
assert_eq!(Amount64::from_str("-"), Err(AmountErrorKind::Empty));
assert_eq!(Amount64::from_str("-."), Err(AmountErrorKind::Empty));
assert_eq!(Amount64::from_str("+."), Err(AmountErrorKind::Empty));
assert_eq!(Amount64::from_str("-+"), Err(AmountErrorKind::InvalidDigit));
assert_eq!(Amount64::from_str("1").unwrap().0, 10000);
assert_eq!(Amount64::from_str("+1").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.0").unwrap().0, 10000);
assert_eq!(Amount64::from_str("+1.0").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.00").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.000").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.0000").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.00000").unwrap().0, 10000);
assert_eq!(Amount64::from_str("01.00000").unwrap().0, 10000);
assert_eq!(Amount64::from_str("-1").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-1.").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-1.0").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-1.00").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-1.000").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-1.0000").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-1.00000").unwrap().0, -10000);
assert_eq!(Amount64::from_str("-01.00000").unwrap().0, -10000);
assert_eq!(Amount64::from_str("1.5").unwrap().0, 15000);
assert_eq!(Amount64::from_str("1.05").unwrap().0, 10500);
assert_eq!(Amount64::from_str("1.005").unwrap().0, 10050);
assert_eq!(Amount64::from_str("1.0005").unwrap().0, 10005);
assert_eq!(Amount64::from_str("1.00005").unwrap().0, 10001);
assert_eq!(Amount64::from_str("1.00004").unwrap().0, 10000);
assert_eq!(Amount64::from_str("1.00006").unwrap().0, 10001);
assert_eq!(
Amount64::from_str("922337203685477.5807").unwrap().0,
9223372036854775807
);
assert_eq!(
Amount64::from_str("-922337203685477.5807").unwrap().0,
-9223372036854775807
);
assert_eq!(
Amount64::from_str("922337203685477.5808"),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
Amount64::from_str("-922337203685477.5808"),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_display() {
assert_eq!(&format!("{}", Decimal::<4>(10000)), "1");
assert_eq!(&format!("{:+}", Decimal::<4>(10000)), "+1");
assert_eq!(&format!("{:2}", Decimal::<4>(10000)), " 1");
assert_eq!(&format!("{:3}", Decimal::<4>(10000)), " 1");
assert_eq!(&format!("{:4.2}", Decimal::<4>(10000)), "1.00");
assert_eq!(&format!("{:T>4.2}", Decimal::<4>(10000)), "1.00");
assert_eq!(&format!("{:<3}", Decimal::<4>(10000)), "1 ");
assert_eq!(&format!("{:03}", Decimal::<4>(10000)), "001");
assert_eq!(&format!("{:+2}", Decimal::<4>(10000)), "+1");
assert_eq!(&format!("{:+05}", Decimal::<4>(10000)), "+0001");
assert_eq!(&format!("{}", Decimal::<4>(1)), "0.0001");
assert_eq!(&format!("{}", Decimal::<4>(10)), "0.001");
assert_eq!(&format!("{}", Decimal::<4>(-10000)), "-1");
assert_eq!(&format!("{:+}", Decimal::<4>(-10000)), "-1");
assert_eq!(&format!("{}", Decimal::<4>(10001)), "1.0001");
assert_eq!(&format!("{}", Decimal::<4>(-10001)), "-1.0001");
assert_eq!(&format!("{}", Decimal::<4>(0)), "0");
assert_eq!(&format!("{:+}", Decimal::<4>(0)), "+0"); }
#[test]
fn test_rate64() {
use crate::Rate64;
let rate = Rate64::from(1.12345678);
assert_eq!(&format!("{}", rate), "1.12345678");
assert_eq!(rate.0, 112345678);
}
#[test]
fn test_rounding_modes() {
use crate::Rounding;
let a = Amount64::from(1.5);
assert_eq!(a.round_to(Rounding::HalfUp), Amount64::from(2));
assert_eq!(a.round_to(Rounding::HalfDown), Amount64::from(1));
assert_eq!(a.round_to(Rounding::HalfEven), Amount64::from(2));
assert_eq!(a.round_to(Rounding::Down), Amount64::from(1));
assert_eq!(a.round_to(Rounding::Up), Amount64::from(2));
}
#[test]
fn test_checked_math() {
let max = Amount64::MAX;
assert_eq!(max.checked_add(Amount64::from(1)), None);
assert_eq!(
Amount64::from(2).checked_sub(Amount64::from(1)),
Some(Amount64::from(1))
);
assert_eq!(Amount64::from(2).checked_div(Amount64::from(0)), None);
assert_eq!(
Amount64::from(6).checked_div(Amount64::from(2)),
Some(Amount64::from(3))
);
assert_eq!(
Amount64::from(1).checked_div(Amount64::from(3)),
Some(Decimal::<4>(3333))
);
assert_eq!(
Amount64::from(-1).checked_div(Amount64::from(3)),
Some(Decimal::<4>(-3333))
);
assert_eq!(max.checked_mul(Amount64::from(2)), None);
}
#[test]
fn test_const_eval() {
use crate::Rounding;
const PRICE: Amount64 = Amount64::from_str_const("19.99");
const RATE: Amount64 = Amount64::from_str_const("0.0825");
const TAX: Amount64 = PRICE.mul_rounded(RATE, Rounding::HalfEven);
const DOUBLED: Option<Amount64> = PRICE.checked_mul(Amount64::from_str_const("2"));
const GROWTH: Amount64 = Amount64::from_str_const("1.05").powi(10);
const ROUNDED: Amount64 = TAX.round_to(Rounding::HalfUp);
const CEIL_NEG: Amount64 = Amount64::from_str_const("-3.25").ceil();
const FLOOR_NEG: Amount64 = Amount64::from_str_const("-3.25").floor();
const REM: Option<Amount64> = PRICE.checked_rem(Amount64::from_str_const("3"));
const POW_OVER: Option<Amount64> = Amount64::MAX.checked_powi(2);
const RND_OVER: Option<Amount64> = Amount64::MAX.checked_round_to(Rounding::Up);
const RDP: Amount64 = Amount64::from_str_const("12.3456").round_dp(2, Rounding::HalfUp);
const EVEN: Option<crate::EvenSplit<Amount64>> =
Amount64::from_str_const("100.00").checked_split_evenly(3);
const FEE: Amount64 = PRICE.mul_rounded(Rate64::from_str_const("0.0725"), Rounding::HalfUp);
const SPLIT: Amount64 =
Amount64::from_str_const("100.0001").div_int_rounded(3, Rounding::HalfUp);
const ROOT2: Amount64 = Amount64::from_str_const("2").sqrt();
assert_eq!(PRICE.0, 199_900);
let price = Amount64::from_str("19.99").unwrap();
let rate = Amount64::from_str("0.0825").unwrap();
assert_eq!(TAX, price.mul_rounded(rate, Rounding::HalfEven));
assert_eq!(DOUBLED, price.checked_mul(Amount64::from(2)));
assert_eq!(GROWTH, Amount64::from_str("1.05").unwrap().powi(10));
assert_eq!(ROUNDED, TAX.round_to(Rounding::HalfUp));
assert_eq!(CEIL_NEG, Amount64::from(-3));
assert_eq!(FLOOR_NEG, Amount64::from(-4));
assert_eq!(REM, Some(Amount64::from_str_const("1.99")));
assert_eq!(POW_OVER, None);
assert_eq!(RND_OVER, None);
assert_eq!(RDP, Amount64::from_str_const("12.35"));
assert_eq!(EVEN.unwrap().high, Amount64::from_str_const("33.3334"));
assert_eq!(
FEE,
price.mul_rounded(Rate64::from_str("0.0725").unwrap(), Rounding::HalfUp)
);
assert_eq!(SPLIT, Amount64::from_str("33.3334").unwrap());
assert_eq!(ROOT2, Amount64::from(2).sqrt());
assert_eq!(ROOT2.0, 14142);
assert_eq!(
Amount64::from_str_rounded("1.00005", Rounding::HalfEven),
Ok(Decimal::<4>(10000))
);
const N: Amount64 = Amount64::from_str_const("2.5");
const ABS: Amount64 = Amount64::from_str_const("-2.5").abs();
const FRACT: Amount64 = N.fract();
const BITS: u64 = N.to_bits();
const FROM_BITS: Amount64 = Amount64::from_bits(25_000);
const SUM: Option<Amount64> = N.checked_add(N);
const FROM_INT: Result<Amount64, AmountErrorKind> = Amount64::from_i64(2);
const PARTS: Result<Amount64, AmountErrorKind> =
Amount64::from_decimal_parts_rounded(25, -1, Rounding::HalfUp);
const INT: i64 = N.to_i64();
assert_eq!(N, Decimal::<4>(25_000));
assert_eq!(ABS, N);
assert_eq!(FRACT, Decimal::<4>(5000));
assert_eq!(BITS, 25_000);
assert_eq!(FROM_BITS, N);
assert_eq!(SUM, Some(Amount64::from(5)));
assert_eq!(FROM_INT, Ok(Amount64::from(2)));
assert_eq!(PARTS, Ok(N));
assert_eq!(INT, 2);
}
#[test]
fn test_div_half_ties() {
assert_eq!(Decimal::<4>(1) / Decimal::<4>(20000), Decimal::<4>(1));
assert_eq!(Decimal::<4>(-1) / Decimal::<4>(20000), Decimal::<4>(-1));
assert_eq!(
Decimal::<4>(1).checked_div(Decimal::<4>(20000)),
Some(Decimal::<4>(1))
);
assert_eq!(Decimal::<4>(200000000).recip(), Decimal::<4>(1));
use crate::Rounding;
assert_eq!(
Decimal::<4>(1).div_rounded(Decimal::<4>(3), Rounding::HalfEven),
Decimal::<4>(3333)
);
assert_eq!(
Decimal::<4>(3333).div_rounded(Decimal::<4>(20000), Rounding::HalfEven),
Decimal::<4>(1666)
);
assert_eq!(
Decimal::<4>(3335).div_rounded(Decimal::<4>(20000), Rounding::HalfEven),
Decimal::<4>(1668)
);
}
#[test]
fn test_mul_rounded_cross_scale() {
use crate::Rounding::*;
let amount = Amount64::from_str("125.00").unwrap();
let rate = Rate64::from_str("0.0725").unwrap();
assert_eq!(
amount.mul_rounded(rate, HalfUp),
Amount64::from_str("9.0625").unwrap()
);
assert_eq!(
Amount64::MAX.checked_mul_rounded(Amount64::from(2), HalfUp),
None
);
assert_eq!(
amount.checked_mul_rounded(rate, HalfUp),
Some(amount.mul_rounded(rate, HalfUp))
);
let tie = Rate64::from_str("0.00005").unwrap();
assert_eq!(Amount64::from(1).mul_rounded(tie, HalfUp).0, 1);
assert_eq!(Amount64::from(1).mul_rounded(tie, HalfEven).0, 0);
assert_eq!(Amount64::from(1).mul_rounded(tie, Down).0, 0);
assert_eq!(Amount64::from(-1).mul_rounded(tie, HalfUp).0, -1);
assert_eq!(Amount64::from(-1).mul_rounded(tie, Down).0, -1);
assert_eq!(Amount64::from(-1).mul_rounded(tie, Up).0, 0);
}
#[test]
fn test_div_rounded_to() {
use crate::Rounding::*;
let two = Amount64::from(2);
let three = Amount64::from(3);
assert_eq!(two.div_rounded_to::<8>(three, HalfUp).0, 66_666_667);
assert_eq!(two.div_rounded_to::<8>(three, Down).0, 66_666_666);
assert_eq!((-two).div_rounded_to::<8>(three, Down).0, -66_666_667);
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
assert_eq!(
two.div_rounded_to::<4>(three, mode),
two.div_rounded(three, mode)
);
}
assert_eq!(two.div_rounded_to::<1>(three, HalfUp).0, 7);
assert_eq!(
two.checked_div_rounded_to::<8>(Amount64::ZERO, HalfUp),
None
);
assert_eq!(
Amount64::MAX
.checked_div_rounded_to::<8>(Amount64::from_str("0.0001").unwrap(), HalfUp),
None
);
assert_eq!(
two.checked_div_rounded_to::<8>(three, HalfUp),
Some(two.div_rounded_to::<8>(three, HalfUp))
);
assert_eq!(
two.checked_div_rounded(three, HalfUp),
Some(two.div_rounded(three, HalfUp))
);
assert_eq!(two.checked_div_rounded(Amount64::ZERO, HalfUp), None);
}
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_div_rounded_to_by_zero() {
let _ = Amount64::from(1).div_rounded_to::<8>(Amount64::ZERO, Rounding::HalfUp);
}
#[test]
fn test_div_int_rounded() {
use crate::Rounding::*;
assert_eq!(Decimal::<4>(1).div_int_rounded(2, HalfUp).0, 1);
assert_eq!(Decimal::<4>(1).div_int_rounded(2, HalfDown).0, 0);
assert_eq!(Decimal::<4>(1).div_int_rounded(2, HalfEven).0, 0);
assert_eq!(Decimal::<4>(-1).div_int_rounded(2, HalfUp).0, -1);
assert_eq!(Decimal::<4>(1).div_int_rounded(-2, HalfUp).0, -1);
assert_eq!(Decimal::<4>(1).div_int_rounded(3, Up).0, 1);
assert_eq!(Decimal::<4>(-1).div_int_rounded(3, Up).0, 0);
assert_eq!(Decimal::<4>(-1).div_int_rounded(3, Down).0, -1);
assert_eq!(Amount64::MAX.div_int_rounded(1, Up), Amount64::MAX);
assert_eq!(Amount64::MAX.div_int_rounded(-1, Up), Amount64::MIN);
assert_eq!(Amount64::from(1).checked_div_int_rounded(0, HalfUp), None);
assert_eq!(
Amount64::from(1).checked_div_int_rounded(3, HalfUp),
Some(Amount64::from(1).div_int_rounded(3, HalfUp))
);
let mut state = 0x9E3779B97F4A7C15u64;
let mut next = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545F4914F6CDD1D)
};
for _ in 0..2000 {
let a = (next() as i64) >> 20;
let n = (next() as i64) >> 49;
if n == 0 {
continue;
}
let da = Decimal::<4>(a);
let dn = Decimal::<4>::from(n);
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
assert_eq!(
da.div_int_rounded(n, mode),
da.div_rounded(dn, mode),
"div_int_rounded {a} / {n}"
);
}
}
}
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_div_int_rounded_by_zero() {
let _ = Amount64::from(1).div_int_rounded(0, Rounding::HalfUp);
}
#[test]
fn test_sqrt() {
use crate::Rounding::*;
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
assert_eq!(
Amount64::from_str("2.25").unwrap().sqrt_rounded(mode),
Amount64::from_str("1.5").unwrap()
);
assert_eq!(Amount64::from(144).sqrt_rounded(mode), Amount64::from(12));
assert_eq!(Amount64::ZERO.sqrt_rounded(mode), Amount64::ZERO);
}
let two = Amount64::from(2);
assert_eq!(two.sqrt().0, 14142);
assert_eq!(two.sqrt_rounded(Down).0, 14142);
assert_eq!(two.sqrt_rounded(Up).0, 14143);
assert_eq!(Amount64::from(3).sqrt().0, 17321);
assert_eq!(Amount64::from(3).sqrt_rounded(Down).0, 17320);
assert_eq!(Amount64::from(-1).checked_sqrt_rounded(HalfUp), None);
assert_eq!(Amount64::from(-1).checked_sqrt(), None);
assert_eq!(
Amount64::from(2).checked_sqrt(),
Some(Amount64::from(2).sqrt())
);
assert_eq!(Decimal::<19>(i64::MAX).checked_sqrt_rounded(HalfUp), None);
let quarter = Decimal::<19>(2_500_000_000_000_000_000); assert_eq!(
quarter.checked_sqrt_rounded(HalfUp),
Some(Decimal::<19>(5_000_000_000_000_000_000))
);
let mut state = 0xC0FFEE123456789Fu64;
let mut next = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545F4914F6CDD1D)
};
for i in 0..5000 {
let a = (next() >> (i % 64)) as i64 & i64::MAX;
let da = Decimal::<4>(a);
let n = a as u128 * 10_000;
let s = n.isqrt();
let rem = n - s * s;
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
let want = s as i64
+ match mode {
Down => 0,
Up => (rem != 0) as i64,
_ => (rem > s) as i64,
};
assert_eq!(da.sqrt_rounded(mode).0, want, "sqrt {a} mode {mode:?}");
}
let f = da.sqrt_rounded(Down).0 as u128;
assert!(f * f <= n && (f + 1) * (f + 1) > n);
}
}
#[test]
#[should_panic(expected = "argument of integer square root cannot be negative")]
fn test_sqrt_negative_panics() {
let _ = Amount64::from(-4).sqrt();
}
#[test]
fn test_mul_div_rounded() {
use crate::Rounding::*;
let total = Amount64::from(100);
assert_eq!(
total
.mul_div_rounded(Amount64::from(1), Amount64::from(3), HalfUp)
.0,
333_333
);
assert_eq!(
total
.mul_div_rounded(Amount64::from(2), Amount64::from(3), HalfUp)
.0,
666_667
);
let part = Rate64::from_str("0.12345678").unwrap();
let whole = Rate64::from_str("0.98765432").unwrap();
let got = total.mul_div_rounded(part, whole, HalfUp);
assert_eq!(got.0, 125_000); for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
assert_eq!(
total.mul_div_rounded(part, Rate64::ONE, mode),
total.mul_rounded(part, mode)
);
}
let big = Amount64::from(30_000_000_000i64);
let exact = big.mul_div_rounded(Amount64::from(1), Amount64::from(3), HalfUp);
assert_eq!(exact, Amount64::from(10_000_000_000i64));
let two_step = big.mul_rounded(
Amount64::from(1).div_rounded_to::<8>(Amount64::from(3), HalfUp),
HalfUp,
);
assert_ne!(exact, two_step);
assert_eq!(
Amount64::MAX.checked_mul_div_rounded(Amount64::from(2), Amount64::from(1), HalfUp),
None
);
assert_eq!(
total.checked_mul_div_rounded(Amount64::from(1), Amount64::ZERO, HalfUp),
None
);
let mut state = 0xFEEDFACE8BADF00Du64;
let mut next = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545F4914F6CDD1D)
};
for _ in 0..5000 {
let a = (next() as i64) >> 20;
let b = (next() as i64) >> 20;
let c = (next() as i64) >> 40;
if c == 0 {
continue;
}
let num = a as i128 * b as i128;
let q = num / c as i128;
let r = num % c as i128;
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
let want = {
let neg = (num < 0) != ((c as i128) < 0);
let qa = q.unsigned_abs();
let ra = r.unsigned_abs();
let da = (c as i128).unsigned_abs();
let up = match mode {
Down => neg && ra != 0,
Up => !neg && ra != 0,
HalfUp => 2 * ra >= da,
HalfDown => 2 * ra > da,
HalfEven => 2 * ra > da || (2 * ra == da && qa & 1 == 1),
};
let m = qa + up as u128;
if m >> 63 != 0 {
None
} else if neg {
Some(-(m as i64))
} else {
Some(m as i64)
}
};
assert_eq!(
Decimal::<4>(a)
.checked_mul_div_rounded(Decimal::<4>(b), Decimal::<4>(c), mode)
.map(|v| v.0),
want,
"mul_div {a} * {b} / {c} mode {mode:?}"
);
}
}
}
#[test]
fn test_error_kind_accessors() {
assert_eq!(AmountErrorKind::Empty.kind(), &AmountErrorKind::Empty);
assert_eq!(AmountErrorKind::Inexact.kind(), &AmountErrorKind::Inexact);
assert_eq!(
format!("{}", AmountErrorKind::Empty),
"cannot parse integer from empty string"
);
assert_eq!(
format!("{}", AmountErrorKind::InvalidDigit),
"invalid symbol found in string"
);
assert_eq!(
format!("{}", AmountErrorKind::Overflow),
"number too large to fit in target type"
);
assert_eq!(
format!("{}", AmountErrorKind::Inexact),
"value cannot be represented exactly at the target scale"
);
}
#[test]
fn test_parse_decimal_i64_default() {
use crate::parse_decimal_i64;
assert_eq!(parse_decimal_i64("1.23455", 4), Ok(12346));
assert_eq!(parse_decimal_i64("-1.23455", 4), Ok(-12346));
assert_eq!(parse_decimal_i64("1.5", 8), Ok(150_000_000));
assert_eq!(
parse_decimal_i64("bad", 4),
Err(AmountErrorKind::InvalidDigit)
);
}
#[test]
fn test_new_and_conversions() {
assert_eq!(Amount64::new(), Amount64::ZERO);
assert_eq!(Amount64::new().0, 0);
assert_eq!(Amount64::from_i64(3), Ok(Decimal::<4>(30000)));
assert_eq!(Amount64::from_i64(-3), Ok(Decimal::<4>(-30000)));
assert_eq!(
Amount64::from_i64(Amount64::INT_MAX),
Ok(Decimal::<4>(Amount64::INT_MAX * Amount64::SCALE_INT))
);
assert_eq!(
Amount64::from_i64(Amount64::INT_MAX + 1),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
Amount64::from_i64(Amount64::INT_MIN - 1),
Err(AmountErrorKind::Overflow)
);
assert_eq!(Decimal::<4>(19999).to_i64(), 1);
assert_eq!(Decimal::<4>(-19999).to_i64(), -1);
assert_eq!(Decimal::<4>(0).to_i64(), 0);
assert_eq!(Decimal::<4>(15000).to_f64(), 1.5);
assert_eq!(Decimal::<4>(-15000).to_f64(), -1.5);
assert_eq!(Amount64::from_f64(1e30), Err(AmountErrorKind::Overflow));
assert_eq!(Amount64::from_f64(-1e30), Err(AmountErrorKind::Overflow));
assert_eq!(Amount64::from_f32(1.5), Ok(Decimal::<4>(15000)));
assert_eq!(Amount64::from_f32(1e30), Err(AmountErrorKind::Overflow));
}
#[test]
fn test_from_decimal_parts_rounded_scale_up() {
use crate::Rounding::*;
assert_eq!(
Amount64::from_decimal_parts_rounded(0, 1000, HalfUp),
Ok(Decimal::<4>(0))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(0, -1000, Down),
Ok(Decimal::<4>(0))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(5, 2, Down),
Ok(Decimal::<4>(5_000_000))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(123, -2, HalfUp),
Ok(Decimal::<4>(12300))
);
assert_eq!(
Amount64::from_decimal_parts_rounded(1, 1000, HalfUp),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
Amount64::from_decimal_parts_rounded(i64::MAX as i128 + 1, -4, HalfUp),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_from_str_const_runtime() {
assert_eq!(Amount64::from_str_const("19.99").0, 199_900);
assert_eq!(Amount64::from_str_const("-1.00005").0, -10001);
}
#[test]
#[should_panic(expected = "invalid decimal literal")]
fn test_from_str_const_invalid_panics() {
let _ = Amount64::from_str_const("not a number");
}
#[test]
fn test_from_str_ref() {
assert_eq!(Amount64::from("1.5"), Decimal::<4>(15000));
assert_eq!(Amount64::from("-0.0001"), Decimal::<4>(-1));
assert_eq!(Amount64::from("1.00005"), Decimal::<4>(10001));
}
#[test]
#[should_panic(expected = "InvalidDigit")]
fn test_from_str_ref_invalid_panics() {
let _ = Amount64::from("bogus");
}
#[test]
fn test_abs_signum_predicates() {
assert_eq!(Decimal::<4>(-12345).abs(), Decimal::<4>(12345));
assert_eq!(Decimal::<4>(12345).abs(), Decimal::<4>(12345));
assert_eq!(Amount64::ZERO.abs(), Amount64::ZERO);
assert!(Amount64::from(10).is_positive());
assert!(!Amount64::ZERO.is_positive());
assert!(!Amount64::from(-10).is_positive());
assert!(Amount64::from(-10).is_negative());
assert!(!Amount64::ZERO.is_negative());
assert!(!Amount64::from(10).is_negative());
assert_eq!(Amount64::from(10).signum(), Amount64::ONE);
assert_eq!(Amount64::from(-10).signum(), Amount64::MINUS_ONE);
assert_eq!(Amount64::ZERO.signum(), Amount64::ZERO);
}
#[test]
fn test_checked_add_sub_edges() {
assert_eq!(
Amount64::from(1).checked_add(Amount64::from(2)),
Some(Amount64::from(3))
);
assert_eq!(Amount64::MIN.checked_sub(Amount64::from(1)), None);
assert_eq!(Amount64::from(-1).checked_add(Amount64::MIN), None);
}
#[test]
fn test_ceil() {
assert_eq!(Decimal::<4>(10000).ceil(), Decimal::<4>(10000));
assert_eq!(Decimal::<4>(10001).ceil(), Decimal::<4>(20000));
assert_eq!(Decimal::<4>(19999).ceil(), Decimal::<4>(20000));
assert_eq!(Decimal::<4>(0).ceil(), Decimal::<4>(0));
assert_eq!(Decimal::<4>(-10000).ceil(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-10001).ceil(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-19999).ceil(), Decimal::<4>(-10000));
assert_eq!(Decimal::<4>(-9999).ceil(), Decimal::<4>(0));
for m in [-19999i64, -10001, -9999, 0, 9999, 10001, 19999] {
assert_eq!(Decimal::<4>(m).ceil(), -Decimal::<4>(-m).floor(), "{m}");
}
}
#[test]
fn test_round_dp() {
use crate::Rounding::*;
let v = Amount64::from_str_const("12.3456");
assert_eq!(v.round_dp(2, HalfUp), Amount64::from_str_const("12.35"));
assert_eq!(v.round_dp(2, Down), Amount64::from_str_const("12.34"));
assert_eq!(v.round_dp(3, HalfUp), Amount64::from_str_const("12.346"));
assert_eq!(v.round_dp(4, HalfUp), v);
assert_eq!(v.round_dp(200, HalfUp), v);
let w = Amount64::from_str_const("-7.5000");
for mode in [HalfUp, HalfDown, HalfEven, Down, Up] {
assert_eq!(v.round_dp(0, mode), v.round_to(mode), "{mode:?}");
assert_eq!(w.round_dp(0, mode), w.round_to(mode), "{mode:?}");
}
assert_eq!(
Amount64::from_str_const("0.0250").round_dp(2, HalfEven),
Amount64::from_str_const("0.02")
);
assert_eq!(
Amount64::from_str_const("0.0350").round_dp(2, HalfEven),
Amount64::from_str_const("0.04")
);
let neg = Amount64::from_str_const("-12.3456");
assert_eq!(neg.round_dp(2, Down), Amount64::from_str_const("-12.35"));
assert_eq!(neg.round_dp(2, Up), Amount64::from_str_const("-12.34"));
assert_eq!(Amount64::MAX.checked_round_dp(2, Up), None);
assert_eq!(Amount64::MIN.checked_round_dp(2, Down), None);
assert_eq!(v.round100(), v.round_dp(2, HalfUp));
}
#[test]
#[should_panic(expected = "attempt to round with overflow")]
fn test_round_dp_edge_overflow_panics() {
let _ = Amount64::MAX.round_dp(2, Rounding::Up);
}
#[test]
fn test_split_evenly() {
let s = Amount64::from_str_const("100.00").split_evenly(3);
assert_eq!(s.high.0, 333_334);
assert_eq!(s.high_count, 1);
assert_eq!(s.low.0, 333_333);
assert_eq!(s.low_count, 2);
let s = Amount64::from_str_const("100.00").split_evenly(4);
assert_eq!((s.high, s.high_count), (s.low, 0));
assert_eq!((s.low.0, s.low_count), (250_000, 4));
let v = Amount64::from_str_const("-3.1415");
let s = v.split_evenly(1);
assert_eq!((s.low, s.low_count, s.high_count), (v, 1, 0));
let s = Amount64::ZERO.split_evenly(7);
assert_eq!((s.low, s.low_count, s.high_count), (Amount64::ZERO, 7, 0));
assert_eq!(v.checked_split_evenly(0), None);
let mut state = 0x5EED5EED5EEDu64;
let mut rng = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545F4914F6CDD1D)
};
for _ in 0..20000 {
let m = (rng() as i64) >> (rng() % 40);
let n = 1 + (rng() % 999) as u32;
let v = Decimal::<4>(m);
let s = v.split_evenly(n);
assert_eq!(s.high_count + s.low_count, n);
assert_eq!(
s.high.0 as i128 * s.high_count as i128 + s.low.0 as i128 * s.low_count as i128,
m as i128,
"conservation {m} / {n}"
);
if s.high_count > 0 {
assert_eq!(s.high.0 - s.low.0, if m < 0 { -1 } else { 1 }, "{m} {n}");
} else {
assert_eq!(s.high, s.low);
}
}
}
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_split_evenly_zero_panics() {
let _ = Amount64::from(1).split_evenly(0);
}
#[test]
fn test_checked_rem_powi_recip_round_to() {
use crate::Rounding::*;
let a = Amount64::from_str_const("7.5");
let b = Amount64::from(2);
assert_eq!(a.checked_rem(b), Some(Amount64::from_str_const("1.5")));
assert_eq!(a.checked_rem(Amount64::ZERO), None);
assert_eq!(b.checked_powi(0), Some(Amount64::ONE));
assert_eq!(b.checked_powi(1), Some(b));
assert_eq!(b.checked_powi(10), Some(Amount64::from(1024)));
assert_eq!(Amount64::MAX.checked_powi(2), None);
let big = Amount64::from(100_000);
assert_eq!(big.checked_powi(3), None);
assert_eq!(big.checked_powi(7), None);
assert_eq!(b.checked_recip(), Some(Amount64::from_str_const("0.5")));
assert_eq!(Amount64::ZERO.checked_recip(), None);
assert_eq!(Decimal::<10>::from_bits(1).checked_recip(), None);
assert_eq!(a.checked_round_to(Down), Some(Amount64::from(7)));
assert_eq!(Amount64::MAX.checked_round_to(Up), None);
assert_eq!(Amount64::MIN.checked_round_to(Down), None);
}
#[test]
#[should_panic(expected = "attempt to calculate the remainder")]
fn test_rem_by_zero_panics() {
let _ = Amount64::from(1) % Amount64::ZERO;
}
#[test]
#[should_panic(expected = "attempt to multiply with overflow")]
fn test_powi_overflow_panics() {
let _ = Amount64::MAX.powi(2);
}
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_recip_zero_panics() {
let _ = Amount64::ZERO.recip();
}
#[test]
#[should_panic(expected = "attempt to divide with overflow")]
fn test_recip_overflow_panics() {
let _ = Decimal::<10>::from_bits(1).recip();
}
#[test]
#[should_panic(expected = "attempt to round with overflow")]
fn test_round100_edge_overflow_panics() {
let _ = Decimal::<3>::MAX.round100();
}
#[test]
#[should_panic(expected = "attempt to round with overflow")]
fn test_ceil_edge_overflow_panics() {
let _ = Amount64::MAX.ceil();
}
#[test]
#[should_panic(expected = "attempt to round with overflow")]
fn test_floor_edge_overflow_panics() {
let _ = Amount64::MIN.floor();
}
#[test]
fn test_f64_eq_uses_type_scale() {
assert!(0.5f64 == Rate64::from_str_const("0.5"));
assert!(0.5f64 != Rate64::from_str_const("0.00005"));
assert!(2.0f64 == Decimal::<0>::from(2));
assert!(0.5f64 == Amount64::from_str_const("0.5"));
}
#[test]
fn test_round_to_halfeven_negative_ties() {
use crate::Rounding::HalfEven;
assert_eq!(
Decimal::<4>(-15000).round_to(HalfEven),
Decimal::<4>(-20000)
);
assert_eq!(
Decimal::<4>(-25000).round_to(HalfEven),
Decimal::<4>(-20000)
);
assert_eq!(
Decimal::<4>(-16000).round_to(HalfEven),
Decimal::<4>(-20000)
);
assert_eq!(
Decimal::<4>(-14000).round_to(HalfEven),
Decimal::<4>(-10000)
);
}
#[test]
fn test_fract() {
assert_eq!(Decimal::<4>(36000).fract(), Decimal::<4>(6000));
assert_eq!(Decimal::<4>(-36000).fract(), Decimal::<4>(-6000));
assert_eq!(Decimal::<4>(30000).fract(), Decimal::<4>(0));
}
#[test]
#[should_panic(expected = "attempt to multiply with overflow")]
fn test_mul_overflow_panics() {
let _ = Amount64::MAX * Amount64::from(2);
}
#[test]
#[should_panic(expected = "attempt to divide with overflow")]
fn test_div_overflow_panics() {
let _ = Amount64::MAX / Amount64::from_str_const("0.0001");
}
#[test]
#[should_panic(expected = "attempt to compute square root with overflow")]
fn test_sqrt_overflow_panics() {
let _ = Decimal::<19>(i64::MAX).sqrt();
}
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_mul_div_rounded_by_zero_panics() {
let _ =
Amount64::from(1).mul_div_rounded(Amount64::from(1), Amount64::ZERO, Rounding::HalfUp);
}
#[test]
#[should_panic(expected = "attempt to divide with overflow")]
fn test_mul_div_rounded_overflow_panics() {
let _ =
Amount64::MAX.mul_div_rounded(Amount64::from(2), Amount64::from(1), Rounding::HalfUp);
}
#[test]
fn test_bits_and_bytes() {
let n = Amount64::from_bits(0x1234567890123456u64);
assert_eq!(n.0, 0x1234567890123456i64);
assert_eq!(n.to_bits(), 0x1234567890123456u64);
assert_eq!(
Amount64::from_bits(Decimal::<4>(-1).to_bits()),
Decimal::<4>(-1)
);
assert_eq!(n.swap_bytes().to_bits(), 0x5634129078563412u64);
assert_eq!(n.swap_bytes().swap_bytes(), n);
let be = [0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56];
let le = [0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12];
assert_eq!(n.to_be_bytes(), be);
assert_eq!(n.to_le_bytes(), le);
assert_eq!(Amount64::from_be_bytes(be), n);
assert_eq!(Amount64::from_le_bytes(le), n);
let ne = n.to_ne_bytes();
assert_eq!(ne, if cfg!(target_endian = "big") { be } else { le });
assert_eq!(Amount64::from_ne_bytes(ne), n);
}
#[test]
fn test_clamp_min_max() {
let lo = Amount64::MINUS_ONE;
let hi = Amount64::ONE;
assert_eq!(Amount64::from(2).clamp(lo, hi), hi);
assert_eq!(Amount64::from(-2).clamp(lo, hi), lo);
assert_eq!(Amount64::ZERO.clamp(lo, hi), Amount64::ZERO);
assert_eq!(hi.min(lo), lo);
assert_eq!(lo.min(hi), lo);
assert_eq!(hi.max(lo), hi);
assert_eq!(lo.max(hi), hi);
let a = Amount64::from(3);
assert_eq!(a.min(a), a);
assert_eq!(a.max(a), a);
}
#[test]
fn test_mixed_comparisons() {
let a = Amount64::from(2);
assert!(a > 1i64);
assert!(a < 3i64);
assert!(a == 2i64);
assert!(1i64 < a);
assert!(3i64 > a);
assert!(2i64 == a);
assert!(2i64 != Amount64::from_str_const("2.5"));
assert!(a > 1.5f64);
assert!(a < 2.5f64);
assert!(a == 2.0f64);
assert!(2.0f64 == a);
assert!(2.5f64 != a);
assert!(Amount64::from_str_const("2.5") == 2.5f64);
}
#[test]
fn test_from_saturating_and_f32() {
assert_eq!(Amount64::from(1e30f64), Amount64::MAX);
assert_eq!(Amount64::from(-1e30f64), Amount64::MIN);
assert_eq!(Amount64::from(1.5f32), Decimal::<4>(15000));
assert_eq!(Amount64::from(-2.25f32), Decimal::<4>(-22500));
assert_eq!(Amount64::from(3i32), Decimal::<4>(30000));
assert_eq!(Amount64::from(-3i32), Decimal::<4>(-30000));
}
#[test]
fn test_mixed_arithmetic_ops() {
let a = Amount64::from(2);
assert_eq!(a + 3i64, Amount64::from(5));
assert_eq!(a + 3i32, Amount64::from(5));
assert_eq!(a + 0.5f64, Amount64::from_str_const("2.5"));
assert_eq!(a - 3i64, Amount64::from(-1));
assert_eq!(a - 1i32, Amount64::from(1));
assert_eq!(a - 0.5f64, Amount64::from_str_const("1.5"));
assert_eq!(10i64 + Amount64::from_str_const("2.5"), 13);
assert_eq!(10i64 + Amount64::from_str_const("2.4999"), 12);
assert_eq!(10i64 + Amount64::from_str_const("-2.5"), 7);
assert_eq!(10i64 - Amount64::from_str_const("2.5"), 7);
assert_eq!(10i64 - Amount64::from_str_const("2.4999"), 8);
assert_eq!(10i64 - Amount64::from_str_const("-2.5"), 13);
assert_eq!(1.5f64 + Amount64::from_str_const("2.25"), 3.75f64);
assert_eq!(10.0f64 - Amount64::from_str_const("2.5"), 7.5f64);
assert_eq!(a * 3i64, Amount64::from(6));
assert_eq!(a * 3i32, Amount64::from(6));
assert_eq!(20000i64 * Amount64::from(3), 60000i64);
}
#[test]
fn test_assign_ops() {
let mut a = Amount64::from(10);
a += Amount64::from(2);
assert_eq!(a, Amount64::from(12));
a -= Amount64::from(4);
assert_eq!(a, Amount64::from(8));
a *= Amount64::from(2);
assert_eq!(a, Amount64::from(16));
a /= Amount64::from(4);
assert_eq!(a, Amount64::from(4));
a %= Amount64::from_str_const("2.5");
assert_eq!(a, Amount64::from_str_const("1.5"));
}
#[test]
fn test_sum_product_iters() {
let vals = [Amount64::from(1), Amount64::from(2), Amount64::from(3)];
assert_eq!(vals.iter().copied().sum::<Amount64>(), Amount64::from(6));
assert_eq!(vals.iter().sum::<Amount64>(), Amount64::from(6));
assert_eq!(
vals.iter().copied().product::<Amount64>(),
Amount64::from(6)
);
assert_eq!(vals.iter().product::<Amount64>(), Amount64::from(6));
assert_eq!(
core::iter::empty::<Amount64>().sum::<Amount64>(),
Amount64::ZERO
);
assert_eq!(
core::iter::empty::<Amount64>().product::<Amount64>(),
Amount64::ONE
);
}
#[test]
fn test_display_error_fallback() {
assert_eq!(format!("{:.200}", Decimal::<4>(10000)), "Amount::ERROR");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde() {
use core::fmt::Write as _;
use serde::de::value::{Error as ValueError, StrDeserializer};
use serde::ser::{Error as _, Impossible};
use serde::{Deserialize, Serialize, Serializer};
use std::string::String;
struct StrCollector;
macro_rules! reject {
($($m:ident($($arg:ty),*) -> $ok:ty;)*) => {$(
fn $m(self, $(_: $arg),*) -> Result<$ok, ValueError> {
Err(ValueError::custom("unsupported"))
}
)*};
}
impl Serializer for StrCollector {
type Ok = String;
type Error = ValueError;
type SerializeSeq = Impossible<String, ValueError>;
type SerializeTuple = Impossible<String, ValueError>;
type SerializeTupleStruct = Impossible<String, ValueError>;
type SerializeTupleVariant = Impossible<String, ValueError>;
type SerializeMap = Impossible<String, ValueError>;
type SerializeStruct = Impossible<String, ValueError>;
type SerializeStructVariant = Impossible<String, ValueError>;
fn collect_str<T>(self, value: &T) -> Result<String, ValueError>
where
T: ?Sized + core::fmt::Display,
{
let mut out = String::new();
write!(out, "{value}").map_err(|_| ValueError::custom("fmt"))?;
Ok(out)
}
reject! {
serialize_bool(bool) -> String;
serialize_i8(i8) -> String;
serialize_i16(i16) -> String;
serialize_i32(i32) -> String;
serialize_i64(i64) -> String;
serialize_u8(u8) -> String;
serialize_u16(u16) -> String;
serialize_u32(u32) -> String;
serialize_u64(u64) -> String;
serialize_f32(f32) -> String;
serialize_f64(f64) -> String;
serialize_char(char) -> String;
serialize_str(&str) -> String;
serialize_bytes(&[u8]) -> String;
serialize_none() -> String;
serialize_unit() -> String;
serialize_unit_struct(&'static str) -> String;
serialize_unit_variant(&'static str, u32, &'static str) -> String;
serialize_seq(Option<usize>) -> Self::SerializeSeq;
serialize_tuple(usize) -> Self::SerializeTuple;
serialize_tuple_struct(&'static str, usize) -> Self::SerializeTupleStruct;
serialize_tuple_variant(&'static str, u32, &'static str, usize) -> Self::SerializeTupleVariant;
serialize_map(Option<usize>) -> Self::SerializeMap;
serialize_struct(&'static str, usize) -> Self::SerializeStruct;
serialize_struct_variant(&'static str, u32, &'static str, usize) -> Self::SerializeStructVariant;
}
fn serialize_some<T>(self, _: &T) -> Result<String, ValueError>
where
T: ?Sized + Serialize,
{
Err(ValueError::custom("unsupported"))
}
fn serialize_newtype_struct<T>(
self,
_: &'static str,
_: &T,
) -> Result<String, ValueError>
where
T: ?Sized + Serialize,
{
Err(ValueError::custom("unsupported"))
}
fn serialize_newtype_variant<T>(
self,
_: &'static str,
_: u32,
_: &'static str,
_: &T,
) -> Result<String, ValueError>
where
T: ?Sized + Serialize,
{
Err(ValueError::custom("unsupported"))
}
}
fn de(s: &str) -> Result<Amount64, ValueError> {
Amount64::deserialize(StrDeserializer::<ValueError>::new(s))
}
let a = Amount64::from_str_const("1.0001");
assert_eq!(a.serialize(StrCollector), Ok(String::from("1.0001")));
assert_eq!(
Amount64::from_str_const("-0.5").serialize(StrCollector),
Ok(String::from("-0.5"))
);
assert_eq!(
Amount64::ZERO.serialize(StrCollector),
Ok(String::from("0"))
);
assert_eq!(de("1.0001"), Ok(a));
assert_eq!(de("1.00005"), Ok(Decimal::<4>(10001)));
assert_eq!(de("-0.5"), Ok(Decimal::<4>(-5000)));
let r = Rate64::from_str_const("0.12345678");
let s = r.serialize(StrCollector).unwrap();
assert_eq!(s, "0.12345678");
assert_eq!(
Rate64::deserialize(StrDeserializer::<ValueError>::new(&s)),
Ok(r)
);
assert!(de("not-a-number").is_err());
assert!(de("").is_err());
assert!(de("922337203685477.5808").is_err());
let non_str =
Amount64::deserialize(serde::de::value::I32Deserializer::<ValueError>::new(5));
assert!(non_str.is_err());
}
#[cfg(feature = "ufmt")]
#[test]
fn test_ufmt() {
struct StringWriter(std::string::String);
impl ufmt::uWrite for StringWriter {
type Error = core::convert::Infallible;
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
self.0.push_str(s);
Ok(())
}
}
let mut w1 = StringWriter(std::string::String::new());
let val1 = Decimal::<4>(10000);
ufmt::uwrite!(&mut w1, "{}", val1).unwrap();
assert_eq!(w1.0, "1");
let mut w2 = StringWriter(std::string::String::new());
let val2 = Decimal::<4>(-10001);
ufmt::uwrite!(&mut w2, "{}", val2).unwrap();
assert_eq!(w2.0, "-1.0001");
let mut w3 = StringWriter(std::string::String::new());
ufmt::uwrite!(&mut w3, "{:?}", val2).unwrap();
assert_eq!(w3.0, "Decimal(-10001)");
}
}