#![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};
#[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(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(self, rhs: Self, mode: Rounding) -> 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, mode) {
Some((neg, mag)) => Decimal::<DIGITS>(i64_from_sign_mag(neg, mag)),
None => panic!("attempt to multiply with overflow"),
}
}
pub fn div_rounded(self, rhs: Self, mode: Rounding) -> Self {
if rhs.0 == 0 {
panic!("Can't divide by zero");
}
let (an, am) = i64_sign_mag(self.0);
let (bn, bm) = i64_sign_mag(rhs.0);
match limbs::dec_div::<DIGITS, 1, 2>(an, &am, bn, &bm, mode) {
Some((neg, mag)) => Decimal::<DIGITS>(i64_from_sign_mag(neg, mag)),
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_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 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);
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!(
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)
);
}
#[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)");
}
}