use crate::{DecimalU64, ScaleMetrics};
use std::iter::Sum;
use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};
impl<S: ScaleMetrics> Mul for DecimalU64<S> {
type Output = DecimalU64<S>;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
let product = self.0 as u128 * rhs.0 as u128;
let scale_factor = S::SCALE_FACTOR as u128;
Self::new((product / scale_factor) as u64)
}
}
impl<S: ScaleMetrics> Add for DecimalU64<S> {
type Output = DecimalU64<S>;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
let sum = self.0 + rhs.0;
Self::new(sum)
}
}
impl<S: ScaleMetrics> Sub for DecimalU64<S> {
type Output = DecimalU64<S>;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
let diff = self.0 - rhs.0;
Self::new(diff)
}
}
impl<S: ScaleMetrics> Div for DecimalU64<S> {
type Output = DecimalU64<S>;
#[inline]
fn div(self, rhs: Self) -> Self::Output {
if rhs.0 == 0 {
panic!("Division by zero");
}
let dividend = self.0 as u128 * S::SCALE_FACTOR as u128;
let quotient = dividend / (rhs.0 as u128);
Self::new(quotient as u64)
}
}
impl<S: ScaleMetrics> AddAssign for DecimalU64<S> {
#[inline]
fn add_assign(&mut self, rhs: DecimalU64<S>) {
self.0 += rhs.0;
}
}
impl<'a, S: ScaleMetrics> AddAssign<&'a DecimalU64<S>> for DecimalU64<S> {
fn add_assign(&mut self, rhs: &'a DecimalU64<S>) {
self.0 += rhs.0;
}
}
impl<S: ScaleMetrics> AddAssign<DecimalU64<S>> for &mut DecimalU64<S> {
#[inline]
fn add_assign(&mut self, rhs: DecimalU64<S>) {
self.0 += rhs.0;
}
}
impl<'a, S: ScaleMetrics> AddAssign<&'a DecimalU64<S>> for &'a mut DecimalU64<S> {
#[inline]
fn add_assign(&mut self, rhs: &'a DecimalU64<S>) {
self.0 += rhs.0;
}
}
impl<S: ScaleMetrics> SubAssign for DecimalU64<S> {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
impl<S: ScaleMetrics> Sum for DecimalU64<S> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let mut sum = Self::ZERO;
for i in iter {
sum += i;
}
sum
}
}
impl<'a, S: ScaleMetrics> Sum<&'a DecimalU64<S>> for DecimalU64<S> {
fn sum<I: Iterator<Item = &'a DecimalU64<S>>>(iter: I) -> Self {
let mut sum = Self::ZERO;
for i in iter {
sum += i;
}
sum
}
}
impl<S: ScaleMetrics> DecimalU64<S> {
#[inline]
pub fn checked_mul(self, other: Self) -> Option<Self> {
let product = (self.0 as u128).checked_mul(other.0 as u128)?;
let scale_factor = S::SCALE_FACTOR as u128;
let result = product / scale_factor;
if result > u64::MAX as u128 {
None
} else {
Some(Self::new(result as u64))
}
}
#[inline]
pub fn checked_add(self, other: Self) -> Option<Self> {
let sum = self.0.checked_add(other.0)?;
Some(Self::new(sum))
}
#[inline]
pub fn checked_sub(self, other: Self) -> Option<Self> {
let diff = self.0.checked_sub(other.0)?;
Some(Self::new(diff))
}
#[inline]
pub fn checked_div(self, other: Self) -> Option<Self> {
if other.0 == 0 {
return None;
}
let dividend = (self.0 as u128).checked_mul(S::SCALE_FACTOR as u128)?;
let quotient = dividend / (other.0 as u128);
if quotient > u64::MAX as u128 {
None
} else {
Some(Self::new(quotient as u64))
}
}
}
#[cfg(test)]
mod tests {
mod mul {
use crate::{DecimalU64, U8};
use rstest_macros::rstest;
#[rstest]
#[case("0.2", "50000", "10000.00000000")]
#[case("1", "1", "1.00000000")]
#[case("0", "123.45", "0.00000000")]
fn should_mul(#[case] a: &str, #[case] b: &str, #[case] expected: &str) {
let dec_a = DecimalU64::<U8>::from_str(a).unwrap();
let dec_b = DecimalU64::<U8>::from_str(b).unwrap();
let result = dec_a.checked_mul(dec_b).unwrap();
assert_eq!(expected, result.to_string());
let result = dec_a * dec_b;
assert_eq!(expected, result.to_string());
}
#[rstest]
#[case("1000000000.00000000", "1000000000.00000000")]
fn should_overflow(#[case] a: &str, #[case] b: &str) {
let dec_a = DecimalU64::<U8>::from_str(a).unwrap();
let dec_b = DecimalU64::<U8>::from_str(b).unwrap();
assert!(dec_a.checked_mul(dec_b).is_none());
}
}
mod add {
use crate::{DecimalU64, U8};
use rstest_macros::rstest;
#[rstest]
#[case("0.2", "50000", "50000.20000000")]
#[case("123.2", "50000", "50123.20000000")]
#[case("0.2", "0", "0.20000000")]
#[case("0", "0", "0.00000000")]
#[case("123.45678901", "0.00000009", "123.45678910")]
fn should_add_success(#[case] a: &str, #[case] b: &str, #[case] expected: &str) {
let dec_a = DecimalU64::<U8>::from_str(a).unwrap();
let dec_b = DecimalU64::<U8>::from_str(b).unwrap();
let result = dec_a.checked_add(dec_b).unwrap();
assert_eq!(expected, result.to_string());
let result = dec_a + dec_b;
assert_eq!(expected, result.to_string());
}
#[test]
fn should_overflow() {
let dec_max = DecimalU64::<U8>::from_str("184467440737.09551615").unwrap();
let dec_small = DecimalU64::<U8>::from_str("0.00000001").unwrap();
assert!(dec_max.checked_add(dec_small).is_none());
}
}
mod sub {
use crate::{DecimalU64, U8};
use rstest_macros::rstest;
#[rstest]
#[case("50000", "0.2", "49999.80000000")]
#[case("50000.02", "0.01", "50000.01000000")]
#[case("123.45678910", "0.00000009", "123.45678901")]
fn should_sub(#[case] a: &str, #[case] b: &str, #[case] expected: &str) {
let dec_a = DecimalU64::<U8>::from_str(a).unwrap();
let dec_b = DecimalU64::<U8>::from_str(b).unwrap();
let result = dec_a.checked_sub(dec_b).unwrap();
assert_eq!(expected, result.to_string());
let result = dec_a - dec_b;
assert_eq!(expected, result.to_string());
}
#[test]
fn should_underflow() {
let dec_zero = DecimalU64::<U8>::from_str("0.00000000").unwrap();
let dec_sub = DecimalU64::<U8>::from_str("0.00000001").unwrap();
assert!(dec_zero.checked_sub(dec_sub).is_none());
}
}
mod div {
use crate::{DecimalU64, U8};
use rstest_macros::rstest;
#[rstest]
#[case("50000", "0.2", "250000.00000000")]
#[case("123.45678901", "2", "61.72839450")]
#[case("0", "123.45678901", "0.00000000")]
#[case("1", "3", "0.33333333")]
#[case("0.129", "0.01", "12.90000000")]
fn should_div(#[case] a: &str, #[case] b: &str, #[case] expected: &str) {
let dec_a = DecimalU64::<U8>::from_str(a).unwrap();
let dec_b = DecimalU64::<U8>::from_str(b).unwrap();
let result = dec_a.checked_div(dec_b).unwrap();
assert_eq!(expected, result.to_string());
let result = dec_a / dec_b;
assert_eq!(expected, result.to_string());
}
#[test]
fn should_not_checked_div_by_zero() {
let dec_a = DecimalU64::<U8>::from_str("123.45678901").unwrap();
let dec_zero = DecimalU64::<U8>::ZERO;
assert!(dec_a.checked_div(dec_zero).is_none());
}
#[test]
#[should_panic = "Division by zero"]
fn should_panic_if_div_by_zero() {
let dec_a = DecimalU64::<U8>::from_str("123.45678901").unwrap();
let dec_zero = DecimalU64::<U8>::ZERO;
let _ = dec_a / dec_zero;
}
#[test]
fn should_overflow() {
let dec_max = DecimalU64::<U8>::from_str("184467440737.09551615").unwrap();
let dec_small = DecimalU64::<U8>::from_str("0.00000001").unwrap();
assert!(dec_max.checked_div(dec_small).is_none());
}
}
mod assign {
use crate::{DecimalU64, U8};
#[test]
fn should_add_and_sub_assign() {
let mut one = DecimalU64::<U8>::from_str("100").unwrap();
let two = DecimalU64::<U8>::from_str("200").unwrap();
one += two;
assert_eq!("300.00000000", one.to_string());
one -= two;
assert_eq!("100.00000000", one.to_string());
}
}
mod sum {
use crate::{DecimalU64, U8};
#[test]
fn should_sum_values() {
let values: Vec<DecimalU64<U8>> = vec![];
let sum = values.iter().sum::<DecimalU64<U8>>();
assert_eq!(sum, DecimalU64::ZERO);
let values: Vec<DecimalU64<U8>> = vec![DecimalU64::ONE, DecimalU64::TWO];
let sum = values.iter().sum::<DecimalU64<U8>>();
assert_eq!(sum, DecimalU64::THREE);
}
}
}