#![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(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;
pub const SCALE_INT_100: i64 = Self::SCALE_INT / 100;
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 fn recip(self) -> Self {
Self::ONE.div_rounded(self, Rounding::HalfUp)
}
#[inline]
pub fn trunc(self) -> Self {
Decimal::<DIGITS>(self.0 - self.0 % Self::SCALE_INT)
}
pub fn floor(self) -> Self {
let frac = self.0 % Self::SCALE_INT;
if self.0 < 0 && frac != 0 {
Decimal::<DIGITS>(self.0 - frac - Self::SCALE_INT)
} else {
Decimal::<DIGITS>(self.0 - frac)
}
}
pub fn ceil(self) -> Self {
let mut frac = self.0 % Self::SCALE_INT;
if frac != 0 {
if self.0 < 0 {
frac += Self::SCALE_INT
} else {
frac -= Self::SCALE_INT
}
Decimal::<DIGITS>(self.0 - frac)
} else {
self
}
}
pub fn round(self) -> Self {
let mut frac = self.0 % Self::SCALE_INT;
if frac >= Self::SCALE_INT_HALF {
frac -= Self::SCALE_INT
} else if frac <= -Self::SCALE_INT_HALF {
frac += Self::SCALE_INT
}
Decimal::<DIGITS>(self.0 - frac)
}
pub fn round100(self) -> Self {
let mut frac = self.0 % Self::SCALE_INT_100;
if frac >= Self::SCALE_INT_HALF_100 {
frac -= Self::SCALE_INT_100
} else if frac <= -Self::SCALE_INT_HALF_100 {
frac += Self::SCALE_INT_100
}
Decimal::<DIGITS>(self.0 - frac)
}
pub const fn round_to(self, mode: Rounding) -> Self {
let frac = self.0 % Decimal::<DIGITS>::SCALE_INT;
if frac == 0 {
return 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 mut res = int_part;
if add_one {
res += Decimal::<DIGITS>::SCALE_INT;
}
if sub_one {
res -= Decimal::<DIGITS>::SCALE_INT;
}
Decimal::<DIGITS>(res)
}
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 = if n >> 64 == 0 {
(n as u64).isqrt()
} else {
limbs::isqrt_f64(n)
};
let rem = n - s as u128 * s as u128;
let up = match mode {
Rounding::Down => false,
Rounding::Up => rem != 0,
_ => rem > s as u128,
};
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> {
if c.0 == 0 {
return None;
}
let neg = ((self.0 < 0) != (b.0 < 0)) != (c.0 < 0);
let num = self.0.unsigned_abs() as u128 * b.0.unsigned_abs() as u128;
let d = c.0.unsigned_abs();
let mut mag = [num as u64, (num >> 64) as u64];
let r = limbs::div_words_by_word(&mut mag, d);
if limbs::round_up_by_cmp(
limbs::cmp_twice_rem_u64(r, d),
r == 0,
mag[0] & 1 != 0,
neg,
mode,
) && limbs::mul_add_word(&mut mag, 1, 1)
{
return None;
}
if mag[1] != 0 || mag[0] >> 63 != 0 {
return None;
}
Some(Decimal::<DIGITS>(i64_from_sign_mag(neg, [mag[0]])))
}
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 multiply 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_ne_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 {
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 * Amount64::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 {
Decimal::<DIGITS>(self.0 % rhs.0)
}
}
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>::SCALE_INT_HALF_100, 50);
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 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!(
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))
);
}
#[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:?}"
);
}
}
}
#[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)");
}
}