From ead2c207e8bc5360d0aeea7c0256c49f2ec5047d Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 7 Mar 2024 12:48:06 +0100 Subject: [PATCH 01/25] solves merge conflict Decimal: Move whole library to a separate file --- packages/decimal/src/decimal.rs | 926 ++++++++++++++++++++++++++++++ packages/decimal/src/lib.rs | 986 +------------------------------- 2 files changed, 929 insertions(+), 983 deletions(-) create mode 100644 packages/decimal/src/decimal.rs diff --git a/packages/decimal/src/decimal.rs b/packages/decimal/src/decimal.rs new file mode 100644 index 000000000..6f1559804 --- /dev/null +++ b/packages/decimal/src/decimal.rs @@ -0,0 +1,926 @@ +// A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache +// License 2.0 - https://github.com/CosmWasm/cosmwasm. + +use soroban_sdk::{Env, String}; + +use core::{ + cmp::{Ordering, PartialEq, PartialOrd}, + fmt, + ops::{Add, Div, Mul, Sub}, + str::FromStr, +}; + +extern crate alloc; + +#[allow(dead_code)] +#[derive(Debug)] +enum Error { + DivideByZero, +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] +pub struct Decimal(i128); + +#[allow(dead_code)] +impl Decimal { + const DECIMAL_FRACTIONAL: i128 = 1_000_000_000_000_000_000i128; // 1*10**18 + const DECIMAL_FRACTIONAL_SQUARED: i128 = 1_000_000_000_000_000_000_000_000_000_000_000_000i128; // (1*10**18)**2 = 1*10**36 + /// The number of decimal places. Since decimal types are fixed-point rather than + /// floating-point, this is a constant. + pub const DECIMAL_PLACES: i32 = 18; + /// The largest value that can be represented by this decimal type. + pub const MAX: Self = Self(i128::MAX); + /// The smallest value that can be represented by this decimal type. + pub const MIN: Self = Self(i128::MIN); + + pub fn new(value: i128) -> Self { + Decimal(value) + } + + pub const fn raw(value: i128) -> Self { + Self(value) + } + + /// Create a 1.0 Decimal + #[inline] + pub const fn one() -> Self { + Self(Self::DECIMAL_FRACTIONAL) + } + + /// Create a 0.0 Decimal + #[inline] + pub const fn zero() -> Self { + Self(0i128) + } + + /// Convert x% into Decimal + pub fn percent(x: i64) -> Self { + Self((x as i128) * 10_000_000_000_000_000) + } + + /// Convert permille (x/1000) into Decimal + pub fn permille(x: i64) -> Self { + Self((x as i128) * 1_000_000_000_000_000) + } + + /// Convert basis points (x/10000) into Decimal + pub fn bps(x: i64) -> Self { + Self((x as i128) * 100_000_000_000_000) + } + + /// The number of decimal places. This is a constant value for now + /// but this could potentially change as the type evolves. + /// + /// See also [`Decimal::atomics()`]. + #[must_use] + #[inline] + pub const fn decimal_places(&self) -> i32 { + Self::DECIMAL_PLACES + } + + #[inline] + fn numerator(&self) -> i128 { + self.0 + } + + #[inline] + fn denominator(&self) -> i128 { + Self::DECIMAL_FRACTIONAL + } + + #[must_use] + pub const fn is_zero(&self) -> bool { + self.0 == 0i128 + } + + /// A decimal is an integer of atomic units plus a number that specifies the + /// position of the decimal dot. So any decimal can be expressed as two numbers. + /// + /// ## Examples + /// + /// ``` + /// use decimal::Decimal; + /// // Value with whole and fractional part + /// let a = Decimal::percent(123); + /// assert_eq!(a.decimal_places(), 18); + /// assert_eq!(a.atomics(), 1230000000000000000); + /// + /// // Smallest possible value + /// let b = Decimal::new(1); + /// assert_eq!(b.decimal_places(), 18); + /// assert_eq!(b.atomics(), 1); + /// ``` + #[must_use] + #[inline] + pub const fn atomics(&self) -> i128 { + self.0 + } + + /// Creates a decimal from a number of atomic units and the number + /// of decimal places. The inputs will be converted internally to form + /// a decimal with 18 decimal places. So the input 1234 and 3 will create + /// the decimal 1.234. + /// + /// Using 18 decimal places is slightly more efficient than other values + /// as no internal conversion is necessary. + /// + /// ## Examples + /// + /// ``` + /// use decimal::Decimal; + /// use soroban_sdk::{String, Env}; + /// + /// let e = Env::default(); + /// let a = Decimal::from_atomics(1234, 3); + /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1.234")); + /// + /// let a = Decimal::from_atomics(1234, 0); + /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1234")); + /// + /// let a = Decimal::from_atomics(1, 18); + /// assert_eq!(a.to_string(&e), String::from_slice(&e, "0.000000000000000001")); + /// ``` + pub fn from_atomics(atomics: i128, decimal_places: i32) -> Self { + const TEN: i128 = 10; + match decimal_places.cmp(&Self::DECIMAL_PLACES) { + Ordering::Less => { + let digits = Self::DECIMAL_PLACES - decimal_places; + let factor = TEN.pow(digits as u32); + Self(atomics * factor) + } + Ordering::Equal => Self(atomics), + Ordering::Greater => { + let digits = decimal_places - Self::DECIMAL_PLACES; + let factor = TEN.pow(digits as u32); + // Since factor cannot be zero, the division is safe. + Self(atomics / factor) + } + } + } + + /// Raises a value to the power of `exp`, panicking if an overflow occurs. + pub fn pow(self, exp: u32) -> Self { + // This uses the exponentiation by squaring algorithm: + // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method + + fn inner(mut x: Decimal, mut n: u32) -> Decimal { + if n == 0 { + return Decimal::one(); + } + + let mut y = Decimal::one(); + + while n > 1 { + if n % 2 == 0 { + x = x * x; // Regular multiplication + n /= 2; + } else { + y = x * y; // Regular multiplication + x = x * x; // Regular multiplication + n = (n - 1) / 2; + } + } + + x * y + } + + inner(self, exp) + } + + /// Returns the multiplicative inverse `1/d` for decimal `d`. + /// + /// If `d` is zero, none is returned. + pub fn inv(&self) -> Option { + if self.is_zero() { + None + } else { + // Let self be p/q with p = self.0 and q = DECIMAL_FRACTIONAL. + // Now we calculate the inverse a/b = q/p such that b = DECIMAL_FRACTIONAL. Then + // `a = DECIMAL_FRACTIONAL*DECIMAL_FRACTIONAL / self.0`. + Some(Decimal(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) + } + } + + /// Returns the ratio (numerator / denominator) as a Decimal + pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + match Decimal::checked_from_ratio(numerator, denominator) { + Ok(ratio) => ratio, + Err(Error::DivideByZero) => panic!("Denominator must not be zero"), + } + } + + pub fn to_i128_with_precision(&self, precision: impl Into) -> i128 { + let value = self.atomics(); + let precision = precision.into(); + + let divisor = 10i128.pow((self.decimal_places() - precision) as u32); + value / divisor + } + + fn multiply_ratio(&self, numerator: Decimal, denominator: Decimal) -> Decimal { + Decimal::from_ratio(self.atomics() * numerator.atomics(), denominator.atomics()) + } + + /// Returns the ratio (numerator / denominator) as a Decimal + fn checked_from_ratio( + numerator: impl Into, + denominator: impl Into, + ) -> Result { + let numerator = numerator.into(); + let denominator = denominator.into(); + + // If denominator is zero, panic. + if denominator == 0 { + return Err(Error::DivideByZero); + } + + // Convert numerator and denominator to BigInt. + // unwrap since i128 is always convertible to BigInt + // let numerator = numerator.to_bigint().unwrap(); + // let denominator = denominator.to_bigint().unwrap(); + // let decimal_fractional = Self::DECIMAL_FRACTIONAL.to_bigint().unwrap(); + + // Compute the ratio: (numerator * DECIMAL_FRACTIONAL) / denominator + let ratio = (numerator * Self::DECIMAL_FRACTIONAL) / denominator; + + // Convert back to i128. If conversion fails, panic. + // let ratio = ratio.to_i128().ok_or(Error::Overflow)?; + + // Construct and return the Decimal. + Ok(Decimal(ratio)) + } + + pub fn abs(&self) -> Self { + if self.0 < 0 { + Decimal(-self.0) + } else { + *self + } + } + + pub fn to_string(&self, env: &Env) -> String { + String::from_str(env, alloc::format!("{}", self).as_str()) + } + + pub const fn abs_diff(self, other: Self) -> Self { + Self(self.0.abs_diff(other.0) as i128) + } +} + +impl Add for Decimal { + type Output = Self; + + fn add(self, other: Self) -> Self { + Decimal(self.0 + other.0) + } +} +impl Sub for Decimal { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Decimal(self.0 - other.0) + } +} + +impl Mul for Decimal { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn mul(self, other: Self) -> Self { + // Decimals are fractions. We can multiply two decimals a and b + // via + // (a.numerator() * b.numerator()) / (a.denominator() * b.denominator()) + // = (a.numerator() * b.numerator()) / a.denominator() / b.denominator() + + // let self_numerator = self.numerator().to_bigint().unwrap(); + // let other_numerator = other.numerator().to_bigint().unwrap(); + + // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL + let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; + + // Convert the result back to i128, and panic on overflow + // let result = result + // .to_i128() + // .unwrap_or_else(|| panic!("attempt to multiply with overflow")); + + // Return a new Decimal + Decimal(result) + } +} + +impl Div for Decimal { + type Output = Self; + + fn div(self, rhs: Self) -> Self { + match Decimal::checked_from_ratio(self.numerator(), rhs.numerator()) { + Ok(ratio) => ratio, + Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), + } + } +} + +impl Mul for Decimal { + type Output = i128; + + fn mul(self, rhs: i128) -> Self::Output { + rhs * self + } +} + +impl Div for Decimal { + type Output = Self; + + fn div(self, rhs: i128) -> Self::Output { + Decimal(self.0 / rhs) + } +} + +impl Mul for i128 { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn mul(self, rhs: Decimal) -> Self::Output { + // 0*a and b*0 is always 0 + if self == 0i128 || rhs.is_zero() { + return 0i128; + } + self * rhs.0 / Decimal::DECIMAL_FRACTIONAL + } +} + +impl FromStr for Decimal { + type Err = (); + + fn from_str(input: &str) -> Result { + let mut parts_iter = input.split('.'); + + let whole_part = parts_iter.next().expect("Unexpected input format"); + let whole: i128 = whole_part.parse().expect("Error parsing whole"); + let mut atomics = whole * Self::DECIMAL_FRACTIONAL; + + if let Some(fractional_part) = parts_iter.next() { + let fractional: i128 = fractional_part.parse().expect("Error parsing fractional"); + let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; + assert!(exp >= 0, "There must be at least one fractional digit"); + let fractional_factor = 10i128.pow(exp as u32); + atomics += fractional * fractional_factor; + } + + assert!(parts_iter.next().is_none(), "Unexpected number of dots"); + + Ok(Decimal(atomics)) + } +} + +impl fmt::Display for Decimal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let whole = self.0 / Self::DECIMAL_FRACTIONAL; + let fractional = self.0 % Self::DECIMAL_FRACTIONAL; + + if fractional == 0 { + write!(f, "{}", whole) + } else { + let fractional_string = alloc::format!( + "{:0>padding$}", + fractional, + padding = Self::DECIMAL_PLACES as usize + ); + f.write_fmt(format_args!( + "{}.{}", + whole, + fractional_string.trim_end_matches('0') + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + + #[test] + fn decimal_new() { + let expected = 300i128; + assert_eq!(Decimal::new(expected).0, expected); + } + + #[test] + fn decimal_raw() { + let value = 300i128; + assert_eq!(Decimal::raw(value).0, value); + } + + #[test] + fn decimal_one() { + let value = Decimal::one(); + assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL); + } + + #[test] + fn decimal_zero() { + let value = Decimal::zero(); + assert_eq!(value.0, 0i128); + } + + #[test] + fn decimal_percent() { + let value = Decimal::percent(50); + assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL / 2i128); + } + + #[test] + fn decimal_from_atomics_works() { + let one = Decimal::one(); + let two = one + one; + + assert_eq!(Decimal::from_atomics(1i128, 0), one); + assert_eq!(Decimal::from_atomics(10i128, 1), one); + assert_eq!(Decimal::from_atomics(100i128, 2), one); + assert_eq!(Decimal::from_atomics(1000i128, 3), one); + assert_eq!(Decimal::from_atomics(1000000000000000000i128, 18), one); + assert_eq!(Decimal::from_atomics(10000000000000000000i128, 19), one); + assert_eq!(Decimal::from_atomics(100000000000000000000i128, 20), one); + + assert_eq!(Decimal::from_atomics(2i128, 0), two); + assert_eq!(Decimal::from_atomics(20i128, 1), two); + assert_eq!(Decimal::from_atomics(200i128, 2), two); + assert_eq!(Decimal::from_atomics(2000i128, 3), two); + assert_eq!(Decimal::from_atomics(2000000000000000000i128, 18), two); + assert_eq!(Decimal::from_atomics(20000000000000000000i128, 19), two); + assert_eq!(Decimal::from_atomics(200000000000000000000i128, 20), two); + + // Cuts decimal digits (20 provided but only 18 can be stored) + assert_eq!( + Decimal::from_atomics(4321i128, 20), + Decimal::from_str("0.000000000000000043").unwrap() + ); + assert_eq!( + Decimal::from_atomics(6789i128, 20), + Decimal::from_str("0.000000000000000067").unwrap() + ); + assert_eq!( + Decimal::from_atomics(i128::MAX, 38), + Decimal::from_str("1.701411834604692317").unwrap() + ); + assert_eq!( + Decimal::from_atomics(i128::MAX, 39), + Decimal::from_str("0.170141183460469231").unwrap() + ); + assert_eq!( + Decimal::from_atomics(i128::MAX, 45), + Decimal::from_str("0.000000170141183460").unwrap() + ); + assert_eq!( + Decimal::from_atomics(i128::MAX, 51), + Decimal::from_str("0.000000000000170141").unwrap() + ); + assert_eq!( + Decimal::from_atomics(i128::MAX, 56), + Decimal::from_str("0.000000000000000001").unwrap() + ); + } + + #[test] + fn decimal_from_ratio_works() { + // 1.0 + assert_eq!(Decimal::from_ratio(1i128, 1i128), Decimal::one()); + assert_eq!(Decimal::from_ratio(53i128, 53i128), Decimal::one()); + assert_eq!(Decimal::from_ratio(125i128, 125i128), Decimal::one()); + + // 1.5 + assert_eq!(Decimal::from_ratio(3i128, 2i128), Decimal::percent(150)); + assert_eq!(Decimal::from_ratio(150i128, 100i128), Decimal::percent(150)); + assert_eq!(Decimal::from_ratio(333i128, 222i128), Decimal::percent(150)); + + // 0.125 + assert_eq!(Decimal::from_ratio(1i64, 8i64), Decimal::permille(125)); + assert_eq!(Decimal::from_ratio(125i64, 1000i64), Decimal::permille(125)); + + // 1/3 (result floored) + assert_eq!( + Decimal::from_ratio(1i128, 3i128), + Decimal(333_333_333_333_333_333i128) + ); + + // 2/3 (result floored) + assert_eq!( + Decimal::from_ratio(2i128, 3i128), + Decimal(666_666_666_666_666_666i128) + ); + + // large inputs + assert_eq!(Decimal::from_ratio(0i128, i128::MAX), Decimal::zero()); + // assert_eq!(Decimal::from_ratio(i128::MAX, i128::MAX), Decimal::one()); + + // due to limited possibilities - we're only allowed to use i128 as input - maximum + // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL + // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. + assert_eq!( + Decimal::from_ratio(340282366920938i128, 340282366920938i128), + Decimal::one() + ); + // This works because of similar orders of magnitude + assert_eq!( + Decimal::from_ratio(34028236692093900000i128, 34028236692093900000i128), + Decimal::one() + ); + assert_eq!( + Decimal::from_ratio(34028236692093900000i128, 1i128), + Decimal::new(34028236692093900000i128 * Decimal::DECIMAL_FRACTIONAL) + ); + } + + #[test] + #[should_panic(expected = "Denominator must not be zero")] + fn decimal_from_ratio_panics_for_zero_denominator() { + Decimal::from_ratio(1i128, 0i128); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_from_ratio_panics_for_mul_overflow() { + Decimal::from_ratio(i128::MAX, 1i128); + } + + #[test] + fn decimal_decimal_places_works() { + let zero = Decimal::zero(); + let one = Decimal::one(); + let half = Decimal::percent(50); + let two = Decimal::percent(200); + let max = Decimal::MAX; + + assert_eq!(zero.decimal_places(), 18); + assert_eq!(one.decimal_places(), 18); + assert_eq!(half.decimal_places(), 18); + assert_eq!(two.decimal_places(), 18); + assert_eq!(max.decimal_places(), 18); + } + + #[test] + fn decimal_from_str_works() { + // Integers + assert_eq!(Decimal::from_str("0").unwrap(), Decimal::percent(0)); + assert_eq!(Decimal::from_str("1").unwrap(), Decimal::percent(100)); + assert_eq!(Decimal::from_str("5").unwrap(), Decimal::percent(500)); + assert_eq!(Decimal::from_str("42").unwrap(), Decimal::percent(4200)); + assert_eq!(Decimal::from_str("000").unwrap(), Decimal::percent(0)); + assert_eq!(Decimal::from_str("001").unwrap(), Decimal::percent(100)); + assert_eq!(Decimal::from_str("005").unwrap(), Decimal::percent(500)); + assert_eq!(Decimal::from_str("0042").unwrap(), Decimal::percent(4200)); + + // Decimals + assert_eq!(Decimal::from_str("1.0").unwrap(), Decimal::percent(100)); + assert_eq!(Decimal::from_str("1.5").unwrap(), Decimal::percent(150)); + assert_eq!(Decimal::from_str("0.5").unwrap(), Decimal::percent(50)); + assert_eq!(Decimal::from_str("0.123").unwrap(), Decimal::permille(123)); + + assert_eq!(Decimal::from_str("40.00").unwrap(), Decimal::percent(4000)); + assert_eq!(Decimal::from_str("04.00").unwrap(), Decimal::percent(400)); + assert_eq!(Decimal::from_str("00.40").unwrap(), Decimal::percent(40)); + assert_eq!(Decimal::from_str("00.04").unwrap(), Decimal::percent(4)); + + // Can handle DECIMAL_PLACES fractional digits + assert_eq!( + Decimal::from_str("7.123456789012345678").unwrap(), + Decimal(7123456789012345678i128) + ); + assert_eq!( + Decimal::from_str("7.999999999999999999").unwrap(), + Decimal(7999999999999999999i128) + ); + } + + #[test] + fn decimal_is_zero_works() { + assert!(Decimal::zero().is_zero()); + assert!(Decimal::percent(0).is_zero()); + assert!(Decimal::permille(0).is_zero()); + + assert!(!Decimal::one().is_zero()); + assert!(!Decimal::percent(123).is_zero()); + assert!(!Decimal::permille(1234).is_zero()); + } + + #[test] + fn decimal_inv_works() { + // d = 0 + assert_eq!(Decimal::zero().inv(), None); + + // d == 1 + assert_eq!(Decimal::one().inv(), Some(Decimal::one())); + + // d > 1 exact + assert_eq!(Decimal::percent(200).inv(), Some(Decimal::percent(50))); + assert_eq!(Decimal::percent(2_000).inv(), Some(Decimal::percent(5))); + assert_eq!(Decimal::percent(20_000).inv(), Some(Decimal::permille(5))); + assert_eq!(Decimal::percent(200_000).inv(), Some(Decimal::bps(5))); + + // d > 1 rounded + assert_eq!( + Decimal::percent(300).inv(), + Some(Decimal::from_ratio(1i128, 3i128)) + ); + assert_eq!( + Decimal::percent(600).inv(), + Some(Decimal::from_ratio(1i128, 6i128)) + ); + + // d < 1 exact + assert_eq!(Decimal::percent(50).inv(), Some(Decimal::percent(200))); + assert_eq!(Decimal::percent(5).inv(), Some(Decimal::percent(2_000))); + assert_eq!(Decimal::permille(5).inv(), Some(Decimal::percent(20_000))); + assert_eq!(Decimal::bps(5).inv(), Some(Decimal::percent(200_000))); + } + + #[test] + fn decimal_add_works() { + let value = Decimal::one() + Decimal::percent(50); // 1.5 + assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL * 3i128 / 2i128); + + assert_eq!( + Decimal::percent(5) + Decimal::percent(4), + Decimal::percent(9) + ); + assert_eq!(Decimal::percent(5) + Decimal::zero(), Decimal::percent(5)); + assert_eq!(Decimal::zero() + Decimal::zero(), Decimal::zero()); + } + + #[test] + #[should_panic(expected = "attempt to add with overflow")] + fn decimal_add_overflow_panics() { + let _value = Decimal::MAX + Decimal::percent(50); + } + + #[test] + fn decimal_sub_works() { + let value = Decimal::one() - Decimal::percent(50); // 0.5 + assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL / 2i128); + + assert_eq!( + Decimal::percent(9) - Decimal::percent(4), + Decimal::percent(5) + ); + assert_eq!(Decimal::percent(16) - Decimal::zero(), Decimal::percent(16)); + assert_eq!(Decimal::percent(16) - Decimal::percent(16), Decimal::zero()); + assert_eq!(Decimal::zero() - Decimal::zero(), Decimal::zero()); + } + + #[test] + fn decimal_implements_mul() { + let one = Decimal::one(); + let two = one + one; + let half = Decimal::percent(50); + + // 1*x and x*1 + assert_eq!(one * Decimal::percent(0), Decimal::percent(0)); + assert_eq!(one * Decimal::percent(1), Decimal::percent(1)); + assert_eq!(one * Decimal::percent(10), Decimal::percent(10)); + assert_eq!(one * Decimal::percent(100), Decimal::percent(100)); + assert_eq!(one * Decimal::percent(1000), Decimal::percent(1000)); + // assert_eq!(one * Decimal::MAX, Decimal::MAX); + assert_eq!(Decimal::percent(0) * one, Decimal::percent(0)); + assert_eq!(Decimal::percent(1) * one, Decimal::percent(1)); + assert_eq!(Decimal::percent(10) * one, Decimal::percent(10)); + assert_eq!(Decimal::percent(100) * one, Decimal::percent(100)); + assert_eq!(Decimal::percent(1000) * one, Decimal::percent(1000)); + // assert_eq!(Decimal::MAX * one, Decimal::MAX); + + // double + assert_eq!(two * Decimal::percent(0), Decimal::percent(0)); + assert_eq!(two * Decimal::percent(1), Decimal::percent(2)); + assert_eq!(two * Decimal::percent(10), Decimal::percent(20)); + assert_eq!(two * Decimal::percent(100), Decimal::percent(200)); + assert_eq!(two * Decimal::percent(1000), Decimal::percent(2000)); + assert_eq!(Decimal::percent(0) * two, Decimal::percent(0)); + assert_eq!(Decimal::percent(1) * two, Decimal::percent(2)); + assert_eq!(Decimal::percent(10) * two, Decimal::percent(20)); + assert_eq!(Decimal::percent(100) * two, Decimal::percent(200)); + assert_eq!(Decimal::percent(1000) * two, Decimal::percent(2000)); + + // half + assert_eq!(half * Decimal::percent(0), Decimal::percent(0)); + assert_eq!(half * Decimal::percent(1), Decimal::permille(5)); + assert_eq!(half * Decimal::percent(10), Decimal::percent(5)); + assert_eq!(half * Decimal::percent(100), Decimal::percent(50)); + assert_eq!(half * Decimal::percent(1000), Decimal::percent(500)); + assert_eq!(Decimal::percent(0) * half, Decimal::percent(0)); + assert_eq!(Decimal::percent(1) * half, Decimal::permille(5)); + assert_eq!(Decimal::percent(10) * half, Decimal::percent(5)); + assert_eq!(Decimal::percent(100) * half, Decimal::percent(50)); + assert_eq!(Decimal::percent(1000) * half, Decimal::percent(500)); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_mul_overflow_panics() { + let _value = Decimal::MAX * Decimal::percent(101); + } + + #[test] + // in this test the Decimal is on the right + fn i128_decimal_multiply() { + // a*b + let left = 300i128; + let right = Decimal::one() + Decimal::percent(50); // 1.5 + assert_eq!(left * right, 450i128); + + // a*0 + let left = 300i128; + let right = Decimal::zero(); + assert_eq!(left * right, 0i128); + + // 0*a + let left = 0i128; + let right = Decimal::one() + Decimal::percent(50); // 1.5 + assert_eq!(left * right, 0i128); + + assert_eq!(0i128 * Decimal::one(), 0i128); + assert_eq!(1i128 * Decimal::one(), 1i128); + assert_eq!(2i128 * Decimal::one(), 2i128); + + assert_eq!(1i128 * Decimal::percent(10), 0i128); + assert_eq!(10i128 * Decimal::percent(10), 1i128); + assert_eq!(100i128 * Decimal::percent(10), 10i128); + + assert_eq!(1i128 * Decimal::percent(50), 0i128); + assert_eq!(100i128 * Decimal::percent(50), 50i128); + assert_eq!(3200i128 * Decimal::percent(50), 1600i128); + assert_eq!(999i128 * Decimal::percent(50), 499i128); // default rounding down + + assert_eq!(1i128 * Decimal::percent(200), 2i128); + assert_eq!(1000i128 * Decimal::percent(200), 2000i128); + } + + #[test] + // in this test the Decimal is on the left + fn decimal_i128_multiply() { + // a*b + let left = Decimal::one() + Decimal::percent(50); // 1.5 + let right = 300i128; + assert_eq!(left * right, 450i128); + + // 0*a + let left = Decimal::zero(); + let right = 300i128; + assert_eq!(left * right, 0i128); + + // a*0 + let left = Decimal::one() + Decimal::percent(50); // 1.5 + let right = 0i128; + assert_eq!(left * right, 0i128); + } + + #[test] + fn decimal_implements_div() { + let one = Decimal::one(); + let two = one + one; + let half = Decimal::percent(50); + + // 1/x and x/1 + assert_eq!(one / Decimal::percent(1), Decimal::percent(10_000)); + assert_eq!(one / Decimal::percent(10), Decimal::percent(1_000)); + assert_eq!(one / Decimal::percent(100), Decimal::percent(100)); + assert_eq!(one / Decimal::percent(1000), Decimal::percent(10)); + assert_eq!(Decimal::percent(0) / one, Decimal::percent(0)); + assert_eq!(Decimal::percent(1) / one, Decimal::percent(1)); + assert_eq!(Decimal::percent(10) / one, Decimal::percent(10)); + assert_eq!(Decimal::percent(100) / one, Decimal::percent(100)); + assert_eq!(Decimal::percent(1000) / one, Decimal::percent(1000)); + + // double + assert_eq!(two / Decimal::percent(1), Decimal::percent(20_000)); + assert_eq!(two / Decimal::percent(10), Decimal::percent(2_000)); + assert_eq!(two / Decimal::percent(100), Decimal::percent(200)); + assert_eq!(two / Decimal::percent(1000), Decimal::percent(20)); + assert_eq!(Decimal::percent(0) / two, Decimal::percent(0)); + assert_eq!(Decimal::percent(10) / two, Decimal::percent(5)); + assert_eq!(Decimal::percent(100) / two, Decimal::percent(50)); + assert_eq!(Decimal::percent(1000) / two, Decimal::percent(500)); + + // half + assert_eq!(half / Decimal::percent(1), Decimal::percent(5_000)); + assert_eq!(half / Decimal::percent(10), Decimal::percent(500)); + assert_eq!(half / Decimal::percent(100), Decimal::percent(50)); + assert_eq!(half / Decimal::percent(1000), Decimal::percent(5)); + assert_eq!(Decimal::percent(0) / half, Decimal::percent(0)); + assert_eq!(Decimal::percent(1) / half, Decimal::percent(2)); + assert_eq!(Decimal::percent(10) / half, Decimal::percent(20)); + assert_eq!(Decimal::percent(100) / half, Decimal::percent(200)); + assert_eq!(Decimal::percent(1000) / half, Decimal::percent(2000)); + + assert_eq!( + Decimal::percent(15) / Decimal::percent(60), + Decimal::percent(25) + ); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_div_overflow_panics() { + let _value = Decimal::MAX / Decimal::percent(10); + } + + #[test] + #[should_panic(expected = "Division failed - denominator must not be zero")] + fn decimal_div_by_zero_panics() { + let _value = Decimal::one() / Decimal::zero(); + } + + #[test] + fn decimal_i128_division() { + // a/b + let left = Decimal::percent(150); // 1.5 + let right = 3i128; + assert_eq!(left / right, Decimal::percent(50)); + + // 0/a + let left = Decimal::zero(); + let right = 300i128; + assert_eq!(left / right, Decimal::zero()); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn decimal_uint128_divide_by_zero() { + let left = Decimal::percent(150); // 1.5 + let right = 0i128; + let _result = left / right; + } + + #[test] + fn decimal_pow_works() { + assert_eq!(Decimal::percent(200).pow(2), Decimal::percent(400)); + assert_eq!(Decimal::percent(100).pow(10), Decimal::percent(100)); + } + + #[test] + #[should_panic] + fn decimal_pow_overflow_panics() { + _ = Decimal::MAX.pow(2u32); + } + + #[test] + fn decimal_abs_with_negative_number() { + let decimal = Decimal::new(128); + + assert_eq!(decimal.abs(), Decimal(128)); + } + + #[test] + fn decimal_abs_with_positive_number() { + let decimal = Decimal::new(128); + + assert_eq!(decimal.abs(), Decimal(128)); + } + + #[test] + fn decimal_displayed_as_string() { + let env = Env::default(); + let decimal = Decimal::percent(128); + + // Convert expected string to Soroban SDK String + let expected_msg = "1.28"; + let expected_string = String::from_str(&env, expected_msg); + + // Convert decimal to String and get its byte representation + let result_string = decimal.to_string(&env); + let result_string_len = result_string.len() as usize; + let mut result_bytes = alloc::vec![0u8; result_string_len]; + result_string.copy_into_slice(&mut result_bytes); + + // Get byte representation of expected string + let expected_string_len = expected_string.len() as usize; + let mut expected_bytes = alloc::vec![0u8; expected_string_len]; + expected_string.copy_into_slice(&mut expected_bytes); + + assert_eq!(result_bytes, expected_bytes); + } + + #[test] + fn decimal_fmt_without_fractional_part() { + let value = Decimal::from_atomics(100, 0); + assert_eq!(format!("{}", value), "100"); + } + + #[test] + fn decimal_fmt_fractional_part() { + let value = Decimal::from_atomics(123456789, 5); + assert_eq!(format!("{}", value), "1234.56789"); + } + + #[test] + fn decimal_fmt_fractional_part_with_trailing_zeros() { + // 12345.6 + let value = Decimal::from_atomics(123456, 1); + assert_eq!(format!("{}", value), "12345.6"); + } + + #[test] + fn decimal_fmt_only_fractional() { + // 0.0789 + let value = Decimal::from_atomics(789, 4); + assert_eq!(format!("{}", value), "0.0789"); + } +} diff --git a/packages/decimal/src/lib.rs b/packages/decimal/src/lib.rs index 2e297e7b8..c866e4471 100644 --- a/packages/decimal/src/lib.rs +++ b/packages/decimal/src/lib.rs @@ -1,986 +1,6 @@ -// A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache -// License 2.0 - https://github.com/CosmWasm/cosmwasm. - #![no_std] -use soroban_sdk::{Env, String}; - -use core::{ - cmp::{Ordering, PartialEq, PartialOrd}, - fmt, - ops::{Add, Div, Mul, Sub}, - str::FromStr, -}; - -extern crate alloc; - -#[allow(dead_code)] -#[derive(Debug)] -enum Error { - DivideByZero, -} - -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] -pub struct Decimal(i128); - -#[allow(dead_code)] -impl Decimal { - const DECIMAL_FRACTIONAL: i128 = 1_000_000_000_000_000_000i128; // 1*10**18 - const DECIMAL_FRACTIONAL_SQUARED: i128 = 1_000_000_000_000_000_000_000_000_000_000_000_000i128; // (1*10**18)**2 = 1*10**36 - /// The number of decimal places. Since decimal types are fixed-point rather than - /// floating-point, this is a constant. - pub const DECIMAL_PLACES: i32 = 18; - /// The largest value that can be represented by this decimal type. - pub const MAX: Self = Self(i128::MAX); - /// The smallest value that can be represented by this decimal type. - pub const MIN: Self = Self(i128::MIN); - - pub fn new(value: i128) -> Self { - Decimal(value) - } - - pub const fn raw(value: i128) -> Self { - Self(value) - } - - /// Create a 1.0 Decimal - #[inline] - pub const fn one() -> Self { - Self(Self::DECIMAL_FRACTIONAL) - } - - /// Create a 0.0 Decimal - #[inline] - pub const fn zero() -> Self { - Self(0i128) - } - - /// Convert x% into Decimal - pub fn percent(x: i64) -> Self { - Self((x as i128) * 10_000_000_000_000_000) - } - - /// Convert permille (x/1000) into Decimal - pub fn permille(x: i64) -> Self { - Self((x as i128) * 1_000_000_000_000_000) - } - - /// Convert basis points (x/10000) into Decimal - pub fn bps(x: i64) -> Self { - Self((x as i128) * 100_000_000_000_000) - } - - /// The number of decimal places. This is a constant value for now - /// but this could potentially change as the type evolves. - /// - /// See also [`Decimal::atomics()`]. - #[must_use] - #[inline] - pub const fn decimal_places(&self) -> i32 { - Self::DECIMAL_PLACES - } - - #[inline] - fn numerator(&self) -> i128 { - self.0 - } - - #[inline] - fn denominator(&self) -> i128 { - Self::DECIMAL_FRACTIONAL - } - - #[must_use] - pub const fn is_zero(&self) -> bool { - self.0 == 0i128 - } - - /// A decimal is an integer of atomic units plus a number that specifies the - /// position of the decimal dot. So any decimal can be expressed as two numbers. - /// - /// ## Examples - /// - /// ``` - /// use soroban_decimal::Decimal; - /// // Value with whole and fractional part - /// let a = Decimal::percent(123); - /// assert_eq!(a.decimal_places(), 18); - /// assert_eq!(a.atomics(), 1230000000000000000); - /// - /// // Smallest possible value - /// let b = Decimal::new(1); - /// assert_eq!(b.decimal_places(), 18); - /// assert_eq!(b.atomics(), 1); - /// ``` - #[must_use] - #[inline] - pub const fn atomics(&self) -> i128 { - self.0 - } - - /// Creates a decimal from a number of atomic units and the number - /// of decimal places. The inputs will be converted internally to form - /// a decimal with 18 decimal places. So the input 1234 and 3 will create - /// the decimal 1.234. - /// - /// Using 18 decimal places is slightly more efficient than other values - /// as no internal conversion is necessary. - /// - /// ## Examples - /// - /// ``` - /// use soroban_decimal::Decimal; - /// use soroban_sdk::{String, Env}; - /// - /// let e = Env::default(); - /// let a = Decimal::from_atomics(1234, 3); - /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1.234")); - /// - /// let a = Decimal::from_atomics(1234, 0); - /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1234")); - /// - /// let a = Decimal::from_atomics(1, 18); - /// assert_eq!(a.to_string(&e), String::from_slice(&e, "0.000000000000000001")); - /// ``` - pub fn from_atomics(atomics: i128, decimal_places: i32) -> Self { - const TEN: i128 = 10; - match decimal_places.cmp(&Self::DECIMAL_PLACES) { - Ordering::Less => { - let digits = Self::DECIMAL_PLACES - decimal_places; - let factor = TEN.pow(digits as u32); - Self(atomics * factor) - } - Ordering::Equal => Self(atomics), - Ordering::Greater => { - let digits = decimal_places - Self::DECIMAL_PLACES; - let factor = TEN.pow(digits as u32); - // Since factor cannot be zero, the division is safe. - Self(atomics / factor) - } - } - } - - /// Raises a value to the power of `exp`, panicking if an overflow occurs. - pub fn pow(self, exp: u32) -> Self { - // This uses the exponentiation by squaring algorithm: - // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method - - fn inner(mut x: Decimal, mut n: u32) -> Decimal { - if n == 0 { - return Decimal::one(); - } - - let mut y = Decimal::one(); - - while n > 1 { - if n % 2 == 0 { - x = x * x; // Regular multiplication - n /= 2; - } else { - y = x * y; // Regular multiplication - x = x * x; // Regular multiplication - n = (n - 1) / 2; - } - } - - x * y - } - - inner(self, exp) - } - - /// Returns the multiplicative inverse `1/d` for decimal `d`. - /// - /// If `d` is zero, none is returned. - pub fn inv(&self) -> Option { - if self.is_zero() { - None - } else { - // Let self be p/q with p = self.0 and q = DECIMAL_FRACTIONAL. - // Now we calculate the inverse a/b = q/p such that b = DECIMAL_FRACTIONAL. Then - // `a = DECIMAL_FRACTIONAL*DECIMAL_FRACTIONAL / self.0`. - Some(Decimal(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) - } - } - - /// Returns the ratio (numerator / denominator) as a Decimal - pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { - match Decimal::checked_from_ratio(numerator, denominator) { - Ok(ratio) => ratio, - Err(Error::DivideByZero) => panic!("Denominator must not be zero"), - } - } - - pub fn to_i128_with_precision(&self, precision: impl Into) -> i128 { - let value = self.atomics(); - let precision = precision.into(); - - let divisor = 10i128.pow((self.decimal_places() - precision) as u32); - value / divisor - } - - fn multiply_ratio(&self, numerator: Decimal, denominator: Decimal) -> Decimal { - Decimal::from_ratio(self.atomics() * numerator.atomics(), denominator.atomics()) - } - - /// Returns the ratio (numerator / denominator) as a Decimal - fn checked_from_ratio( - numerator: impl Into, - denominator: impl Into, - ) -> Result { - let numerator = numerator.into(); - let denominator = denominator.into(); - - // If denominator is zero, panic. - if denominator == 0 { - return Err(Error::DivideByZero); - } - - // Convert numerator and denominator to BigInt. - // unwrap since i128 is always convertible to BigInt - // let numerator = numerator.to_bigint().unwrap(); - // let denominator = denominator.to_bigint().unwrap(); - // let decimal_fractional = Self::DECIMAL_FRACTIONAL.to_bigint().unwrap(); - - // Compute the ratio: (numerator * DECIMAL_FRACTIONAL) / denominator - let ratio = (numerator * Self::DECIMAL_FRACTIONAL) / denominator; - - // Convert back to i128. If conversion fails, panic. - // let ratio = ratio.to_i128().ok_or(Error::Overflow)?; - - // Construct and return the Decimal. - Ok(Decimal(ratio)) - } - - pub fn abs(&self) -> Self { - if self.0 < 0 { - Decimal(-self.0) - } else { - *self - } - } - - pub fn to_string(&self, env: &Env) -> String { - String::from_str(env, alloc::format!("{}", self).as_str()) - } - - pub const fn abs_diff(self, other: Self) -> Self { - Self(self.0.abs_diff(other.0) as i128) - } -} - -impl Add for Decimal { - type Output = Self; - - fn add(self, other: Self) -> Self { - Decimal(self.0 + other.0) - } -} -impl Sub for Decimal { - type Output = Self; - - fn sub(self, other: Self) -> Self { - Decimal(self.0 - other.0) - } -} - -impl Mul for Decimal { - type Output = Self; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, other: Self) -> Self { - // Decimals are fractions. We can multiply two decimals a and b - // via - // (a.numerator() * b.numerator()) / (a.denominator() * b.denominator()) - // = (a.numerator() * b.numerator()) / a.denominator() / b.denominator() - - // let self_numerator = self.numerator().to_bigint().unwrap(); - // let other_numerator = other.numerator().to_bigint().unwrap(); - - // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL - let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; - - // Convert the result back to i128, and panic on overflow - // let result = result - // .to_i128() - // .unwrap_or_else(|| panic!("attempt to multiply with overflow")); - - // Return a new Decimal - Decimal(result) - } -} - -impl Div for Decimal { - type Output = Self; - - fn div(self, rhs: Self) -> Self { - match Decimal::checked_from_ratio(self.numerator(), rhs.numerator()) { - Ok(ratio) => ratio, - Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), - } - } -} - -impl Mul for Decimal { - type Output = i128; - - fn mul(self, rhs: i128) -> Self::Output { - rhs * self - } -} - -impl Div for Decimal { - type Output = Self; - - fn div(self, rhs: i128) -> Self::Output { - Decimal(self.0 / rhs) - } -} - -impl Mul for i128 { - type Output = Self; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, rhs: Decimal) -> Self::Output { - // 0*a and b*0 is always 0 - if self == 0i128 || rhs.is_zero() { - return 0i128; - } - self * rhs.0 / Decimal::DECIMAL_FRACTIONAL - } -} - -impl FromStr for Decimal { - type Err = (); - - fn from_str(input: &str) -> Result { - let mut parts_iter = input.split('.'); - - let whole_part = parts_iter.next().expect("Unexpected input format"); - let whole: i128 = whole_part.parse().expect("Error parsing whole"); - let mut atomics = whole * Self::DECIMAL_FRACTIONAL; - - if let Some(fractional_part) = parts_iter.next() { - let fractional: i128 = fractional_part.parse().expect("Error parsing fractional"); - let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; - assert!(exp >= 0, "There must be at least one fractional digit"); - let fractional_factor = 10i128.pow(exp as u32); - atomics += fractional * fractional_factor; - } - - assert!(parts_iter.next().is_none(), "Unexpected number of dots"); - - Ok(Decimal(atomics)) - } -} - -impl fmt::Display for Decimal { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let whole = self.0 / Self::DECIMAL_FRACTIONAL; - let fractional = self.0 % Self::DECIMAL_FRACTIONAL; - - if fractional == 0 { - write!(f, "{}", whole) - } else { - let fractional_string = alloc::format!( - "{:0>padding$}", - fractional, - padding = Self::DECIMAL_PLACES as usize - ); - f.write_fmt(format_args!( - "{}.{}", - whole, - fractional_string.trim_end_matches('0') - )) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::format; - - #[test] - fn decimal_new() { - let expected = 300i128; - assert_eq!(Decimal::new(expected).0, expected); - } - - #[test] - fn decimal_raw() { - let value = 300i128; - assert_eq!(Decimal::raw(value).0, value); - } - - #[test] - fn decimal_one() { - let value = Decimal::one(); - assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL); - } - - #[test] - fn decimal_zero() { - let value = Decimal::zero(); - assert_eq!(value.0, 0i128); - } - - #[test] - fn decimal_percent() { - let value = Decimal::percent(50); - assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL / 2i128); - } - - #[test] - fn decimal_from_atomics_works() { - let one = Decimal::one(); - let two = one + one; - - assert_eq!(Decimal::from_atomics(1i128, 0), one); - assert_eq!(Decimal::from_atomics(10i128, 1), one); - assert_eq!(Decimal::from_atomics(100i128, 2), one); - assert_eq!(Decimal::from_atomics(1000i128, 3), one); - assert_eq!(Decimal::from_atomics(1000000000000000000i128, 18), one); - assert_eq!(Decimal::from_atomics(10000000000000000000i128, 19), one); - assert_eq!(Decimal::from_atomics(100000000000000000000i128, 20), one); - - assert_eq!(Decimal::from_atomics(2i128, 0), two); - assert_eq!(Decimal::from_atomics(20i128, 1), two); - assert_eq!(Decimal::from_atomics(200i128, 2), two); - assert_eq!(Decimal::from_atomics(2000i128, 3), two); - assert_eq!(Decimal::from_atomics(2000000000000000000i128, 18), two); - assert_eq!(Decimal::from_atomics(20000000000000000000i128, 19), two); - assert_eq!(Decimal::from_atomics(200000000000000000000i128, 20), two); - - // Cuts decimal digits (20 provided but only 18 can be stored) - assert_eq!( - Decimal::from_atomics(4321i128, 20), - Decimal::from_str("0.000000000000000043").unwrap() - ); - assert_eq!( - Decimal::from_atomics(6789i128, 20), - Decimal::from_str("0.000000000000000067").unwrap() - ); - assert_eq!( - Decimal::from_atomics(i128::MAX, 38), - Decimal::from_str("1.701411834604692317").unwrap() - ); - assert_eq!( - Decimal::from_atomics(i128::MAX, 39), - Decimal::from_str("0.170141183460469231").unwrap() - ); - assert_eq!( - Decimal::from_atomics(i128::MAX, 45), - Decimal::from_str("0.000000170141183460").unwrap() - ); - assert_eq!( - Decimal::from_atomics(i128::MAX, 51), - Decimal::from_str("0.000000000000170141").unwrap() - ); - assert_eq!( - Decimal::from_atomics(i128::MAX, 56), - Decimal::from_str("0.000000000000000001").unwrap() - ); - } - - #[test] - fn decimal_from_ratio_works() { - // 1.0 - assert_eq!(Decimal::from_ratio(1i128, 1i128), Decimal::one()); - assert_eq!(Decimal::from_ratio(53i128, 53i128), Decimal::one()); - assert_eq!(Decimal::from_ratio(125i128, 125i128), Decimal::one()); - - // 1.5 - assert_eq!(Decimal::from_ratio(3i128, 2i128), Decimal::percent(150)); - assert_eq!(Decimal::from_ratio(150i128, 100i128), Decimal::percent(150)); - assert_eq!(Decimal::from_ratio(333i128, 222i128), Decimal::percent(150)); - - // 0.125 - assert_eq!(Decimal::from_ratio(1i64, 8i64), Decimal::permille(125)); - assert_eq!(Decimal::from_ratio(125i64, 1000i64), Decimal::permille(125)); - - // 1/3 (result floored) - assert_eq!( - Decimal::from_ratio(1i128, 3i128), - Decimal(333_333_333_333_333_333i128) - ); - - // 2/3 (result floored) - assert_eq!( - Decimal::from_ratio(2i128, 3i128), - Decimal(666_666_666_666_666_666i128) - ); - - // large inputs - assert_eq!(Decimal::from_ratio(0i128, i128::MAX), Decimal::zero()); - // assert_eq!(Decimal::from_ratio(i128::MAX, i128::MAX), Decimal::one()); - - // due to limited possibilities - we're only allowed to use i128 as input - maximum - // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL - // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. - assert_eq!( - Decimal::from_ratio(340282366920938i128, 340282366920938i128), - Decimal::one() - ); - // This works because of similar orders of magnitude - assert_eq!( - Decimal::from_ratio(34028236692093900000i128, 34028236692093900000i128), - Decimal::one() - ); - assert_eq!( - Decimal::from_ratio(34028236692093900000i128, 1i128), - Decimal::new(34028236692093900000i128 * Decimal::DECIMAL_FRACTIONAL) - ); - } - - #[test] - #[should_panic(expected = "Denominator must not be zero")] - fn decimal_from_ratio_panics_for_zero_denominator() { - Decimal::from_ratio(1i128, 0i128); - } - - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_from_ratio_panics_for_mul_overflow() { - Decimal::from_ratio(i128::MAX, 1i128); - } - - #[test] - fn decimal_decimal_places_works() { - let zero = Decimal::zero(); - let one = Decimal::one(); - let half = Decimal::percent(50); - let two = Decimal::percent(200); - let max = Decimal::MAX; - - assert_eq!(zero.decimal_places(), 18); - assert_eq!(one.decimal_places(), 18); - assert_eq!(half.decimal_places(), 18); - assert_eq!(two.decimal_places(), 18); - assert_eq!(max.decimal_places(), 18); - } - - #[test] - fn decimal_from_str_works() { - // Integers - assert_eq!(Decimal::from_str("0").unwrap(), Decimal::percent(0)); - assert_eq!(Decimal::from_str("1").unwrap(), Decimal::percent(100)); - assert_eq!(Decimal::from_str("5").unwrap(), Decimal::percent(500)); - assert_eq!(Decimal::from_str("42").unwrap(), Decimal::percent(4200)); - assert_eq!(Decimal::from_str("000").unwrap(), Decimal::percent(0)); - assert_eq!(Decimal::from_str("001").unwrap(), Decimal::percent(100)); - assert_eq!(Decimal::from_str("005").unwrap(), Decimal::percent(500)); - assert_eq!(Decimal::from_str("0042").unwrap(), Decimal::percent(4200)); - - // Decimals - assert_eq!(Decimal::from_str("1.0").unwrap(), Decimal::percent(100)); - assert_eq!(Decimal::from_str("1.5").unwrap(), Decimal::percent(150)); - assert_eq!(Decimal::from_str("0.5").unwrap(), Decimal::percent(50)); - assert_eq!(Decimal::from_str("0.123").unwrap(), Decimal::permille(123)); - - assert_eq!(Decimal::from_str("40.00").unwrap(), Decimal::percent(4000)); - assert_eq!(Decimal::from_str("04.00").unwrap(), Decimal::percent(400)); - assert_eq!(Decimal::from_str("00.40").unwrap(), Decimal::percent(40)); - assert_eq!(Decimal::from_str("00.04").unwrap(), Decimal::percent(4)); - - // Can handle DECIMAL_PLACES fractional digits - assert_eq!( - Decimal::from_str("7.123456789012345678").unwrap(), - Decimal(7123456789012345678i128) - ); - assert_eq!( - Decimal::from_str("7.999999999999999999").unwrap(), - Decimal(7999999999999999999i128) - ); - } - - #[test] - fn decimal_is_zero_works() { - assert!(Decimal::zero().is_zero()); - assert!(Decimal::percent(0).is_zero()); - assert!(Decimal::permille(0).is_zero()); - - assert!(!Decimal::one().is_zero()); - assert!(!Decimal::percent(123).is_zero()); - assert!(!Decimal::permille(1234).is_zero()); - } - - #[test] - fn decimal_inv_works() { - // d = 0 - assert_eq!(Decimal::zero().inv(), None); - - // d == 1 - assert_eq!(Decimal::one().inv(), Some(Decimal::one())); - - // d > 1 exact - assert_eq!(Decimal::percent(200).inv(), Some(Decimal::percent(50))); - assert_eq!(Decimal::percent(2_000).inv(), Some(Decimal::percent(5))); - assert_eq!(Decimal::percent(20_000).inv(), Some(Decimal::permille(5))); - assert_eq!(Decimal::percent(200_000).inv(), Some(Decimal::bps(5))); - - // d > 1 rounded - assert_eq!( - Decimal::percent(300).inv(), - Some(Decimal::from_ratio(1i128, 3i128)) - ); - assert_eq!( - Decimal::percent(600).inv(), - Some(Decimal::from_ratio(1i128, 6i128)) - ); - - // d < 1 exact - assert_eq!(Decimal::percent(50).inv(), Some(Decimal::percent(200))); - assert_eq!(Decimal::percent(5).inv(), Some(Decimal::percent(2_000))); - assert_eq!(Decimal::permille(5).inv(), Some(Decimal::percent(20_000))); - assert_eq!(Decimal::bps(5).inv(), Some(Decimal::percent(200_000))); - } - - #[test] - fn decimal_add_works() { - let value = Decimal::one() + Decimal::percent(50); // 1.5 - assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL * 3i128 / 2i128); - - assert_eq!( - Decimal::percent(5) + Decimal::percent(4), - Decimal::percent(9) - ); - assert_eq!(Decimal::percent(5) + Decimal::zero(), Decimal::percent(5)); - assert_eq!(Decimal::zero() + Decimal::zero(), Decimal::zero()); - } - - #[test] - #[should_panic(expected = "attempt to add with overflow")] - fn decimal_add_overflow_panics() { - let _value = Decimal::MAX + Decimal::percent(50); - } - - #[test] - fn decimal_sub_works() { - let value = Decimal::one() - Decimal::percent(50); // 0.5 - assert_eq!(value.0, Decimal::DECIMAL_FRACTIONAL / 2i128); - - assert_eq!( - Decimal::percent(9) - Decimal::percent(4), - Decimal::percent(5) - ); - assert_eq!(Decimal::percent(16) - Decimal::zero(), Decimal::percent(16)); - assert_eq!(Decimal::percent(16) - Decimal::percent(16), Decimal::zero()); - assert_eq!(Decimal::zero() - Decimal::zero(), Decimal::zero()); - } - - #[test] - fn decimal_implements_mul() { - let one = Decimal::one(); - let two = one + one; - let half = Decimal::percent(50); - - // 1*x and x*1 - assert_eq!(one * Decimal::percent(0), Decimal::percent(0)); - assert_eq!(one * Decimal::percent(1), Decimal::percent(1)); - assert_eq!(one * Decimal::percent(10), Decimal::percent(10)); - assert_eq!(one * Decimal::percent(100), Decimal::percent(100)); - assert_eq!(one * Decimal::percent(1000), Decimal::percent(1000)); - // assert_eq!(one * Decimal::MAX, Decimal::MAX); - assert_eq!(Decimal::percent(0) * one, Decimal::percent(0)); - assert_eq!(Decimal::percent(1) * one, Decimal::percent(1)); - assert_eq!(Decimal::percent(10) * one, Decimal::percent(10)); - assert_eq!(Decimal::percent(100) * one, Decimal::percent(100)); - assert_eq!(Decimal::percent(1000) * one, Decimal::percent(1000)); - // assert_eq!(Decimal::MAX * one, Decimal::MAX); - - // double - assert_eq!(two * Decimal::percent(0), Decimal::percent(0)); - assert_eq!(two * Decimal::percent(1), Decimal::percent(2)); - assert_eq!(two * Decimal::percent(10), Decimal::percent(20)); - assert_eq!(two * Decimal::percent(100), Decimal::percent(200)); - assert_eq!(two * Decimal::percent(1000), Decimal::percent(2000)); - assert_eq!(Decimal::percent(0) * two, Decimal::percent(0)); - assert_eq!(Decimal::percent(1) * two, Decimal::percent(2)); - assert_eq!(Decimal::percent(10) * two, Decimal::percent(20)); - assert_eq!(Decimal::percent(100) * two, Decimal::percent(200)); - assert_eq!(Decimal::percent(1000) * two, Decimal::percent(2000)); - - // half - assert_eq!(half * Decimal::percent(0), Decimal::percent(0)); - assert_eq!(half * Decimal::percent(1), Decimal::permille(5)); - assert_eq!(half * Decimal::percent(10), Decimal::percent(5)); - assert_eq!(half * Decimal::percent(100), Decimal::percent(50)); - assert_eq!(half * Decimal::percent(1000), Decimal::percent(500)); - assert_eq!(Decimal::percent(0) * half, Decimal::percent(0)); - assert_eq!(Decimal::percent(1) * half, Decimal::permille(5)); - assert_eq!(Decimal::percent(10) * half, Decimal::percent(5)); - assert_eq!(Decimal::percent(100) * half, Decimal::percent(50)); - assert_eq!(Decimal::percent(1000) * half, Decimal::percent(500)); - } - - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_mul_overflow_panics() { - let _value = Decimal::MAX * Decimal::percent(101); - } - - #[test] - // in this test the Decimal is on the right - fn i128_decimal_multiply() { - // a*b - let left = 300i128; - let right = Decimal::one() + Decimal::percent(50); // 1.5 - assert_eq!(left * right, 450i128); - - // a*0 - let left = 300i128; - let right = Decimal::zero(); - assert_eq!(left * right, 0i128); - - // 0*a - let left = 0i128; - let right = Decimal::one() + Decimal::percent(50); // 1.5 - assert_eq!(left * right, 0i128); - - assert_eq!(0i128 * Decimal::one(), 0i128); - assert_eq!(1i128 * Decimal::one(), 1i128); - assert_eq!(2i128 * Decimal::one(), 2i128); - - assert_eq!(1i128 * Decimal::percent(10), 0i128); - assert_eq!(10i128 * Decimal::percent(10), 1i128); - assert_eq!(100i128 * Decimal::percent(10), 10i128); - - assert_eq!(1i128 * Decimal::percent(50), 0i128); - assert_eq!(100i128 * Decimal::percent(50), 50i128); - assert_eq!(3200i128 * Decimal::percent(50), 1600i128); - assert_eq!(999i128 * Decimal::percent(50), 499i128); // default rounding down - - assert_eq!(1i128 * Decimal::percent(200), 2i128); - assert_eq!(1000i128 * Decimal::percent(200), 2000i128); - } - - #[test] - // in this test the Decimal is on the left - fn decimal_i128_multiply() { - // a*b - let left = Decimal::one() + Decimal::percent(50); // 1.5 - let right = 300i128; - assert_eq!(left * right, 450i128); - - // 0*a - let left = Decimal::zero(); - let right = 300i128; - assert_eq!(left * right, 0i128); - - // a*0 - let left = Decimal::one() + Decimal::percent(50); // 1.5 - let right = 0i128; - assert_eq!(left * right, 0i128); - } - - #[test] - fn decimal_implements_div() { - let one = Decimal::one(); - let two = one + one; - let half = Decimal::percent(50); - - // 1/x and x/1 - assert_eq!(one / Decimal::percent(1), Decimal::percent(10_000)); - assert_eq!(one / Decimal::percent(10), Decimal::percent(1_000)); - assert_eq!(one / Decimal::percent(100), Decimal::percent(100)); - assert_eq!(one / Decimal::percent(1000), Decimal::percent(10)); - assert_eq!(Decimal::percent(0) / one, Decimal::percent(0)); - assert_eq!(Decimal::percent(1) / one, Decimal::percent(1)); - assert_eq!(Decimal::percent(10) / one, Decimal::percent(10)); - assert_eq!(Decimal::percent(100) / one, Decimal::percent(100)); - assert_eq!(Decimal::percent(1000) / one, Decimal::percent(1000)); - - // double - assert_eq!(two / Decimal::percent(1), Decimal::percent(20_000)); - assert_eq!(two / Decimal::percent(10), Decimal::percent(2_000)); - assert_eq!(two / Decimal::percent(100), Decimal::percent(200)); - assert_eq!(two / Decimal::percent(1000), Decimal::percent(20)); - assert_eq!(Decimal::percent(0) / two, Decimal::percent(0)); - assert_eq!(Decimal::percent(10) / two, Decimal::percent(5)); - assert_eq!(Decimal::percent(100) / two, Decimal::percent(50)); - assert_eq!(Decimal::percent(1000) / two, Decimal::percent(500)); - - // half - assert_eq!(half / Decimal::percent(1), Decimal::percent(5_000)); - assert_eq!(half / Decimal::percent(10), Decimal::percent(500)); - assert_eq!(half / Decimal::percent(100), Decimal::percent(50)); - assert_eq!(half / Decimal::percent(1000), Decimal::percent(5)); - assert_eq!(Decimal::percent(0) / half, Decimal::percent(0)); - assert_eq!(Decimal::percent(1) / half, Decimal::percent(2)); - assert_eq!(Decimal::percent(10) / half, Decimal::percent(20)); - assert_eq!(Decimal::percent(100) / half, Decimal::percent(200)); - assert_eq!(Decimal::percent(1000) / half, Decimal::percent(2000)); - - assert_eq!( - Decimal::percent(15) / Decimal::percent(60), - Decimal::percent(25) - ); - } - - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_div_overflow_panics() { - let _value = Decimal::MAX / Decimal::percent(10); - } - - #[test] - #[should_panic(expected = "Division failed - denominator must not be zero")] - fn decimal_div_by_zero_panics() { - let _value = Decimal::one() / Decimal::zero(); - } - - #[test] - fn decimal_i128_division() { - // a/b - let left = Decimal::percent(150); // 1.5 - let right = 3i128; - assert_eq!(left / right, Decimal::percent(50)); - - // 0/a - let left = Decimal::zero(); - let right = 300i128; - assert_eq!(left / right, Decimal::zero()); - } - - #[test] - #[should_panic(expected = "attempt to divide by zero")] - fn decimal_uint128_divide_by_zero() { - let left = Decimal::percent(150); // 1.5 - let right = 0i128; - let _result = left / right; - } - - #[test] - fn decimal_pow_works() { - assert_eq!(Decimal::percent(200).pow(2), Decimal::percent(400)); - assert_eq!(Decimal::percent(100).pow(10), Decimal::percent(100)); - } - - #[test] - #[should_panic] - fn decimal_pow_overflow_panics() { - _ = Decimal::MAX.pow(2u32); - } - - #[test] - fn decimal_abs_with_negative_number() { - let decimal = Decimal::new(-128); - - assert_eq!(decimal.abs(), Decimal(128)); - } - - #[test] - fn decimal_abs_with_positive_number() { - let decimal = Decimal::new(128); - - assert_eq!(decimal.abs(), Decimal(128)); - } - - #[test] - fn decimal_displayed_as_string() { - let env = Env::default(); - let decimal = Decimal::percent(128); - - // Convert expected string to Soroban SDK String - let expected_msg = "1.28"; - let expected_string = String::from_str(&env, expected_msg); - - // Convert decimal to String and get its byte representation - let result_string = decimal.to_string(&env); - let result_string_len = result_string.len() as usize; - let mut result_bytes = alloc::vec![0u8; result_string_len]; - result_string.copy_into_slice(&mut result_bytes); - - // Get byte representation of expected string - let expected_string_len = expected_string.len() as usize; - let mut expected_bytes = alloc::vec![0u8; expected_string_len]; - expected_string.copy_into_slice(&mut expected_bytes); - - assert_eq!(result_bytes, expected_bytes); - } - - #[test] - fn decimal_fmt_without_fractional_part() { - let value = Decimal::from_atomics(100, 0); - assert_eq!(format!("{}", value), "100"); - } - - #[test] - fn decimal_fmt_fractional_part() { - let value = Decimal::from_atomics(123456789, 5); - assert_eq!(format!("{}", value), "1234.56789"); - } - - #[test] - fn decimal_fmt_fractional_part_with_trailing_zeros() { - // 12345.6 - let value = Decimal::from_atomics(123456, 1); - assert_eq!(format!("{}", value), "12345.6"); - } - - #[test] - fn decimal_fmt_only_fractional() { - // 0.0789 - let value = Decimal::from_atomics(789, 4); - assert_eq!(format!("{}", value), "0.0789"); - } - - #[test] - fn test_denominator() { - let decimal = Decimal::percent(123); - assert_eq!(decimal.denominator(), Decimal::DECIMAL_FRACTIONAL); - } - - #[test] - fn test_atomics() { - let decimal = Decimal::percent(123); - assert_eq!(decimal.atomics(), 1230000000000000000); - } - - #[test] - fn test_to_i128_with_precision() { - let decimal = Decimal::percent(124); - assert_eq!(decimal.to_i128_with_precision(1), 12); - assert_eq!(decimal.to_i128_with_precision(2), 124); - } - - #[test] - fn test_multiply_ratio() { - let decimal = Decimal::percent(1); - let numerator = Decimal::new(2); - let denominator = Decimal::new(5); - - // decimal is 10_000_000_000_000_000, atomics would be same - // numerator is 20_000_000_000_000_000, atomics would be same - // denominator is 50_000_000_000_000_000, amount would be same - // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 - // decimal from ratio - // numerator 200_000_000_000_000_000_000_000_000_000 - // denominator = 50_000_000_000_000_000 - // numerator * DECIMAL_FRACTIONAL / denominator is the result - assert_eq!( - decimal.multiply_ratio(numerator, denominator), - Decimal::new(4000000000000000000000000000000000) - ); - } - - #[test] - fn test_abs_difference() { - let a = Decimal::new(100); - let b = Decimal::new(200); - - assert_eq!(a.abs_diff(b), Decimal::new(100)); - - let negative_a = Decimal::new(-100); - let negative_b = Decimal::new(-200); - - assert_eq!(negative_a.abs_diff(negative_b), Decimal::new(100)); - } +mod decimal; +mod decimal256; - #[test] - #[should_panic(expected = "There must be at least one fractional digit")] - fn test_from_str_with_too_many_fractional_digits() { - let _ = Decimal::from_str("123.12345678901187245619827346"); - } -} +pub use decimal::Decimal; From 84832b4356cca706c6f8e7d130dea56350cc72fc Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 7 Mar 2024 17:11:12 +0100 Subject: [PATCH 02/25] Decimal: Current state of 256 implementation --- packages/decimal/src/decimal256.rs | 1012 ++++++++++++++++++++++++++++ 1 file changed, 1012 insertions(+) create mode 100644 packages/decimal/src/decimal256.rs diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs new file mode 100644 index 000000000..037880d98 --- /dev/null +++ b/packages/decimal/src/decimal256.rs @@ -0,0 +1,1012 @@ +// A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache +// License 2.0 - https://github.com/CosmWasm/cosmwasm. + +use soroban_sdk::{Env, String, I256, Bytes}; + +use core::{ + cmp::{Ordering, PartialEq, PartialOrd}, + fmt, + ops::{Add, Div, Mul, Sub}, + str::FromStr, +}; + +extern crate alloc; + +#[allow(dead_code)] +#[derive(Debug)] +enum Error { + DivideByZero, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] +pub struct Decimal256(I256); + +#[allow(dead_code)] +impl Decimal256 { + const DECIMAL_FRACTIONAL: I256 = + I256::from_i128(&Env::default(), 1_000_000_000_000_000_000i128); // 1*10**18 + const DECIMAL_FRACTIONAL_SQUARED: I256 = I256::from_i128( + &Env::default(), + 1_000_000_000_000_000_000_000_000_000_000_000_000i128, + ); // (1*10**18)**2 = 1*10**36 + /// The number of decimal places. Since decimal types are fixed-point rather than + /// floating-point, this is a constant. + pub const DECIMAL_PLACES: i32 = 18; + /// The largest value that can be represented by this decimal type. + pub const MAX: Self = Self(I256::from_i128(&Env::default(), (1i128 << 127) - 1)); + /// The smallest value that can be represented by this decimal type. + pub const MIN: Self = Self(I256::from_i128(&Env::default(), -1i128 << 127)); + + pub fn new(value: I256) -> Self { + Decimal256(value) + } + + pub const fn raw(value: I256) -> Self { + Self(value) + } + + /// Create a 1.0 Decimal256 + #[inline] + pub const fn one() -> Self { + Self(Self::DECIMAL_FRACTIONAL) + } + + /// Create a 0.0 Decimal256 + #[inline] + pub const fn zero() -> Self { + Self(I256::from_i32(&Env::default(), 0i32)) + } + + /// Convert x% into Decimal256 + pub fn percent(x: i64) -> Self { + Self(I256::from_i128(&Env::default(), (x as i128) * 10_000_000_000_000_000)) + } + + /// Convert permille (x/1000) into Decimal256 + pub fn permille(x: i64) -> Self { + Self(I256::from_i128(&Env::default(), (x as i128) * 1_000_000_000_000_000)) + } + + /// Convert basis points (x/10000) into Decimal256 + pub fn bps(x: i64) -> Self { + Self(I256::from_i128(&Env::default(), (x as i128) * 100_000_000_000_000)) + } + + /// The number of decimal places. This is a constant value for now + /// but this could potentially change as the type evolves. + /// + /// See also [`Decimal256::atomics()`]. + #[must_use] + #[inline] + pub const fn decimal_places(&self) -> i32 { + Self::DECIMAL_PLACES + } + + #[inline] + fn numerator(&self) -> I256 { + self.0 + } + + #[inline] + fn denominator(&self) -> I256 { + Self::DECIMAL_FRACTIONAL + } + + #[must_use] + pub const fn is_zero(&self) -> bool { + self.0 == I256::from_i32(&Env::default(), 0i32) + } + + /// A decimal is an integer of atomic units plus a number that specifies the + /// position of the decimal dot. So any decimal can be expressed as two numbers. + /// + /// ## Examples + /// + /// ``` + /// use decimal::Decimal256; + /// // Value with whole and fractional part + /// let a = Decimal256::percent(123); + /// assert_eq!(a.decimal_places(), 18); + /// assert_eq!(a.atomics(), 1230000000000000000); + /// + /// // Smallest possible value + /// let b = Decimal256::new(1); + /// assert_eq!(b.decimal_places(), 18); + /// assert_eq!(b.atomics(), 1); + /// ``` + #[must_use] + #[inline] + pub const fn atomics(&self) -> I256 { + self.0 + } + + /// Creates a decimal from a number of atomic units and the number + /// of decimal places. The inputs will be converted internally to form + /// a decimal with 18 decimal places. So the input 1234 and 3 will create + /// the decimal 1.234. + /// + /// Using 18 decimal places is slightly more efficient than other values + /// as no internal conversion is necessary. + /// + /// ## Examples + /// + /// ``` + /// use decimal::Decimal256; + /// use soroban_sdk::{String, Env}; + /// + /// let e = Env::default(); + /// let a = Decimal256::from_atomics(1234, 3); + /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1.234")); + /// + /// let a = Decimal256::from_atomics(1234, 0); + /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1234")); + /// + /// let a = Decimal256::from_atomics(1, 18); + /// assert_eq!(a.to_string(&e), String::from_slice(&e, "0.000000000000000001")); + /// ``` + pub fn from_atomics(atomics: impl Into, decimal_places: i32) -> Self { + let atomics: I256 = atomics.into(); + const TEN: I256 = I256::from_be_bytes(&Env::default(), &Bytes::from_slice(&Env::default(), &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 10, + ])); + match decimal_places.cmp(&Self::DECIMAL_PLACES) { + Ordering::Less => { + let digits = Self::DECIMAL_PLACES - decimal_places; + let factor: I256 = TEN.pow(digits as u32); + Self(atomics * factor) + } + Ordering::Equal => Self(atomics), + Ordering::Greater => { + let digits = decimal_places - Self::DECIMAL_PLACES; + let factor = TEN.pow(digits as u32); + // Since factor cannot be zero, the division is safe. + Self(atomics / factor) + } + } + } + + /// Raises a value to the power of `exp`, panicking if an overflow occurs. + pub fn pow(self, exp: u32) -> Self { + // This uses the exponentiation by squaring algorithm: + // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method + + fn inner(mut x: Decimal256, mut n: u32) -> Decimal256 { + if n == 0 { + return Decimal256::one(); + } + + let mut y = Decimal256::one(); + + while n > 1 { + if n % 2 == 0 { + x = x * x; // Regular multiplication + n /= 2; + } else { + y = x * y; // Regular multiplication + x = x * x; // Regular multiplication + n = (n - 1) / 2; + } + } + + x * y + } + + inner(self, exp) + } + + /// Returns the multiplicative inverse `1/d` for decimal `d`. + /// + /// If `d` is zero, none is returned. + pub fn inv(&self) -> Option { + if self.is_zero() { + None + } else { + // Let self be p/q with p = self.0 and q = DECIMAL_FRACTIONAL. + // Now we calculate the inverse a/b = q/p such that b = DECIMAL_FRACTIONAL. Then + // `a = DECIMAL_FRACTIONAL*DECIMAL_FRACTIONAL / self.0`. + Some(Decimal256(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) + } + } + + /// Returns the ratio (numerator / denominator) as a Decimal256 + pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + match Decimal256::checked_from_ratio(numerator, denominator) { + Ok(ratio) => ratio, + Err(Error::DivideByZero) => panic!("Denominator must not be zero"), + } + } + + pub fn to_I256_with_precision(&self, precision: impl Into) -> I256 { + let value = self.atomics(); + let precision = precision.into(); + + let divisor = 10I256.pow((self.decimal_places() - precision) as u32); + value / divisor + } + + fn multiply_ratio(&self, numerator: Decimal256, denominator: Decimal256) -> Decimal256 { + Decimal256::from_ratio(self.atomics() * numerator.atomics(), denominator.atomics()) + } + + /// Returns the ratio (numerator / denominator) as a Decimal256 + fn checked_from_ratio( + numerator: impl Into, + denominator: impl Into, + ) -> Result { + let numerator = numerator.into(); + let denominator = denominator.into(); + + // If denominator is zero, panic. + if denominator == 0 { + return Err(Error::DivideByZero); + } + + // Convert numerator and denominator to BigInt. + // unwrap since I256 is always convertible to BigInt + // let numerator = numerator.to_bigint().unwrap(); + // let denominator = denominator.to_bigint().unwrap(); + // let decimal_fractional = Self::DECIMAL_FRACTIONAL.to_bigint().unwrap(); + + // Compute the ratio: (numerator * DECIMAL_FRACTIONAL) / denominator + let ratio = (numerator * Self::DECIMAL_FRACTIONAL) / denominator; + + // Convert back to I256. If conversion fails, panic. + // let ratio = ratio.to_I256().ok_or(Error::Overflow)?; + + // Construct and return the Decimal256. + Ok(Decimal256(ratio)) + } + + pub fn abs(&self) -> Self { + if self.0 < 0 { + Decimal256(-self.0) + } else { + *self + } + } + + pub fn to_string(&self, env: &Env) -> String { + String::from_str(env, alloc::format!("{}", self).as_str()) + } + + pub const fn abs_diff(self, other: Self) -> Self { + Self(self.0.abs_diff(other.0) as I256) + } +} + +impl Add for Decimal256 { + type Output = Self; + + fn add(self, other: Self) -> Self { + Decimal256(self.0 + other.0) + } +} +impl Sub for Decimal256 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Decimal256(self.0 - other.0) + } +} + +impl Mul for Decimal256 { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn mul(self, other: Self) -> Self { + // Decimal256s are fractions. We can multiply two decimals a and b + // via + // (a.numerator() * b.numerator()) / (a.denominator() * b.denominator()) + // = (a.numerator() * b.numerator()) / a.denominator() / b.denominator() + + // let self_numerator = self.numerator().to_bigint().unwrap(); + // let other_numerator = other.numerator().to_bigint().unwrap(); + + // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL + let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; + + // Convert the result back to I256, and panic on overflow + // let result = result + // .to_I256() + // .unwrap_or_else(|| panic!("attempt to multiply with overflow")); + + // Return a new Decimal256 + Decimal256(result) + } +} + +impl Div for Decimal256 { + type Output = Self; + + fn div(self, rhs: Self) -> Self { + match Decimal256::checked_from_ratio(self.numerator(), rhs.numerator()) { + Ok(ratio) => ratio, + Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), + } + } +} + +impl Mul for Decimal256 { + type Output = I256; + + fn mul(self, rhs: I256) -> Self::Output { + rhs * self + } +} + +impl Div for Decimal256 { + type Output = Self; + + fn div(self, rhs: I256) -> Self::Output { + Decimal256(self.0 / rhs) + } +} + +impl Mul for I256 { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn mul(self, rhs: Decimal256) -> Self::Output { + // 0*a and b*0 is always 0 + if self == 0I256 || rhs.is_zero() { + return 0I256; + } + self * rhs.0 / Decimal256::DECIMAL_FRACTIONAL + } +} + +impl FromStr for Decimal256 { + type Err = (); + + fn from_str(input: &str) -> Result { + let mut parts_iter = input.split('.'); + + let whole_part = parts_iter.next().expect("Unexpected input format"); + let whole: I256 = whole_part.parse().expect("Error parsing whole"); + let mut atomics = whole * Self::DECIMAL_FRACTIONAL; + + if let Some(fractional_part) = parts_iter.next() { + let fractional: I256 = fractional_part.parse().expect("Error parsing fractional"); + let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; + assert!(exp >= 0, "There must be at least one fractional digit"); + let fractional_factor = 10I256.pow(exp as u32); + atomics += fractional * fractional_factor; + } + + assert!(parts_iter.next().is_none(), "Unexpected number of dots"); + + Ok(Decimal256(atomics)) + } +} + +impl fmt::Display for Decimal256 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let whole = self.0 / Self::DECIMAL_FRACTIONAL; + let fractional = self.0 % Self::DECIMAL_FRACTIONAL; + + if fractional == 0 { + write!(f, "{}", whole) + } else { + let fractional_string = alloc::format!( + "{:0>padding$}", + fractional, + padding = Self::DECIMAL_PLACES as usize + ); + f.write_fmt(format_args!( + "{}.{}", + whole, + fractional_string.trim_end_matches('0') + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + + #[test] + fn decimal_new() { + let expected = 300i128; + assert_eq!(Decimal256::new(expected).0, expected); + } + + #[test] + fn decimal_raw() { + let value = 300i128; + assert_eq!(Decimal256::raw(value).0, value); + } + + #[test] + fn decimal_one() { + let value = Decimal256::one(); + assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL); + } + + #[test] + fn decimal_zero() { + let value = Decimal256::zero(); + assert_eq!(value.0, 0i128); + } + + #[test] + fn decimal_percent() { + let value = Decimal256::percent(50); + assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); + } + + #[test] + fn decimal_from_atomics_works() { + let one = Decimal256::one(); + let two = one + one; + + assert_eq!(Decimal256::from_atomics(1i128, 0), one); + assert_eq!(Decimal256::from_atomics(10i128, 1), one); + assert_eq!(Decimal256::from_atomics(100i128, 2), one); + assert_eq!(Decimal256::from_atomics(1000i128, 3), one); + assert_eq!(Decimal256::from_atomics(1000000000000000000i128, 18), one); + assert_eq!(Decimal256::from_atomics(10000000000000000000i128, 19), one); + assert_eq!(Decimal256::from_atomics(100000000000000000000i128, 20), one); + + assert_eq!(Decimal256::from_atomics(2i128, 0), two); + assert_eq!(Decimal256::from_atomics(20i128, 1), two); + assert_eq!(Decimal256::from_atomics(200i128, 2), two); + assert_eq!(Decimal256::from_atomics(2000i128, 3), two); + assert_eq!(Decimal256::from_atomics(2000000000000000000i128, 18), two); + assert_eq!(Decimal256::from_atomics(20000000000000000000i128, 19), two); + assert_eq!(Decimal256::from_atomics(200000000000000000000i128, 20), two); + + // Cuts decimal digits (20 provided but only 18 can be stored) + assert_eq!( + Decimal256::from_atomics(4321i128, 20), + Decimal256::from_str("0.000000000000000043").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(6789i128, 20), + Decimal256::from_str("0.000000000000000067").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(i128::MAX, 38), + Decimal256::from_str("1.701411834604692317").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(i128::MAX, 39), + Decimal256::from_str("0.170141183460469231").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(i128::MAX, 45), + Decimal256::from_str("0.000000170141183460").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(i128::MAX, 51), + Decimal256::from_str("0.000000000000170141").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(i128::MAX, 56), + Decimal256::from_str("0.000000000000000001").unwrap() + ); + } + + #[test] + fn decimal_from_ratio_works() { + // 1.0 + assert_eq!(Decimal256::from_ratio(1i128, 1i128), Decimal256::one()); + assert_eq!(Decimal256::from_ratio(53i128, 53i128), Decimal256::one()); + assert_eq!(Decimal256::from_ratio(125i128, 125i128), Decimal256::one()); + + // 1.5 + assert_eq!( + Decimal256::from_ratio(3i128, 2i128), + Decimal256::percent(150) + ); + assert_eq!( + Decimal256::from_ratio(150i128, 100i128), + Decimal256::percent(150) + ); + assert_eq!( + Decimal256::from_ratio(333i128, 222i128), + Decimal256::percent(150) + ); + + // 0.125 + assert_eq!( + Decimal256::from_ratio(1i64, 8i64), + Decimal256::permille(125) + ); + assert_eq!( + Decimal256::from_ratio(125i64, 1000i64), + Decimal256::permille(125) + ); + + // 1/3 (result floored) + assert_eq!( + Decimal256::from_ratio(1i128, 3i128), + Decimal256(333_333_333_333_333_333i128) + ); + + // 2/3 (result floored) + assert_eq!( + Decimal256::from_ratio(2i128, 3i128), + Decimal256(666_666_666_666_666_666i128) + ); + + // large inputs + assert_eq!(Decimal256::from_ratio(0i128, i128::MAX), Decimal256::zero()); + // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); + + // due to limited possibilities - we're only allowed to use i128 as input - maximum + // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL + // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. + assert_eq!( + Decimal256::from_ratio(340282366920938i128, 340282366920938i128), + Decimal256::one() + ); + // This works because of similar orders of magnitude + assert_eq!( + Decimal256::from_ratio(34028236692093900000i128, 34028236692093900000i128), + Decimal256::one() + ); + assert_eq!( + Decimal256::from_ratio(34028236692093900000i128, 1i128), + Decimal256::new(34028236692093900000i128 * Decimal256::DECIMAL_FRACTIONAL) + ); + } + + #[test] + #[should_panic(expected = "Denominator must not be zero")] + fn decimal_from_ratio_panics_for_zero_denominator() { + Decimal256::from_ratio(1i128, 0i128); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_from_ratio_panics_for_mul_overflow() { + Decimal256::from_ratio(i128::MAX, 1i128); + } + + #[test] + fn decimal_decimal_places_works() { + let zero = Decimal256::zero(); + let one = Decimal256::one(); + let half = Decimal256::percent(50); + let two = Decimal256::percent(200); + let max = Decimal256::MAX; + + assert_eq!(zero.decimal_places(), 18); + assert_eq!(one.decimal_places(), 18); + assert_eq!(half.decimal_places(), 18); + assert_eq!(two.decimal_places(), 18); + assert_eq!(max.decimal_places(), 18); + } + + #[test] + fn decimal_from_str_works() { + // Integers + assert_eq!(Decimal256::from_str("0").unwrap(), Decimal256::percent(0)); + assert_eq!(Decimal256::from_str("1").unwrap(), Decimal256::percent(100)); + assert_eq!(Decimal256::from_str("5").unwrap(), Decimal256::percent(500)); + assert_eq!( + Decimal256::from_str("42").unwrap(), + Decimal256::percent(4200) + ); + assert_eq!(Decimal256::from_str("000").unwrap(), Decimal256::percent(0)); + assert_eq!( + Decimal256::from_str("001").unwrap(), + Decimal256::percent(100) + ); + assert_eq!( + Decimal256::from_str("005").unwrap(), + Decimal256::percent(500) + ); + assert_eq!( + Decimal256::from_str("0042").unwrap(), + Decimal256::percent(4200) + ); + + // Decimal256s + assert_eq!( + Decimal256::from_str("1.0").unwrap(), + Decimal256::percent(100) + ); + assert_eq!( + Decimal256::from_str("1.5").unwrap(), + Decimal256::percent(150) + ); + assert_eq!( + Decimal256::from_str("0.5").unwrap(), + Decimal256::percent(50) + ); + assert_eq!( + Decimal256::from_str("0.123").unwrap(), + Decimal256::permille(123) + ); + + assert_eq!( + Decimal256::from_str("40.00").unwrap(), + Decimal256::percent(4000) + ); + assert_eq!( + Decimal256::from_str("04.00").unwrap(), + Decimal256::percent(400) + ); + assert_eq!( + Decimal256::from_str("00.40").unwrap(), + Decimal256::percent(40) + ); + assert_eq!( + Decimal256::from_str("00.04").unwrap(), + Decimal256::percent(4) + ); + + // Can handle DECIMAL_PLACES fractional digits + assert_eq!( + Decimal256::from_str("7.123456789012345678").unwrap(), + Decimal256(7123456789012345678i128) + ); + assert_eq!( + Decimal256::from_str("7.999999999999999999").unwrap(), + Decimal256(7999999999999999999i128) + ); + } + + #[test] + fn decimal_is_zero_works() { + assert!(Decimal256::zero().is_zero()); + assert!(Decimal256::percent(0).is_zero()); + assert!(Decimal256::permille(0).is_zero()); + + assert!(!Decimal256::one().is_zero()); + assert!(!Decimal256::percent(123).is_zero()); + assert!(!Decimal256::permille(1234).is_zero()); + } + + #[test] + fn decimal_inv_works() { + // d = 0 + assert_eq!(Decimal256::zero().inv(), None); + + // d == 1 + assert_eq!(Decimal256::one().inv(), Some(Decimal256::one())); + + // d > 1 exact + assert_eq!( + Decimal256::percent(200).inv(), + Some(Decimal256::percent(50)) + ); + assert_eq!( + Decimal256::percent(2_000).inv(), + Some(Decimal256::percent(5)) + ); + assert_eq!( + Decimal256::percent(20_000).inv(), + Some(Decimal256::permille(5)) + ); + assert_eq!(Decimal256::percent(200_000).inv(), Some(Decimal256::bps(5))); + + // d > 1 rounded + assert_eq!( + Decimal256::percent(300).inv(), + Some(Decimal256::from_ratio(1i128, 3i128)) + ); + assert_eq!( + Decimal256::percent(600).inv(), + Some(Decimal256::from_ratio(1i128, 6i128)) + ); + + // d < 1 exact + assert_eq!( + Decimal256::percent(50).inv(), + Some(Decimal256::percent(200)) + ); + assert_eq!( + Decimal256::percent(5).inv(), + Some(Decimal256::percent(2_000)) + ); + assert_eq!( + Decimal256::permille(5).inv(), + Some(Decimal256::percent(20_000)) + ); + assert_eq!(Decimal256::bps(5).inv(), Some(Decimal256::percent(200_000))); + } + + #[test] + fn decimal_add_works() { + let value = Decimal256::one() + Decimal256::percent(50); // 1.5 + assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL * 3i128 / 2i128); + + assert_eq!( + Decimal256::percent(5) + Decimal256::percent(4), + Decimal256::percent(9) + ); + assert_eq!( + Decimal256::percent(5) + Decimal256::zero(), + Decimal256::percent(5) + ); + assert_eq!(Decimal256::zero() + Decimal256::zero(), Decimal256::zero()); + } + + #[test] + #[should_panic(expected = "attempt to add with overflow")] + fn decimal_add_overflow_panics() { + let _value = Decimal256::MAX + Decimal256::percent(50); + } + + #[test] + fn decimal_sub_works() { + let value = Decimal256::one() - Decimal256::percent(50); // 0.5 + assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); + + assert_eq!( + Decimal256::percent(9) - Decimal256::percent(4), + Decimal256::percent(5) + ); + assert_eq!( + Decimal256::percent(16) - Decimal256::zero(), + Decimal256::percent(16) + ); + assert_eq!( + Decimal256::percent(16) - Decimal256::percent(16), + Decimal256::zero() + ); + assert_eq!(Decimal256::zero() - Decimal256::zero(), Decimal256::zero()); + } + + #[test] + fn decimal_implements_mul() { + let one = Decimal256::one(); + let two = one + one; + let half = Decimal256::percent(50); + + // 1*x and x*1 + assert_eq!(one * Decimal256::percent(0), Decimal256::percent(0)); + assert_eq!(one * Decimal256::percent(1), Decimal256::percent(1)); + assert_eq!(one * Decimal256::percent(10), Decimal256::percent(10)); + assert_eq!(one * Decimal256::percent(100), Decimal256::percent(100)); + assert_eq!(one * Decimal256::percent(1000), Decimal256::percent(1000)); + // assert_eq!(one * Decimal256::MAX, Decimal256::MAX); + assert_eq!(Decimal256::percent(0) * one, Decimal256::percent(0)); + assert_eq!(Decimal256::percent(1) * one, Decimal256::percent(1)); + assert_eq!(Decimal256::percent(10) * one, Decimal256::percent(10)); + assert_eq!(Decimal256::percent(100) * one, Decimal256::percent(100)); + assert_eq!(Decimal256::percent(1000) * one, Decimal256::percent(1000)); + // assert_eq!(Decimal256::MAX * one, Decimal256::MAX); + + // double + assert_eq!(two * Decimal256::percent(0), Decimal256::percent(0)); + assert_eq!(two * Decimal256::percent(1), Decimal256::percent(2)); + assert_eq!(two * Decimal256::percent(10), Decimal256::percent(20)); + assert_eq!(two * Decimal256::percent(100), Decimal256::percent(200)); + assert_eq!(two * Decimal256::percent(1000), Decimal256::percent(2000)); + assert_eq!(Decimal256::percent(0) * two, Decimal256::percent(0)); + assert_eq!(Decimal256::percent(1) * two, Decimal256::percent(2)); + assert_eq!(Decimal256::percent(10) * two, Decimal256::percent(20)); + assert_eq!(Decimal256::percent(100) * two, Decimal256::percent(200)); + assert_eq!(Decimal256::percent(1000) * two, Decimal256::percent(2000)); + + // half + assert_eq!(half * Decimal256::percent(0), Decimal256::percent(0)); + assert_eq!(half * Decimal256::percent(1), Decimal256::permille(5)); + assert_eq!(half * Decimal256::percent(10), Decimal256::percent(5)); + assert_eq!(half * Decimal256::percent(100), Decimal256::percent(50)); + assert_eq!(half * Decimal256::percent(1000), Decimal256::percent(500)); + assert_eq!(Decimal256::percent(0) * half, Decimal256::percent(0)); + assert_eq!(Decimal256::percent(1) * half, Decimal256::permille(5)); + assert_eq!(Decimal256::percent(10) * half, Decimal256::percent(5)); + assert_eq!(Decimal256::percent(100) * half, Decimal256::percent(50)); + assert_eq!(Decimal256::percent(1000) * half, Decimal256::percent(500)); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_mul_overflow_panics() { + let _value = Decimal256::MAX * Decimal256::percent(101); + } + + #[test] + // in this test the Decimal256 is on the right + fn i128_decimal_multiply() { + // a*b + let left = 300i128; + let right = Decimal256::one() + Decimal256::percent(50); // 1.5 + assert_eq!(left * right, 450i128); + + // a*0 + let left = 300i128; + let right = Decimal256::zero(); + assert_eq!(left * right, 0i128); + + // 0*a + let left = 0i128; + let right = Decimal256::one() + Decimal256::percent(50); // 1.5 + assert_eq!(left * right, 0i128); + + assert_eq!(0i128 * Decimal256::one(), 0i128); + assert_eq!(1i128 * Decimal256::one(), 1i128); + assert_eq!(2i128 * Decimal256::one(), 2i128); + + assert_eq!(1i128 * Decimal256::percent(10), 0i128); + assert_eq!(10i128 * Decimal256::percent(10), 1i128); + assert_eq!(100i128 * Decimal256::percent(10), 10i128); + + assert_eq!(1i128 * Decimal256::percent(50), 0i128); + assert_eq!(100i128 * Decimal256::percent(50), 50i128); + assert_eq!(3200i128 * Decimal256::percent(50), 1600i128); + assert_eq!(999i128 * Decimal256::percent(50), 499i128); // default rounding down + + assert_eq!(1i128 * Decimal256::percent(200), 2i128); + assert_eq!(1000i128 * Decimal256::percent(200), 2000i128); + } + + #[test] + // in this test the Decimal256 is on the left + fn decimal_i128_multiply() { + // a*b + let left = Decimal256::one() + Decimal256::percent(50); // 1.5 + let right = 300i128; + assert_eq!(left * right, 450i128); + + // 0*a + let left = Decimal256::zero(); + let right = 300i128; + assert_eq!(left * right, 0i128); + + // a*0 + let left = Decimal256::one() + Decimal256::percent(50); // 1.5 + let right = 0i128; + assert_eq!(left * right, 0i128); + } + + #[test] + fn decimal_implements_div() { + let one = Decimal256::one(); + let two = one + one; + let half = Decimal256::percent(50); + + // 1/x and x/1 + assert_eq!(one / Decimal256::percent(1), Decimal256::percent(10_000)); + assert_eq!(one / Decimal256::percent(10), Decimal256::percent(1_000)); + assert_eq!(one / Decimal256::percent(100), Decimal256::percent(100)); + assert_eq!(one / Decimal256::percent(1000), Decimal256::percent(10)); + assert_eq!(Decimal256::percent(0) / one, Decimal256::percent(0)); + assert_eq!(Decimal256::percent(1) / one, Decimal256::percent(1)); + assert_eq!(Decimal256::percent(10) / one, Decimal256::percent(10)); + assert_eq!(Decimal256::percent(100) / one, Decimal256::percent(100)); + assert_eq!(Decimal256::percent(1000) / one, Decimal256::percent(1000)); + + // double + assert_eq!(two / Decimal256::percent(1), Decimal256::percent(20_000)); + assert_eq!(two / Decimal256::percent(10), Decimal256::percent(2_000)); + assert_eq!(two / Decimal256::percent(100), Decimal256::percent(200)); + assert_eq!(two / Decimal256::percent(1000), Decimal256::percent(20)); + assert_eq!(Decimal256::percent(0) / two, Decimal256::percent(0)); + assert_eq!(Decimal256::percent(10) / two, Decimal256::percent(5)); + assert_eq!(Decimal256::percent(100) / two, Decimal256::percent(50)); + assert_eq!(Decimal256::percent(1000) / two, Decimal256::percent(500)); + + // half + assert_eq!(half / Decimal256::percent(1), Decimal256::percent(5_000)); + assert_eq!(half / Decimal256::percent(10), Decimal256::percent(500)); + assert_eq!(half / Decimal256::percent(100), Decimal256::percent(50)); + assert_eq!(half / Decimal256::percent(1000), Decimal256::percent(5)); + assert_eq!(Decimal256::percent(0) / half, Decimal256::percent(0)); + assert_eq!(Decimal256::percent(1) / half, Decimal256::percent(2)); + assert_eq!(Decimal256::percent(10) / half, Decimal256::percent(20)); + assert_eq!(Decimal256::percent(100) / half, Decimal256::percent(200)); + assert_eq!(Decimal256::percent(1000) / half, Decimal256::percent(2000)); + + assert_eq!( + Decimal256::percent(15) / Decimal256::percent(60), + Decimal256::percent(25) + ); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_div_overflow_panics() { + let _value = Decimal256::MAX / Decimal256::percent(10); + } + + #[test] + #[should_panic(expected = "Division failed - denominator must not be zero")] + fn decimal_div_by_zero_panics() { + let _value = Decimal256::one() / Decimal256::zero(); + } + + #[test] + fn decimal_i128_division() { + // a/b + let left = Decimal256::percent(150); // 1.5 + let right = 3i128; + assert_eq!(left / right, Decimal256::percent(50)); + + // 0/a + let left = Decimal256::zero(); + let right = 300i128; + assert_eq!(left / right, Decimal256::zero()); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn decimal_uint128_divide_by_zero() { + let left = Decimal256::percent(150); // 1.5 + let right = 0i128; + let _result = left / right; + } + + #[test] + fn decimal_pow_works() { + assert_eq!(Decimal256::percent(200).pow(2), Decimal256::percent(400)); + assert_eq!(Decimal256::percent(100).pow(10), Decimal256::percent(100)); + } + + #[test] + #[should_panic] + fn decimal_pow_overflow_panics() { + _ = Decimal256::MAX.pow(2u32); + } + + #[test] + fn decimal_abs_with_negative_number() { + let decimal = Decimal256::new(128); + + assert_eq!(decimal.abs(), Decimal256(128)); + } + + #[test] + fn decimal_abs_with_positive_number() { + let decimal = Decimal256::new(128); + + assert_eq!(decimal.abs(), Decimal256(128)); + } + + #[test] + fn decimal_displayed_as_string() { + let env = Env::default(); + let decimal = Decimal256::percent(128); + + // Convert expected string to Soroban SDK String + let expected_msg = "1.28"; + let expected_string = String::from_str(&env, expected_msg); + + // Convert decimal to String and get its byte representation + let result_string = decimal.to_string(&env); + let result_string_len = result_string.len() as usize; + let mut result_bytes = alloc::vec![0u8; result_string_len]; + result_string.copy_into_slice(&mut result_bytes); + + // Get byte representation of expected string + let expected_string_len = expected_string.len() as usize; + let mut expected_bytes = alloc::vec![0u8; expected_string_len]; + expected_string.copy_into_slice(&mut expected_bytes); + + assert_eq!(result_bytes, expected_bytes); + } + + #[test] + fn decimal_fmt_without_fractional_part() { + let value = Decimal256::from_atomics(100, 0); + assert_eq!(format!("{}", value), "100"); + } + + #[test] + fn decimal_fmt_fractional_part() { + let value = Decimal256::from_atomics(123456789, 5); + assert_eq!(format!("{}", value), "1234.56789"); + } + + #[test] + fn decimal_fmt_fractional_part_with_trailing_zeros() { + // 12345.6 + let value = Decimal256::from_atomics(123456, 1); + assert_eq!(format!("{}", value), "12345.6"); + } + + #[test] + fn decimal_fmt_only_fractional() { + // 0.0789 + let value = Decimal256::from_atomics(789, 4); + assert_eq!(format!("{}", value), "0.0789"); + } +} From 1fd7bf3779355483d182d2b36d75f7aa683cb55d Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 12 Mar 2024 00:35:05 +0100 Subject: [PATCH 03/25] Decimal256: Lazy initialization to be able to work with Env type --- packages/decimal/src/decimal256.rs | 760 +++++++++++++++-------------- 1 file changed, 385 insertions(+), 375 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 037880d98..62ac42dfb 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -1,7 +1,7 @@ // A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache // License 2.0 - https://github.com/CosmWasm/cosmwasm. -use soroban_sdk::{Env, String, I256, Bytes}; +use soroban_sdk::{Bytes, Env, String, I256}; use core::{ cmp::{Ordering, PartialEq, PartialOrd}, @@ -23,384 +23,394 @@ pub struct Decimal256(I256); #[allow(dead_code)] impl Decimal256 { - const DECIMAL_FRACTIONAL: I256 = - I256::from_i128(&Env::default(), 1_000_000_000_000_000_000i128); // 1*10**18 - const DECIMAL_FRACTIONAL_SQUARED: I256 = I256::from_i128( - &Env::default(), - 1_000_000_000_000_000_000_000_000_000_000_000_000i128, - ); // (1*10**18)**2 = 1*10**36 - /// The number of decimal places. Since decimal types are fixed-point rather than - /// floating-point, this is a constant. + // Number of decimal places pub const DECIMAL_PLACES: i32 = 18; - /// The largest value that can be represented by this decimal type. - pub const MAX: Self = Self(I256::from_i128(&Env::default(), (1i128 << 127) - 1)); - /// The smallest value that can be represented by this decimal type. - pub const MIN: Self = Self(I256::from_i128(&Env::default(), -1i128 << 127)); - - pub fn new(value: I256) -> Self { - Decimal256(value) - } - - pub const fn raw(value: I256) -> Self { - Self(value) - } - - /// Create a 1.0 Decimal256 - #[inline] - pub const fn one() -> Self { - Self(Self::DECIMAL_FRACTIONAL) - } - - /// Create a 0.0 Decimal256 - #[inline] - pub const fn zero() -> Self { - Self(I256::from_i32(&Env::default(), 0i32)) - } - - /// Convert x% into Decimal256 - pub fn percent(x: i64) -> Self { - Self(I256::from_i128(&Env::default(), (x as i128) * 10_000_000_000_000_000)) - } - - /// Convert permille (x/1000) into Decimal256 - pub fn permille(x: i64) -> Self { - Self(I256::from_i128(&Env::default(), (x as i128) * 1_000_000_000_000_000)) - } - - /// Convert basis points (x/10000) into Decimal256 - pub fn bps(x: i64) -> Self { - Self(I256::from_i128(&Env::default(), (x as i128) * 100_000_000_000_000)) - } - - /// The number of decimal places. This is a constant value for now - /// but this could potentially change as the type evolves. - /// - /// See also [`Decimal256::atomics()`]. - #[must_use] - #[inline] - pub const fn decimal_places(&self) -> i32 { - Self::DECIMAL_PLACES - } - - #[inline] - fn numerator(&self) -> I256 { - self.0 - } - - #[inline] - fn denominator(&self) -> I256 { - Self::DECIMAL_FRACTIONAL - } - - #[must_use] - pub const fn is_zero(&self) -> bool { - self.0 == I256::from_i32(&Env::default(), 0i32) - } - - /// A decimal is an integer of atomic units plus a number that specifies the - /// position of the decimal dot. So any decimal can be expressed as two numbers. - /// - /// ## Examples - /// - /// ``` - /// use decimal::Decimal256; - /// // Value with whole and fractional part - /// let a = Decimal256::percent(123); - /// assert_eq!(a.decimal_places(), 18); - /// assert_eq!(a.atomics(), 1230000000000000000); - /// - /// // Smallest possible value - /// let b = Decimal256::new(1); - /// assert_eq!(b.decimal_places(), 18); - /// assert_eq!(b.atomics(), 1); - /// ``` - #[must_use] - #[inline] - pub const fn atomics(&self) -> I256 { - self.0 - } - - /// Creates a decimal from a number of atomic units and the number - /// of decimal places. The inputs will be converted internally to form - /// a decimal with 18 decimal places. So the input 1234 and 3 will create - /// the decimal 1.234. - /// - /// Using 18 decimal places is slightly more efficient than other values - /// as no internal conversion is necessary. - /// - /// ## Examples - /// - /// ``` - /// use decimal::Decimal256; - /// use soroban_sdk::{String, Env}; - /// - /// let e = Env::default(); - /// let a = Decimal256::from_atomics(1234, 3); - /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1.234")); - /// - /// let a = Decimal256::from_atomics(1234, 0); - /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1234")); - /// - /// let a = Decimal256::from_atomics(1, 18); - /// assert_eq!(a.to_string(&e), String::from_slice(&e, "0.000000000000000001")); - /// ``` - pub fn from_atomics(atomics: impl Into, decimal_places: i32) -> Self { - let atomics: I256 = atomics.into(); - const TEN: I256 = I256::from_be_bytes(&Env::default(), &Bytes::from_slice(&Env::default(), &[ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 10, - ])); - match decimal_places.cmp(&Self::DECIMAL_PLACES) { - Ordering::Less => { - let digits = Self::DECIMAL_PLACES - decimal_places; - let factor: I256 = TEN.pow(digits as u32); - Self(atomics * factor) - } - Ordering::Equal => Self(atomics), - Ordering::Greater => { - let digits = decimal_places - Self::DECIMAL_PLACES; - let factor = TEN.pow(digits as u32); - // Since factor cannot be zero, the division is safe. - Self(atomics / factor) - } - } - } - - /// Raises a value to the power of `exp`, panicking if an overflow occurs. - pub fn pow(self, exp: u32) -> Self { - // This uses the exponentiation by squaring algorithm: - // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method - - fn inner(mut x: Decimal256, mut n: u32) -> Decimal256 { - if n == 0 { - return Decimal256::one(); - } - - let mut y = Decimal256::one(); - - while n > 1 { - if n % 2 == 0 { - x = x * x; // Regular multiplication - n /= 2; - } else { - y = x * y; // Regular multiplication - x = x * x; // Regular multiplication - n = (n - 1) / 2; - } - } - - x * y - } - - inner(self, exp) - } - - /// Returns the multiplicative inverse `1/d` for decimal `d`. - /// - /// If `d` is zero, none is returned. - pub fn inv(&self) -> Option { - if self.is_zero() { - None - } else { - // Let self be p/q with p = self.0 and q = DECIMAL_FRACTIONAL. - // Now we calculate the inverse a/b = q/p such that b = DECIMAL_FRACTIONAL. Then - // `a = DECIMAL_FRACTIONAL*DECIMAL_FRACTIONAL / self.0`. - Some(Decimal256(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) - } - } - - /// Returns the ratio (numerator / denominator) as a Decimal256 - pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { - match Decimal256::checked_from_ratio(numerator, denominator) { - Ok(ratio) => ratio, - Err(Error::DivideByZero) => panic!("Denominator must not be zero"), - } - } - - pub fn to_I256_with_precision(&self, precision: impl Into) -> I256 { - let value = self.atomics(); - let precision = precision.into(); - - let divisor = 10I256.pow((self.decimal_places() - precision) as u32); - value / divisor - } - - fn multiply_ratio(&self, numerator: Decimal256, denominator: Decimal256) -> Decimal256 { - Decimal256::from_ratio(self.atomics() * numerator.atomics(), denominator.atomics()) - } - - /// Returns the ratio (numerator / denominator) as a Decimal256 - fn checked_from_ratio( - numerator: impl Into, - denominator: impl Into, - ) -> Result { - let numerator = numerator.into(); - let denominator = denominator.into(); - - // If denominator is zero, panic. - if denominator == 0 { - return Err(Error::DivideByZero); - } - - // Convert numerator and denominator to BigInt. - // unwrap since I256 is always convertible to BigInt - // let numerator = numerator.to_bigint().unwrap(); - // let denominator = denominator.to_bigint().unwrap(); - // let decimal_fractional = Self::DECIMAL_FRACTIONAL.to_bigint().unwrap(); - - // Compute the ratio: (numerator * DECIMAL_FRACTIONAL) / denominator - let ratio = (numerator * Self::DECIMAL_FRACTIONAL) / denominator; - // Convert back to I256. If conversion fails, panic. - // let ratio = ratio.to_I256().ok_or(Error::Overflow)?; - - // Construct and return the Decimal256. - Ok(Decimal256(ratio)) - } - - pub fn abs(&self) -> Self { - if self.0 < 0 { - Decimal256(-self.0) - } else { - *self - } - } - - pub fn to_string(&self, env: &Env) -> String { - String::from_str(env, alloc::format!("{}", self).as_str()) - } - - pub const fn abs_diff(self, other: Self) -> Self { - Self(self.0.abs_diff(other.0) as I256) - } -} - -impl Add for Decimal256 { - type Output = Self; - - fn add(self, other: Self) -> Self { - Decimal256(self.0 + other.0) - } + // Function to get DECIMAL_FRACTIONAL + pub fn decimal_fractional(env: &Env) -> I256 { + I256::from_i128(env, 1_000_000_000_000_000_000i128) // 1*10**18 + } + + // Function to get DECIMAL_FRACTIONAL_SQUARED + pub fn decimal_fractional_squared(env: &Env) -> I256 { + I256::from_i128(env, 1_000_000_000_000_000_000_000_000_000_000_000_000i128) + // (1*10**18)**2 = 1*10**36 + } + + // Function to get MAX value + pub fn max(env: &Env) -> Self { + Self(I256::from_i128(env, (1i128 << 127) - 1)) + } + + // Function to get MIN value + pub fn min(env: &Env) -> Self { + Self(I256::from_i128(env, -1i128 << 127)) + } + + // pub fn new(value: I256) -> Self { + // Decimal256(value) + // } + + // pub const fn raw(value: I256) -> Self { + // Self(value) + // } + + // /// Create a 1.0 Decimal256 + // #[inline] + // pub const fn one() -> Self { + // Self(Self::DECIMAL_FRACTIONAL) + // } + + // /// Create a 0.0 Decimal256 + // #[inline] + // pub const fn zero() -> Self { + // Self(I256::from_i32(&Env::default(), 0i32)) + // } + + // /// Convert x% into Decimal256 + // pub fn percent(x: i64) -> Self { + // Self(I256::from_i128(&Env::default(), (x as i128) * 10_000_000_000_000_000)) + // } + + // /// Convert permille (x/1000) into Decimal256 + // pub fn permille(x: i64) -> Self { + // Self(I256::from_i128(&Env::default(), (x as i128) * 1_000_000_000_000_000)) + // } + + // /// Convert basis points (x/10000) into Decimal256 + // pub fn bps(x: i64) -> Self { + // Self(I256::from_i128(&Env::default(), (x as i128) * 100_000_000_000_000)) + // } + + // /// The number of decimal places. This is a constant value for now + // /// but this could potentially change as the type evolves. + // /// + // /// See also [`Decimal256::atomics()`]. + // #[must_use] + // #[inline] + // pub const fn decimal_places(&self) -> i32 { + // Self::DECIMAL_PLACES + // } + + // #[inline] + // fn numerator(&self) -> I256 { + // self.0 + // } + + // #[inline] + // fn denominator(&self) -> I256 { + // Self::DECIMAL_FRACTIONAL + // } + + // #[must_use] + // pub const fn is_zero(&self) -> bool { + // self.0 == I256::from_i32(&Env::default(), 0i32) + // } + + // /// A decimal is an integer of atomic units plus a number that specifies the + // /// position of the decimal dot. So any decimal can be expressed as two numbers. + // /// + // /// ## Examples + // /// + // /// ``` + // /// use decimal::Decimal256; + // /// // Value with whole and fractional part + // /// let a = Decimal256::percent(123); + // /// assert_eq!(a.decimal_places(), 18); + // /// assert_eq!(a.atomics(), 1230000000000000000); + // /// + // /// // Smallest possible value + // /// let b = Decimal256::new(1); + // /// assert_eq!(b.decimal_places(), 18); + // /// assert_eq!(b.atomics(), 1); + // /// ``` + // #[must_use] + // #[inline] + // pub const fn atomics(&self) -> I256 { + // self.0 + // } + + // /// Creates a decimal from a number of atomic units and the number + // /// of decimal places. The inputs will be converted internally to form + // /// a decimal with 18 decimal places. So the input 1234 and 3 will create + // /// the decimal 1.234. + // /// + // /// Using 18 decimal places is slightly more efficient than other values + // /// as no internal conversion is necessary. + // /// + // /// ## Examples + // /// + // /// ``` + // /// use decimal::Decimal256; + // /// use soroban_sdk::{String, Env}; + // /// + // /// let e = Env::default(); + // /// let a = Decimal256::from_atomics(1234, 3); + // /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1.234")); + // /// + // /// let a = Decimal256::from_atomics(1234, 0); + // /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1234")); + // /// + // /// let a = Decimal256::from_atomics(1, 18); + // /// assert_eq!(a.to_string(&e), String::from_slice(&e, "0.000000000000000001")); + // /// ``` + // pub fn from_atomics(atomics: impl Into, decimal_places: i32) -> Self { + // let atomics: I256 = atomics.into(); + // const TEN: I256 = I256::from_be_bytes(&Env::default(), &Bytes::from_slice(&Env::default(), &[ + // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // 0, 0, 10, + // ])); + // match decimal_places.cmp(&Self::DECIMAL_PLACES) { + // Ordering::Less => { + // let digits = Self::DECIMAL_PLACES - decimal_places; + // let factor: I256 = TEN.pow(digits as u32); + // Self(atomics * factor) + // } + // Ordering::Equal => Self(atomics), + // Ordering::Greater => { + // let digits = decimal_places - Self::DECIMAL_PLACES; + // let factor = TEN.pow(digits as u32); + // // Since factor cannot be zero, the division is safe. + // Self(atomics / factor) + // } + // } + // } + + // /// Raises a value to the power of `exp`, panicking if an overflow occurs. + // pub fn pow(self, exp: u32) -> Self { + // // This uses the exponentiation by squaring algorithm: + // // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method + + // fn inner(mut x: Decimal256, mut n: u32) -> Decimal256 { + // if n == 0 { + // return Decimal256::one(); + // } + + // let mut y = Decimal256::one(); + + // while n > 1 { + // if n % 2 == 0 { + // x = x * x; // Regular multiplication + // n /= 2; + // } else { + // y = x * y; // Regular multiplication + // x = x * x; // Regular multiplication + // n = (n - 1) / 2; + // } + // } + + // x * y + // } + + // inner(self, exp) + // } + + // /// Returns the multiplicative inverse `1/d` for decimal `d`. + // /// + // /// If `d` is zero, none is returned. + // pub fn inv(&self) -> Option { + // if self.is_zero() { + // None + // } else { + // // Let self be p/q with p = self.0 and q = DECIMAL_FRACTIONAL. + // // Now we calculate the inverse a/b = q/p such that b = DECIMAL_FRACTIONAL. Then + // // `a = DECIMAL_FRACTIONAL*DECIMAL_FRACTIONAL / self.0`. + // Some(Decimal256(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) + // } + // } + + // /// Returns the ratio (numerator / denominator) as a Decimal256 + // pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + // match Decimal256::checked_from_ratio(numerator, denominator) { + // Ok(ratio) => ratio, + // Err(Error::DivideByZero) => panic!("Denominator must not be zero"), + // } + // } + + // pub fn to_I256_with_precision(&self, precision: impl Into) -> I256 { + // let value = self.atomics(); + // let precision = precision.into(); + + // let divisor = 10I256.pow((self.decimal_places() - precision) as u32); + // value / divisor + // } + + // fn multiply_ratio(&self, numerator: Decimal256, denominator: Decimal256) -> Decimal256 { + // Decimal256::from_ratio(self.atomics() * numerator.atomics(), denominator.atomics()) + // } + + // /// Returns the ratio (numerator / denominator) as a Decimal256 + // fn checked_from_ratio( + // numerator: impl Into, + // denominator: impl Into, + // ) -> Result { + // let numerator = numerator.into(); + // let denominator = denominator.into(); + + // // If denominator is zero, panic. + // if denominator == 0 { + // return Err(Error::DivideByZero); + // } + + // // Convert numerator and denominator to BigInt. + // // unwrap since I256 is always convertible to BigInt + // // let numerator = numerator.to_bigint().unwrap(); + // // let denominator = denominator.to_bigint().unwrap(); + // // let decimal_fractional = Self::DECIMAL_FRACTIONAL.to_bigint().unwrap(); + + // // Compute the ratio: (numerator * DECIMAL_FRACTIONAL) / denominator + // let ratio = (numerator * Self::DECIMAL_FRACTIONAL) / denominator; + + // // Convert back to I256. If conversion fails, panic. + // // let ratio = ratio.to_I256().ok_or(Error::Overflow)?; + + // // Construct and return the Decimal256. + // Ok(Decimal256(ratio)) + // } + + // pub fn abs(&self) -> Self { + // if self.0 < 0 { + // Decimal256(-self.0) + // } else { + // *self + // } + // } + + // pub fn to_string(&self, env: &Env) -> String { + // String::from_str(env, alloc::format!("{}", self).as_str()) + // } + + // pub const fn abs_diff(self, other: Self) -> Self { + // Self(self.0.abs_diff(other.0) as I256) + // } } -impl Sub for Decimal256 { - type Output = Self; - fn sub(self, other: Self) -> Self { - Decimal256(self.0 - other.0) - } -} - -impl Mul for Decimal256 { - type Output = Self; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, other: Self) -> Self { - // Decimal256s are fractions. We can multiply two decimals a and b - // via - // (a.numerator() * b.numerator()) / (a.denominator() * b.denominator()) - // = (a.numerator() * b.numerator()) / a.denominator() / b.denominator() - - // let self_numerator = self.numerator().to_bigint().unwrap(); - // let other_numerator = other.numerator().to_bigint().unwrap(); - - // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL - let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; - - // Convert the result back to I256, and panic on overflow - // let result = result - // .to_I256() - // .unwrap_or_else(|| panic!("attempt to multiply with overflow")); - - // Return a new Decimal256 - Decimal256(result) - } -} - -impl Div for Decimal256 { - type Output = Self; - - fn div(self, rhs: Self) -> Self { - match Decimal256::checked_from_ratio(self.numerator(), rhs.numerator()) { - Ok(ratio) => ratio, - Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), - } - } -} - -impl Mul for Decimal256 { - type Output = I256; - - fn mul(self, rhs: I256) -> Self::Output { - rhs * self - } -} - -impl Div for Decimal256 { - type Output = Self; - - fn div(self, rhs: I256) -> Self::Output { - Decimal256(self.0 / rhs) - } -} - -impl Mul for I256 { - type Output = Self; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, rhs: Decimal256) -> Self::Output { - // 0*a and b*0 is always 0 - if self == 0I256 || rhs.is_zero() { - return 0I256; - } - self * rhs.0 / Decimal256::DECIMAL_FRACTIONAL - } -} - -impl FromStr for Decimal256 { - type Err = (); - - fn from_str(input: &str) -> Result { - let mut parts_iter = input.split('.'); - - let whole_part = parts_iter.next().expect("Unexpected input format"); - let whole: I256 = whole_part.parse().expect("Error parsing whole"); - let mut atomics = whole * Self::DECIMAL_FRACTIONAL; - - if let Some(fractional_part) = parts_iter.next() { - let fractional: I256 = fractional_part.parse().expect("Error parsing fractional"); - let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; - assert!(exp >= 0, "There must be at least one fractional digit"); - let fractional_factor = 10I256.pow(exp as u32); - atomics += fractional * fractional_factor; - } - - assert!(parts_iter.next().is_none(), "Unexpected number of dots"); - - Ok(Decimal256(atomics)) - } -} - -impl fmt::Display for Decimal256 { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let whole = self.0 / Self::DECIMAL_FRACTIONAL; - let fractional = self.0 % Self::DECIMAL_FRACTIONAL; - - if fractional == 0 { - write!(f, "{}", whole) - } else { - let fractional_string = alloc::format!( - "{:0>padding$}", - fractional, - padding = Self::DECIMAL_PLACES as usize - ); - f.write_fmt(format_args!( - "{}.{}", - whole, - fractional_string.trim_end_matches('0') - )) - } - } -} +// impl Add for Decimal256 { +// type Output = Self; +// +// fn add(self, other: Self) -> Self { +// Decimal256(self.0 + other.0) +// } +// } +// impl Sub for Decimal256 { +// type Output = Self; +// +// fn sub(self, other: Self) -> Self { +// Decimal256(self.0 - other.0) +// } +// } +// +// impl Mul for Decimal256 { +// type Output = Self; +// +// #[allow(clippy::suspicious_arithmetic_impl)] +// fn mul(self, other: Self) -> Self { +// // Decimal256s are fractions. We can multiply two decimals a and b +// // via +// // (a.numerator() * b.numerator()) / (a.denominator() * b.denominator()) +// // = (a.numerator() * b.numerator()) / a.denominator() / b.denominator() +// +// // let self_numerator = self.numerator().to_bigint().unwrap(); +// // let other_numerator = other.numerator().to_bigint().unwrap(); +// +// // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL +// let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; +// +// // Convert the result back to I256, and panic on overflow +// // let result = result +// // .to_I256() +// // .unwrap_or_else(|| panic!("attempt to multiply with overflow")); +// +// // Return a new Decimal256 +// Decimal256(result) +// } +// } +// +// impl Div for Decimal256 { +// type Output = Self; +// +// fn div(self, rhs: Self) -> Self { +// match Decimal256::checked_from_ratio(self.numerator(), rhs.numerator()) { +// Ok(ratio) => ratio, +// Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), +// } +// } +// } +// +// impl Mul for Decimal256 { +// type Output = I256; +// +// fn mul(self, rhs: I256) -> Self::Output { +// rhs * self +// } +// } +// +// impl Div for Decimal256 { +// type Output = Self; +// +// fn div(self, rhs: I256) -> Self::Output { +// Decimal256(self.0 / rhs) +// } +// } +// +// impl Mul for I256 { +// type Output = Self; +// +// #[allow(clippy::suspicious_arithmetic_impl)] +// fn mul(self, rhs: Decimal256) -> Self::Output { +// // 0*a and b*0 is always 0 +// if self == 0I256 || rhs.is_zero() { +// return 0I256; +// } +// self * rhs.0 / Decimal256::DECIMAL_FRACTIONAL +// } +// } +// +// impl FromStr for Decimal256 { +// type Err = (); +// +// fn from_str(input: &str) -> Result { +// let mut parts_iter = input.split('.'); +// +// let whole_part = parts_iter.next().expect("Unexpected input format"); +// let whole: I256 = whole_part.parse().expect("Error parsing whole"); +// let mut atomics = whole * Self::DECIMAL_FRACTIONAL; +// +// if let Some(fractional_part) = parts_iter.next() { +// let fractional: I256 = fractional_part.parse().expect("Error parsing fractional"); +// let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; +// assert!(exp >= 0, "There must be at least one fractional digit"); +// let fractional_factor = 10I256.pow(exp as u32); +// atomics += fractional * fractional_factor; +// } +// +// assert!(parts_iter.next().is_none(), "Unexpected number of dots"); +// +// Ok(Decimal256(atomics)) +// } +// } +// +// impl fmt::Display for Decimal256 { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// let whole = self.0 / Self::DECIMAL_FRACTIONAL; +// let fractional = self.0 % Self::DECIMAL_FRACTIONAL; +// +// if fractional == 0 { +// write!(f, "{}", whole) +// } else { +// let fractional_string = alloc::format!( +// "{:0>padding$}", +// fractional, +// padding = Self::DECIMAL_PLACES as usize +// ); +// f.write_fmt(format_args!( +// "{}.{}", +// whole, +// fractional_string.trim_end_matches('0') +// )) +// } +// } +// } #[cfg(test)] mod tests { From 00bc90081e5f4396e07fb339ff460908d1bd1732 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 23 Apr 2024 17:47:14 +0200 Subject: [PATCH 04/25] Decimal: Update latest changes from the main branch --- packages/decimal/src/decimal.rs | 60 ++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/decimal/src/decimal.rs b/packages/decimal/src/decimal.rs index 6f1559804..794db50da 100644 --- a/packages/decimal/src/decimal.rs +++ b/packages/decimal/src/decimal.rs @@ -863,7 +863,7 @@ mod tests { #[test] fn decimal_abs_with_negative_number() { - let decimal = Decimal::new(128); + let decimal = Decimal::new(-128); assert_eq!(decimal.abs(), Decimal(128)); } @@ -923,4 +923,62 @@ mod tests { let value = Decimal::from_atomics(789, 4); assert_eq!(format!("{}", value), "0.0789"); } + + #[test] + fn test_denominator() { + let decimal = Decimal::percent(123); + assert_eq!(decimal.denominator(), Decimal::DECIMAL_FRACTIONAL); + } + + #[test] + fn test_atomics() { + let decimal = Decimal::percent(123); + assert_eq!(decimal.atomics(), 1230000000000000000); + } + + #[test] + fn test_to_i128_with_precision() { + let decimal = Decimal::percent(124); + assert_eq!(decimal.to_i128_with_precision(1), 12); + assert_eq!(decimal.to_i128_with_precision(2), 124); + } + + #[test] + fn test_multiply_ratio() { + let decimal = Decimal::percent(1); + let numerator = Decimal::new(2); + let denominator = Decimal::new(5); + + // decimal is 10_000_000_000_000_000, atomics would be same + // numerator is 20_000_000_000_000_000, atomics would be same + // denominator is 50_000_000_000_000_000, amount would be same + // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 + // decimal from ratio + // numerator 200_000_000_000_000_000_000_000_000_000 + // denominator = 50_000_000_000_000_000 + // numerator * DECIMAL_FRACTIONAL / denominator is the result + assert_eq!( + decimal.multiply_ratio(numerator, denominator), + Decimal::new(4000000000000000000000000000000000) + ); + } + + #[test] + fn test_abs_difference() { + let a = Decimal::new(100); + let b = Decimal::new(200); + + assert_eq!(a.abs_diff(b), Decimal::new(100)); + + let negative_a = Decimal::new(-100); + let negative_b = Decimal::new(-200); + + assert_eq!(negative_a.abs_diff(negative_b), Decimal::new(100)); + } + + #[test] + #[should_panic(expected = "There must be at least one fractional digit")] + fn test_from_str_with_too_many_fractional_digits() { + let _ = Decimal::from_str("123.12345678901187245619827346"); + } } From fddbe72e084d1d0af9b769468c55c8aa3378d467 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Mon, 6 May 2024 13:51:25 +0200 Subject: [PATCH 05/25] Decimal256: Expand implementation --- packages/decimal/src/decimal256.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 62ac42dfb..d06543a95 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -47,13 +47,13 @@ impl Decimal256 { Self(I256::from_i128(env, -1i128 << 127)) } - // pub fn new(value: I256) -> Self { - // Decimal256(value) - // } + pub fn new(value: I256) -> Self { + Decimal256(value) + } - // pub const fn raw(value: I256) -> Self { - // Self(value) - // } + pub const fn raw(value: I256) -> Self { + Self(value) + } // /// Create a 1.0 Decimal256 // #[inline] From c5a6e797844247c3154f8af06298fecc390aface Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Mon, 6 May 2024 14:04:41 +0200 Subject: [PATCH 06/25] Decimal256: Remove const from constructors --- packages/decimal/src/decimal256.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index d06543a95..8ef0b8361 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -21,6 +21,11 @@ enum Error { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] pub struct Decimal256(I256); +/// This implementation faced a lot of issues. +/// 1. U/I256 doesn't have a const contructors for some reason +/// 2. U/I256 requires an environment variable to be passed through +/// ...which results in a worse quality of the code. We'll try to work on that. + #[allow(dead_code)] impl Decimal256 { // Number of decimal places @@ -56,16 +61,16 @@ impl Decimal256 { } // /// Create a 1.0 Decimal256 - // #[inline] - // pub const fn one() -> Self { - // Self(Self::DECIMAL_FRACTIONAL) - // } + #[inline] + pub fn one(env: &Env) -> Self { + Self(Self::decimal_fractional(env)) + } // /// Create a 0.0 Decimal256 - // #[inline] - // pub const fn zero() -> Self { - // Self(I256::from_i32(&Env::default(), 0i32)) - // } + #[inline] + pub fn zero() -> Self { + Self(I256::from_i32(&Env::default(), 0i32)) + } // /// Convert x% into Decimal256 // pub fn percent(x: i64) -> Self { From 5f66c251fb26dd970216a4c28acdee490defc81a Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Mon, 6 May 2024 14:10:24 +0200 Subject: [PATCH 07/25] Decimal256: Working percent/permille/bps --- packages/decimal/src/decimal256.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 8ef0b8361..8dc24254e 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -73,19 +73,28 @@ impl Decimal256 { } // /// Convert x% into Decimal256 - // pub fn percent(x: i64) -> Self { - // Self(I256::from_i128(&Env::default(), (x as i128) * 10_000_000_000_000_000)) - // } + pub fn percent(x: i64) -> Self { + Self(I256::from_i128( + &Env::default(), + (x as i128) * 10_000_000_000_000_000, + )) + } // /// Convert permille (x/1000) into Decimal256 - // pub fn permille(x: i64) -> Self { - // Self(I256::from_i128(&Env::default(), (x as i128) * 1_000_000_000_000_000)) - // } + pub fn permille(x: i64) -> Self { + Self(I256::from_i128( + &Env::default(), + (x as i128) * 1_000_000_000_000_000, + )) + } // /// Convert basis points (x/10000) into Decimal256 - // pub fn bps(x: i64) -> Self { - // Self(I256::from_i128(&Env::default(), (x as i128) * 100_000_000_000_000)) - // } + pub fn bps(x: i64) -> Self { + Self(I256::from_i128( + &Env::default(), + (x as i128) * 100_000_000_000_000, + )) + } // /// The number of decimal places. This is a constant value for now // /// but this could potentially change as the type evolves. From 867d5c1a1e21e217c8162dc95ee80c5c5ef9cd7b Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Mon, 6 May 2024 14:47:00 +0200 Subject: [PATCH 08/25] Decimal256: Comment the tests and we decided to not follow with a now-impossible from_str implementation - because of the Env variable --- packages/decimal/src/decimal256.rs | 1258 ++++++++++++++-------------- 1 file changed, 629 insertions(+), 629 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 8dc24254e..a6f9cc515 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -42,15 +42,15 @@ impl Decimal256 { // (1*10**18)**2 = 1*10**36 } - // Function to get MAX value - pub fn max(env: &Env) -> Self { - Self(I256::from_i128(env, (1i128 << 127) - 1)) - } + // // Function to get MAX value + // pub fn max(env: &Env) -> Self { + // Self(I256::from_i128(env, (1i128 << 127) - 1)) + // } - // Function to get MIN value - pub fn min(env: &Env) -> Self { - Self(I256::from_i128(env, -1i128 << 127)) - } + // // Function to get MIN value + // pub fn min(env: &Env) -> Self { + // Self(I256::from_i128(env, -1i128 << 127)) + // } pub fn new(value: I256) -> Self { Decimal256(value) @@ -72,7 +72,7 @@ impl Decimal256 { Self(I256::from_i32(&Env::default(), 0i32)) } - // /// Convert x% into Decimal256 + /// Convert x% into Decimal256 pub fn percent(x: i64) -> Self { Self(I256::from_i128( &Env::default(), @@ -106,15 +106,15 @@ impl Decimal256 { // Self::DECIMAL_PLACES // } - // #[inline] - // fn numerator(&self) -> I256 { - // self.0 - // } + #[inline] + fn numerator(&self) -> I256 { + self.0.clone() + } - // #[inline] - // fn denominator(&self) -> I256 { - // Self::DECIMAL_FRACTIONAL - // } + #[inline] + fn denominator(&self, env: &Env) -> I256 { + Self::decimal_fractional(env) + } // #[must_use] // pub const fn is_zero(&self) -> bool { @@ -379,7 +379,7 @@ impl Decimal256 { // self * rhs.0 / Decimal256::DECIMAL_FRACTIONAL // } // } -// + // impl FromStr for Decimal256 { // type Err = (); // @@ -394,8 +394,8 @@ impl Decimal256 { // let fractional: I256 = fractional_part.parse().expect("Error parsing fractional"); // let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; // assert!(exp >= 0, "There must be at least one fractional digit"); -// let fractional_factor = 10I256.pow(exp as u32); -// atomics += fractional * fractional_factor; +// let fractional_factor = 10i128.pow(exp as u32); +// atomics += fractional * I256::from_i128(env, fractional_factor); // } // // assert!(parts_iter.next().is_none(), "Unexpected number of dots"); @@ -403,7 +403,7 @@ impl Decimal256 { // Ok(Decimal256(atomics)) // } // } -// + // impl fmt::Display for Decimal256 { // fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // let whole = self.0 / Self::DECIMAL_FRACTIONAL; @@ -426,611 +426,611 @@ impl Decimal256 { // } // } -#[cfg(test)] -mod tests { - use super::*; - use alloc::format; - - #[test] - fn decimal_new() { - let expected = 300i128; - assert_eq!(Decimal256::new(expected).0, expected); - } - - #[test] - fn decimal_raw() { - let value = 300i128; - assert_eq!(Decimal256::raw(value).0, value); - } - - #[test] - fn decimal_one() { - let value = Decimal256::one(); - assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL); - } - - #[test] - fn decimal_zero() { - let value = Decimal256::zero(); - assert_eq!(value.0, 0i128); - } - - #[test] - fn decimal_percent() { - let value = Decimal256::percent(50); - assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); - } - - #[test] - fn decimal_from_atomics_works() { - let one = Decimal256::one(); - let two = one + one; - - assert_eq!(Decimal256::from_atomics(1i128, 0), one); - assert_eq!(Decimal256::from_atomics(10i128, 1), one); - assert_eq!(Decimal256::from_atomics(100i128, 2), one); - assert_eq!(Decimal256::from_atomics(1000i128, 3), one); - assert_eq!(Decimal256::from_atomics(1000000000000000000i128, 18), one); - assert_eq!(Decimal256::from_atomics(10000000000000000000i128, 19), one); - assert_eq!(Decimal256::from_atomics(100000000000000000000i128, 20), one); - - assert_eq!(Decimal256::from_atomics(2i128, 0), two); - assert_eq!(Decimal256::from_atomics(20i128, 1), two); - assert_eq!(Decimal256::from_atomics(200i128, 2), two); - assert_eq!(Decimal256::from_atomics(2000i128, 3), two); - assert_eq!(Decimal256::from_atomics(2000000000000000000i128, 18), two); - assert_eq!(Decimal256::from_atomics(20000000000000000000i128, 19), two); - assert_eq!(Decimal256::from_atomics(200000000000000000000i128, 20), two); - - // Cuts decimal digits (20 provided but only 18 can be stored) - assert_eq!( - Decimal256::from_atomics(4321i128, 20), - Decimal256::from_str("0.000000000000000043").unwrap() - ); - assert_eq!( - Decimal256::from_atomics(6789i128, 20), - Decimal256::from_str("0.000000000000000067").unwrap() - ); - assert_eq!( - Decimal256::from_atomics(i128::MAX, 38), - Decimal256::from_str("1.701411834604692317").unwrap() - ); - assert_eq!( - Decimal256::from_atomics(i128::MAX, 39), - Decimal256::from_str("0.170141183460469231").unwrap() - ); - assert_eq!( - Decimal256::from_atomics(i128::MAX, 45), - Decimal256::from_str("0.000000170141183460").unwrap() - ); - assert_eq!( - Decimal256::from_atomics(i128::MAX, 51), - Decimal256::from_str("0.000000000000170141").unwrap() - ); - assert_eq!( - Decimal256::from_atomics(i128::MAX, 56), - Decimal256::from_str("0.000000000000000001").unwrap() - ); - } - - #[test] - fn decimal_from_ratio_works() { - // 1.0 - assert_eq!(Decimal256::from_ratio(1i128, 1i128), Decimal256::one()); - assert_eq!(Decimal256::from_ratio(53i128, 53i128), Decimal256::one()); - assert_eq!(Decimal256::from_ratio(125i128, 125i128), Decimal256::one()); - - // 1.5 - assert_eq!( - Decimal256::from_ratio(3i128, 2i128), - Decimal256::percent(150) - ); - assert_eq!( - Decimal256::from_ratio(150i128, 100i128), - Decimal256::percent(150) - ); - assert_eq!( - Decimal256::from_ratio(333i128, 222i128), - Decimal256::percent(150) - ); - - // 0.125 - assert_eq!( - Decimal256::from_ratio(1i64, 8i64), - Decimal256::permille(125) - ); - assert_eq!( - Decimal256::from_ratio(125i64, 1000i64), - Decimal256::permille(125) - ); - - // 1/3 (result floored) - assert_eq!( - Decimal256::from_ratio(1i128, 3i128), - Decimal256(333_333_333_333_333_333i128) - ); - - // 2/3 (result floored) - assert_eq!( - Decimal256::from_ratio(2i128, 3i128), - Decimal256(666_666_666_666_666_666i128) - ); - - // large inputs - assert_eq!(Decimal256::from_ratio(0i128, i128::MAX), Decimal256::zero()); - // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); - - // due to limited possibilities - we're only allowed to use i128 as input - maximum - // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL - // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. - assert_eq!( - Decimal256::from_ratio(340282366920938i128, 340282366920938i128), - Decimal256::one() - ); - // This works because of similar orders of magnitude - assert_eq!( - Decimal256::from_ratio(34028236692093900000i128, 34028236692093900000i128), - Decimal256::one() - ); - assert_eq!( - Decimal256::from_ratio(34028236692093900000i128, 1i128), - Decimal256::new(34028236692093900000i128 * Decimal256::DECIMAL_FRACTIONAL) - ); - } - - #[test] - #[should_panic(expected = "Denominator must not be zero")] - fn decimal_from_ratio_panics_for_zero_denominator() { - Decimal256::from_ratio(1i128, 0i128); - } - - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_from_ratio_panics_for_mul_overflow() { - Decimal256::from_ratio(i128::MAX, 1i128); - } - - #[test] - fn decimal_decimal_places_works() { - let zero = Decimal256::zero(); - let one = Decimal256::one(); - let half = Decimal256::percent(50); - let two = Decimal256::percent(200); - let max = Decimal256::MAX; - - assert_eq!(zero.decimal_places(), 18); - assert_eq!(one.decimal_places(), 18); - assert_eq!(half.decimal_places(), 18); - assert_eq!(two.decimal_places(), 18); - assert_eq!(max.decimal_places(), 18); - } - - #[test] - fn decimal_from_str_works() { - // Integers - assert_eq!(Decimal256::from_str("0").unwrap(), Decimal256::percent(0)); - assert_eq!(Decimal256::from_str("1").unwrap(), Decimal256::percent(100)); - assert_eq!(Decimal256::from_str("5").unwrap(), Decimal256::percent(500)); - assert_eq!( - Decimal256::from_str("42").unwrap(), - Decimal256::percent(4200) - ); - assert_eq!(Decimal256::from_str("000").unwrap(), Decimal256::percent(0)); - assert_eq!( - Decimal256::from_str("001").unwrap(), - Decimal256::percent(100) - ); - assert_eq!( - Decimal256::from_str("005").unwrap(), - Decimal256::percent(500) - ); - assert_eq!( - Decimal256::from_str("0042").unwrap(), - Decimal256::percent(4200) - ); - - // Decimal256s - assert_eq!( - Decimal256::from_str("1.0").unwrap(), - Decimal256::percent(100) - ); - assert_eq!( - Decimal256::from_str("1.5").unwrap(), - Decimal256::percent(150) - ); - assert_eq!( - Decimal256::from_str("0.5").unwrap(), - Decimal256::percent(50) - ); - assert_eq!( - Decimal256::from_str("0.123").unwrap(), - Decimal256::permille(123) - ); - - assert_eq!( - Decimal256::from_str("40.00").unwrap(), - Decimal256::percent(4000) - ); - assert_eq!( - Decimal256::from_str("04.00").unwrap(), - Decimal256::percent(400) - ); - assert_eq!( - Decimal256::from_str("00.40").unwrap(), - Decimal256::percent(40) - ); - assert_eq!( - Decimal256::from_str("00.04").unwrap(), - Decimal256::percent(4) - ); - - // Can handle DECIMAL_PLACES fractional digits - assert_eq!( - Decimal256::from_str("7.123456789012345678").unwrap(), - Decimal256(7123456789012345678i128) - ); - assert_eq!( - Decimal256::from_str("7.999999999999999999").unwrap(), - Decimal256(7999999999999999999i128) - ); - } - - #[test] - fn decimal_is_zero_works() { - assert!(Decimal256::zero().is_zero()); - assert!(Decimal256::percent(0).is_zero()); - assert!(Decimal256::permille(0).is_zero()); - - assert!(!Decimal256::one().is_zero()); - assert!(!Decimal256::percent(123).is_zero()); - assert!(!Decimal256::permille(1234).is_zero()); - } - - #[test] - fn decimal_inv_works() { - // d = 0 - assert_eq!(Decimal256::zero().inv(), None); - - // d == 1 - assert_eq!(Decimal256::one().inv(), Some(Decimal256::one())); - - // d > 1 exact - assert_eq!( - Decimal256::percent(200).inv(), - Some(Decimal256::percent(50)) - ); - assert_eq!( - Decimal256::percent(2_000).inv(), - Some(Decimal256::percent(5)) - ); - assert_eq!( - Decimal256::percent(20_000).inv(), - Some(Decimal256::permille(5)) - ); - assert_eq!(Decimal256::percent(200_000).inv(), Some(Decimal256::bps(5))); - - // d > 1 rounded - assert_eq!( - Decimal256::percent(300).inv(), - Some(Decimal256::from_ratio(1i128, 3i128)) - ); - assert_eq!( - Decimal256::percent(600).inv(), - Some(Decimal256::from_ratio(1i128, 6i128)) - ); - - // d < 1 exact - assert_eq!( - Decimal256::percent(50).inv(), - Some(Decimal256::percent(200)) - ); - assert_eq!( - Decimal256::percent(5).inv(), - Some(Decimal256::percent(2_000)) - ); - assert_eq!( - Decimal256::permille(5).inv(), - Some(Decimal256::percent(20_000)) - ); - assert_eq!(Decimal256::bps(5).inv(), Some(Decimal256::percent(200_000))); - } - - #[test] - fn decimal_add_works() { - let value = Decimal256::one() + Decimal256::percent(50); // 1.5 - assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL * 3i128 / 2i128); - - assert_eq!( - Decimal256::percent(5) + Decimal256::percent(4), - Decimal256::percent(9) - ); - assert_eq!( - Decimal256::percent(5) + Decimal256::zero(), - Decimal256::percent(5) - ); - assert_eq!(Decimal256::zero() + Decimal256::zero(), Decimal256::zero()); - } - - #[test] - #[should_panic(expected = "attempt to add with overflow")] - fn decimal_add_overflow_panics() { - let _value = Decimal256::MAX + Decimal256::percent(50); - } - - #[test] - fn decimal_sub_works() { - let value = Decimal256::one() - Decimal256::percent(50); // 0.5 - assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); - - assert_eq!( - Decimal256::percent(9) - Decimal256::percent(4), - Decimal256::percent(5) - ); - assert_eq!( - Decimal256::percent(16) - Decimal256::zero(), - Decimal256::percent(16) - ); - assert_eq!( - Decimal256::percent(16) - Decimal256::percent(16), - Decimal256::zero() - ); - assert_eq!(Decimal256::zero() - Decimal256::zero(), Decimal256::zero()); - } - - #[test] - fn decimal_implements_mul() { - let one = Decimal256::one(); - let two = one + one; - let half = Decimal256::percent(50); - - // 1*x and x*1 - assert_eq!(one * Decimal256::percent(0), Decimal256::percent(0)); - assert_eq!(one * Decimal256::percent(1), Decimal256::percent(1)); - assert_eq!(one * Decimal256::percent(10), Decimal256::percent(10)); - assert_eq!(one * Decimal256::percent(100), Decimal256::percent(100)); - assert_eq!(one * Decimal256::percent(1000), Decimal256::percent(1000)); - // assert_eq!(one * Decimal256::MAX, Decimal256::MAX); - assert_eq!(Decimal256::percent(0) * one, Decimal256::percent(0)); - assert_eq!(Decimal256::percent(1) * one, Decimal256::percent(1)); - assert_eq!(Decimal256::percent(10) * one, Decimal256::percent(10)); - assert_eq!(Decimal256::percent(100) * one, Decimal256::percent(100)); - assert_eq!(Decimal256::percent(1000) * one, Decimal256::percent(1000)); - // assert_eq!(Decimal256::MAX * one, Decimal256::MAX); - - // double - assert_eq!(two * Decimal256::percent(0), Decimal256::percent(0)); - assert_eq!(two * Decimal256::percent(1), Decimal256::percent(2)); - assert_eq!(two * Decimal256::percent(10), Decimal256::percent(20)); - assert_eq!(two * Decimal256::percent(100), Decimal256::percent(200)); - assert_eq!(two * Decimal256::percent(1000), Decimal256::percent(2000)); - assert_eq!(Decimal256::percent(0) * two, Decimal256::percent(0)); - assert_eq!(Decimal256::percent(1) * two, Decimal256::percent(2)); - assert_eq!(Decimal256::percent(10) * two, Decimal256::percent(20)); - assert_eq!(Decimal256::percent(100) * two, Decimal256::percent(200)); - assert_eq!(Decimal256::percent(1000) * two, Decimal256::percent(2000)); - - // half - assert_eq!(half * Decimal256::percent(0), Decimal256::percent(0)); - assert_eq!(half * Decimal256::percent(1), Decimal256::permille(5)); - assert_eq!(half * Decimal256::percent(10), Decimal256::percent(5)); - assert_eq!(half * Decimal256::percent(100), Decimal256::percent(50)); - assert_eq!(half * Decimal256::percent(1000), Decimal256::percent(500)); - assert_eq!(Decimal256::percent(0) * half, Decimal256::percent(0)); - assert_eq!(Decimal256::percent(1) * half, Decimal256::permille(5)); - assert_eq!(Decimal256::percent(10) * half, Decimal256::percent(5)); - assert_eq!(Decimal256::percent(100) * half, Decimal256::percent(50)); - assert_eq!(Decimal256::percent(1000) * half, Decimal256::percent(500)); - } - - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_mul_overflow_panics() { - let _value = Decimal256::MAX * Decimal256::percent(101); - } - - #[test] - // in this test the Decimal256 is on the right - fn i128_decimal_multiply() { - // a*b - let left = 300i128; - let right = Decimal256::one() + Decimal256::percent(50); // 1.5 - assert_eq!(left * right, 450i128); - - // a*0 - let left = 300i128; - let right = Decimal256::zero(); - assert_eq!(left * right, 0i128); - - // 0*a - let left = 0i128; - let right = Decimal256::one() + Decimal256::percent(50); // 1.5 - assert_eq!(left * right, 0i128); - - assert_eq!(0i128 * Decimal256::one(), 0i128); - assert_eq!(1i128 * Decimal256::one(), 1i128); - assert_eq!(2i128 * Decimal256::one(), 2i128); - - assert_eq!(1i128 * Decimal256::percent(10), 0i128); - assert_eq!(10i128 * Decimal256::percent(10), 1i128); - assert_eq!(100i128 * Decimal256::percent(10), 10i128); - - assert_eq!(1i128 * Decimal256::percent(50), 0i128); - assert_eq!(100i128 * Decimal256::percent(50), 50i128); - assert_eq!(3200i128 * Decimal256::percent(50), 1600i128); - assert_eq!(999i128 * Decimal256::percent(50), 499i128); // default rounding down - - assert_eq!(1i128 * Decimal256::percent(200), 2i128); - assert_eq!(1000i128 * Decimal256::percent(200), 2000i128); - } - - #[test] - // in this test the Decimal256 is on the left - fn decimal_i128_multiply() { - // a*b - let left = Decimal256::one() + Decimal256::percent(50); // 1.5 - let right = 300i128; - assert_eq!(left * right, 450i128); - - // 0*a - let left = Decimal256::zero(); - let right = 300i128; - assert_eq!(left * right, 0i128); - - // a*0 - let left = Decimal256::one() + Decimal256::percent(50); // 1.5 - let right = 0i128; - assert_eq!(left * right, 0i128); - } - - #[test] - fn decimal_implements_div() { - let one = Decimal256::one(); - let two = one + one; - let half = Decimal256::percent(50); - - // 1/x and x/1 - assert_eq!(one / Decimal256::percent(1), Decimal256::percent(10_000)); - assert_eq!(one / Decimal256::percent(10), Decimal256::percent(1_000)); - assert_eq!(one / Decimal256::percent(100), Decimal256::percent(100)); - assert_eq!(one / Decimal256::percent(1000), Decimal256::percent(10)); - assert_eq!(Decimal256::percent(0) / one, Decimal256::percent(0)); - assert_eq!(Decimal256::percent(1) / one, Decimal256::percent(1)); - assert_eq!(Decimal256::percent(10) / one, Decimal256::percent(10)); - assert_eq!(Decimal256::percent(100) / one, Decimal256::percent(100)); - assert_eq!(Decimal256::percent(1000) / one, Decimal256::percent(1000)); - - // double - assert_eq!(two / Decimal256::percent(1), Decimal256::percent(20_000)); - assert_eq!(two / Decimal256::percent(10), Decimal256::percent(2_000)); - assert_eq!(two / Decimal256::percent(100), Decimal256::percent(200)); - assert_eq!(two / Decimal256::percent(1000), Decimal256::percent(20)); - assert_eq!(Decimal256::percent(0) / two, Decimal256::percent(0)); - assert_eq!(Decimal256::percent(10) / two, Decimal256::percent(5)); - assert_eq!(Decimal256::percent(100) / two, Decimal256::percent(50)); - assert_eq!(Decimal256::percent(1000) / two, Decimal256::percent(500)); - - // half - assert_eq!(half / Decimal256::percent(1), Decimal256::percent(5_000)); - assert_eq!(half / Decimal256::percent(10), Decimal256::percent(500)); - assert_eq!(half / Decimal256::percent(100), Decimal256::percent(50)); - assert_eq!(half / Decimal256::percent(1000), Decimal256::percent(5)); - assert_eq!(Decimal256::percent(0) / half, Decimal256::percent(0)); - assert_eq!(Decimal256::percent(1) / half, Decimal256::percent(2)); - assert_eq!(Decimal256::percent(10) / half, Decimal256::percent(20)); - assert_eq!(Decimal256::percent(100) / half, Decimal256::percent(200)); - assert_eq!(Decimal256::percent(1000) / half, Decimal256::percent(2000)); - - assert_eq!( - Decimal256::percent(15) / Decimal256::percent(60), - Decimal256::percent(25) - ); - } - - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_div_overflow_panics() { - let _value = Decimal256::MAX / Decimal256::percent(10); - } - - #[test] - #[should_panic(expected = "Division failed - denominator must not be zero")] - fn decimal_div_by_zero_panics() { - let _value = Decimal256::one() / Decimal256::zero(); - } - - #[test] - fn decimal_i128_division() { - // a/b - let left = Decimal256::percent(150); // 1.5 - let right = 3i128; - assert_eq!(left / right, Decimal256::percent(50)); - - // 0/a - let left = Decimal256::zero(); - let right = 300i128; - assert_eq!(left / right, Decimal256::zero()); - } - - #[test] - #[should_panic(expected = "attempt to divide by zero")] - fn decimal_uint128_divide_by_zero() { - let left = Decimal256::percent(150); // 1.5 - let right = 0i128; - let _result = left / right; - } - - #[test] - fn decimal_pow_works() { - assert_eq!(Decimal256::percent(200).pow(2), Decimal256::percent(400)); - assert_eq!(Decimal256::percent(100).pow(10), Decimal256::percent(100)); - } - - #[test] - #[should_panic] - fn decimal_pow_overflow_panics() { - _ = Decimal256::MAX.pow(2u32); - } - - #[test] - fn decimal_abs_with_negative_number() { - let decimal = Decimal256::new(128); - - assert_eq!(decimal.abs(), Decimal256(128)); - } - - #[test] - fn decimal_abs_with_positive_number() { - let decimal = Decimal256::new(128); - - assert_eq!(decimal.abs(), Decimal256(128)); - } - - #[test] - fn decimal_displayed_as_string() { - let env = Env::default(); - let decimal = Decimal256::percent(128); - - // Convert expected string to Soroban SDK String - let expected_msg = "1.28"; - let expected_string = String::from_str(&env, expected_msg); - - // Convert decimal to String and get its byte representation - let result_string = decimal.to_string(&env); - let result_string_len = result_string.len() as usize; - let mut result_bytes = alloc::vec![0u8; result_string_len]; - result_string.copy_into_slice(&mut result_bytes); - - // Get byte representation of expected string - let expected_string_len = expected_string.len() as usize; - let mut expected_bytes = alloc::vec![0u8; expected_string_len]; - expected_string.copy_into_slice(&mut expected_bytes); - - assert_eq!(result_bytes, expected_bytes); - } - - #[test] - fn decimal_fmt_without_fractional_part() { - let value = Decimal256::from_atomics(100, 0); - assert_eq!(format!("{}", value), "100"); - } - - #[test] - fn decimal_fmt_fractional_part() { - let value = Decimal256::from_atomics(123456789, 5); - assert_eq!(format!("{}", value), "1234.56789"); - } - - #[test] - fn decimal_fmt_fractional_part_with_trailing_zeros() { - // 12345.6 - let value = Decimal256::from_atomics(123456, 1); - assert_eq!(format!("{}", value), "12345.6"); - } - - #[test] - fn decimal_fmt_only_fractional() { - // 0.0789 - let value = Decimal256::from_atomics(789, 4); - assert_eq!(format!("{}", value), "0.0789"); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use alloc::format; +// +// #[test] +// fn decimal_new() { +// let expected = 300i128; +// assert_eq!(Decimal256::new(expected).0, expected); +// } +// +// #[test] +// fn decimal_raw() { +// let value = 300i128; +// assert_eq!(Decimal256::raw(value).0, value); +// } +// +// #[test] +// fn decimal_one() { +// let value = Decimal256::one(); +// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL); +// } +// +// #[test] +// fn decimal_zero() { +// let value = Decimal256::zero(); +// assert_eq!(value.0, 0i128); +// } +// +// #[test] +// fn decimal_percent() { +// let value = Decimal256::percent(50); +// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); +// } +// +// #[test] +// fn decimal_from_atomics_works() { +// let one = Decimal256::one(); +// let two = one + one; +// +// assert_eq!(Decimal256::from_atomics(1i128, 0), one); +// assert_eq!(Decimal256::from_atomics(10i128, 1), one); +// assert_eq!(Decimal256::from_atomics(100i128, 2), one); +// assert_eq!(Decimal256::from_atomics(1000i128, 3), one); +// assert_eq!(Decimal256::from_atomics(1000000000000000000i128, 18), one); +// assert_eq!(Decimal256::from_atomics(10000000000000000000i128, 19), one); +// assert_eq!(Decimal256::from_atomics(100000000000000000000i128, 20), one); +// +// assert_eq!(Decimal256::from_atomics(2i128, 0), two); +// assert_eq!(Decimal256::from_atomics(20i128, 1), two); +// assert_eq!(Decimal256::from_atomics(200i128, 2), two); +// assert_eq!(Decimal256::from_atomics(2000i128, 3), two); +// assert_eq!(Decimal256::from_atomics(2000000000000000000i128, 18), two); +// assert_eq!(Decimal256::from_atomics(20000000000000000000i128, 19), two); +// assert_eq!(Decimal256::from_atomics(200000000000000000000i128, 20), two); +// +// // Cuts decimal digits (20 provided but only 18 can be stored) +// assert_eq!( +// Decimal256::from_atomics(4321i128, 20), +// Decimal256::from_str("0.000000000000000043").unwrap() +// ); +// assert_eq!( +// Decimal256::from_atomics(6789i128, 20), +// Decimal256::from_str("0.000000000000000067").unwrap() +// ); +// assert_eq!( +// Decimal256::from_atomics(i128::MAX, 38), +// Decimal256::from_str("1.701411834604692317").unwrap() +// ); +// assert_eq!( +// Decimal256::from_atomics(i128::MAX, 39), +// Decimal256::from_str("0.170141183460469231").unwrap() +// ); +// assert_eq!( +// Decimal256::from_atomics(i128::MAX, 45), +// Decimal256::from_str("0.000000170141183460").unwrap() +// ); +// assert_eq!( +// Decimal256::from_atomics(i128::MAX, 51), +// Decimal256::from_str("0.000000000000170141").unwrap() +// ); +// assert_eq!( +// Decimal256::from_atomics(i128::MAX, 56), +// Decimal256::from_str("0.000000000000000001").unwrap() +// ); +// } +// +// #[test] +// fn decimal_from_ratio_works() { +// // 1.0 +// assert_eq!(Decimal256::from_ratio(1i128, 1i128), Decimal256::one()); +// assert_eq!(Decimal256::from_ratio(53i128, 53i128), Decimal256::one()); +// assert_eq!(Decimal256::from_ratio(125i128, 125i128), Decimal256::one()); +// +// // 1.5 +// assert_eq!( +// Decimal256::from_ratio(3i128, 2i128), +// Decimal256::percent(150) +// ); +// assert_eq!( +// Decimal256::from_ratio(150i128, 100i128), +// Decimal256::percent(150) +// ); +// assert_eq!( +// Decimal256::from_ratio(333i128, 222i128), +// Decimal256::percent(150) +// ); +// +// // 0.125 +// assert_eq!( +// Decimal256::from_ratio(1i64, 8i64), +// Decimal256::permille(125) +// ); +// assert_eq!( +// Decimal256::from_ratio(125i64, 1000i64), +// Decimal256::permille(125) +// ); +// +// // 1/3 (result floored) +// assert_eq!( +// Decimal256::from_ratio(1i128, 3i128), +// Decimal256(333_333_333_333_333_333i128) +// ); +// +// // 2/3 (result floored) +// assert_eq!( +// Decimal256::from_ratio(2i128, 3i128), +// Decimal256(666_666_666_666_666_666i128) +// ); +// +// // large inputs +// assert_eq!(Decimal256::from_ratio(0i128, i128::MAX), Decimal256::zero()); +// // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); +// +// // due to limited possibilities - we're only allowed to use i128 as input - maximum +// // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL +// // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. +// assert_eq!( +// Decimal256::from_ratio(340282366920938i128, 340282366920938i128), +// Decimal256::one() +// ); +// // This works because of similar orders of magnitude +// assert_eq!( +// Decimal256::from_ratio(34028236692093900000i128, 34028236692093900000i128), +// Decimal256::one() +// ); +// assert_eq!( +// Decimal256::from_ratio(34028236692093900000i128, 1i128), +// Decimal256::new(34028236692093900000i128 * Decimal256::DECIMAL_FRACTIONAL) +// ); +// } +// +// #[test] +// #[should_panic(expected = "Denominator must not be zero")] +// fn decimal_from_ratio_panics_for_zero_denominator() { +// Decimal256::from_ratio(1i128, 0i128); +// } +// +// #[test] +// #[should_panic(expected = "attempt to multiply with overflow")] +// fn decimal_from_ratio_panics_for_mul_overflow() { +// Decimal256::from_ratio(i128::MAX, 1i128); +// } +// +// #[test] +// fn decimal_decimal_places_works() { +// let zero = Decimal256::zero(); +// let one = Decimal256::one(); +// let half = Decimal256::percent(50); +// let two = Decimal256::percent(200); +// let max = Decimal256::MAX; +// +// assert_eq!(zero.decimal_places(), 18); +// assert_eq!(one.decimal_places(), 18); +// assert_eq!(half.decimal_places(), 18); +// assert_eq!(two.decimal_places(), 18); +// assert_eq!(max.decimal_places(), 18); +// } +// +// #[test] +// fn decimal_from_str_works() { +// // Integers +// assert_eq!(Decimal256::from_str("0").unwrap(), Decimal256::percent(0)); +// assert_eq!(Decimal256::from_str("1").unwrap(), Decimal256::percent(100)); +// assert_eq!(Decimal256::from_str("5").unwrap(), Decimal256::percent(500)); +// assert_eq!( +// Decimal256::from_str("42").unwrap(), +// Decimal256::percent(4200) +// ); +// assert_eq!(Decimal256::from_str("000").unwrap(), Decimal256::percent(0)); +// assert_eq!( +// Decimal256::from_str("001").unwrap(), +// Decimal256::percent(100) +// ); +// assert_eq!( +// Decimal256::from_str("005").unwrap(), +// Decimal256::percent(500) +// ); +// assert_eq!( +// Decimal256::from_str("0042").unwrap(), +// Decimal256::percent(4200) +// ); +// +// // Decimal256s +// assert_eq!( +// Decimal256::from_str("1.0").unwrap(), +// Decimal256::percent(100) +// ); +// assert_eq!( +// Decimal256::from_str("1.5").unwrap(), +// Decimal256::percent(150) +// ); +// assert_eq!( +// Decimal256::from_str("0.5").unwrap(), +// Decimal256::percent(50) +// ); +// assert_eq!( +// Decimal256::from_str("0.123").unwrap(), +// Decimal256::permille(123) +// ); +// +// assert_eq!( +// Decimal256::from_str("40.00").unwrap(), +// Decimal256::percent(4000) +// ); +// assert_eq!( +// Decimal256::from_str("04.00").unwrap(), +// Decimal256::percent(400) +// ); +// assert_eq!( +// Decimal256::from_str("00.40").unwrap(), +// Decimal256::percent(40) +// ); +// assert_eq!( +// Decimal256::from_str("00.04").unwrap(), +// Decimal256::percent(4) +// ); +// +// // Can handle DECIMAL_PLACES fractional digits +// assert_eq!( +// Decimal256::from_str("7.123456789012345678").unwrap(), +// Decimal256(7123456789012345678i128) +// ); +// assert_eq!( +// Decimal256::from_str("7.999999999999999999").unwrap(), +// Decimal256(7999999999999999999i128) +// ); +// } +// +// #[test] +// fn decimal_is_zero_works() { +// assert!(Decimal256::zero().is_zero()); +// assert!(Decimal256::percent(0).is_zero()); +// assert!(Decimal256::permille(0).is_zero()); +// +// assert!(!Decimal256::one().is_zero()); +// assert!(!Decimal256::percent(123).is_zero()); +// assert!(!Decimal256::permille(1234).is_zero()); +// } +// +// #[test] +// fn decimal_inv_works() { +// // d = 0 +// assert_eq!(Decimal256::zero().inv(), None); +// +// // d == 1 +// assert_eq!(Decimal256::one().inv(), Some(Decimal256::one())); +// +// // d > 1 exact +// assert_eq!( +// Decimal256::percent(200).inv(), +// Some(Decimal256::percent(50)) +// ); +// assert_eq!( +// Decimal256::percent(2_000).inv(), +// Some(Decimal256::percent(5)) +// ); +// assert_eq!( +// Decimal256::percent(20_000).inv(), +// Some(Decimal256::permille(5)) +// ); +// assert_eq!(Decimal256::percent(200_000).inv(), Some(Decimal256::bps(5))); +// +// // d > 1 rounded +// assert_eq!( +// Decimal256::percent(300).inv(), +// Some(Decimal256::from_ratio(1i128, 3i128)) +// ); +// assert_eq!( +// Decimal256::percent(600).inv(), +// Some(Decimal256::from_ratio(1i128, 6i128)) +// ); +// +// // d < 1 exact +// assert_eq!( +// Decimal256::percent(50).inv(), +// Some(Decimal256::percent(200)) +// ); +// assert_eq!( +// Decimal256::percent(5).inv(), +// Some(Decimal256::percent(2_000)) +// ); +// assert_eq!( +// Decimal256::permille(5).inv(), +// Some(Decimal256::percent(20_000)) +// ); +// assert_eq!(Decimal256::bps(5).inv(), Some(Decimal256::percent(200_000))); +// } +// +// #[test] +// fn decimal_add_works() { +// let value = Decimal256::one() + Decimal256::percent(50); // 1.5 +// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL * 3i128 / 2i128); +// +// assert_eq!( +// Decimal256::percent(5) + Decimal256::percent(4), +// Decimal256::percent(9) +// ); +// assert_eq!( +// Decimal256::percent(5) + Decimal256::zero(), +// Decimal256::percent(5) +// ); +// assert_eq!(Decimal256::zero() + Decimal256::zero(), Decimal256::zero()); +// } +// +// #[test] +// #[should_panic(expected = "attempt to add with overflow")] +// fn decimal_add_overflow_panics() { +// let _value = Decimal256::MAX + Decimal256::percent(50); +// } +// +// #[test] +// fn decimal_sub_works() { +// let value = Decimal256::one() - Decimal256::percent(50); // 0.5 +// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); +// +// assert_eq!( +// Decimal256::percent(9) - Decimal256::percent(4), +// Decimal256::percent(5) +// ); +// assert_eq!( +// Decimal256::percent(16) - Decimal256::zero(), +// Decimal256::percent(16) +// ); +// assert_eq!( +// Decimal256::percent(16) - Decimal256::percent(16), +// Decimal256::zero() +// ); +// assert_eq!(Decimal256::zero() - Decimal256::zero(), Decimal256::zero()); +// } +// +// #[test] +// fn decimal_implements_mul() { +// let one = Decimal256::one(); +// let two = one + one; +// let half = Decimal256::percent(50); +// +// // 1*x and x*1 +// assert_eq!(one * Decimal256::percent(0), Decimal256::percent(0)); +// assert_eq!(one * Decimal256::percent(1), Decimal256::percent(1)); +// assert_eq!(one * Decimal256::percent(10), Decimal256::percent(10)); +// assert_eq!(one * Decimal256::percent(100), Decimal256::percent(100)); +// assert_eq!(one * Decimal256::percent(1000), Decimal256::percent(1000)); +// // assert_eq!(one * Decimal256::MAX, Decimal256::MAX); +// assert_eq!(Decimal256::percent(0) * one, Decimal256::percent(0)); +// assert_eq!(Decimal256::percent(1) * one, Decimal256::percent(1)); +// assert_eq!(Decimal256::percent(10) * one, Decimal256::percent(10)); +// assert_eq!(Decimal256::percent(100) * one, Decimal256::percent(100)); +// assert_eq!(Decimal256::percent(1000) * one, Decimal256::percent(1000)); +// // assert_eq!(Decimal256::MAX * one, Decimal256::MAX); +// +// // double +// assert_eq!(two * Decimal256::percent(0), Decimal256::percent(0)); +// assert_eq!(two * Decimal256::percent(1), Decimal256::percent(2)); +// assert_eq!(two * Decimal256::percent(10), Decimal256::percent(20)); +// assert_eq!(two * Decimal256::percent(100), Decimal256::percent(200)); +// assert_eq!(two * Decimal256::percent(1000), Decimal256::percent(2000)); +// assert_eq!(Decimal256::percent(0) * two, Decimal256::percent(0)); +// assert_eq!(Decimal256::percent(1) * two, Decimal256::percent(2)); +// assert_eq!(Decimal256::percent(10) * two, Decimal256::percent(20)); +// assert_eq!(Decimal256::percent(100) * two, Decimal256::percent(200)); +// assert_eq!(Decimal256::percent(1000) * two, Decimal256::percent(2000)); +// +// // half +// assert_eq!(half * Decimal256::percent(0), Decimal256::percent(0)); +// assert_eq!(half * Decimal256::percent(1), Decimal256::permille(5)); +// assert_eq!(half * Decimal256::percent(10), Decimal256::percent(5)); +// assert_eq!(half * Decimal256::percent(100), Decimal256::percent(50)); +// assert_eq!(half * Decimal256::percent(1000), Decimal256::percent(500)); +// assert_eq!(Decimal256::percent(0) * half, Decimal256::percent(0)); +// assert_eq!(Decimal256::percent(1) * half, Decimal256::permille(5)); +// assert_eq!(Decimal256::percent(10) * half, Decimal256::percent(5)); +// assert_eq!(Decimal256::percent(100) * half, Decimal256::percent(50)); +// assert_eq!(Decimal256::percent(1000) * half, Decimal256::percent(500)); +// } +// +// #[test] +// #[should_panic(expected = "attempt to multiply with overflow")] +// fn decimal_mul_overflow_panics() { +// let _value = Decimal256::MAX * Decimal256::percent(101); +// } +// +// #[test] +// // in this test the Decimal256 is on the right +// fn i128_decimal_multiply() { +// // a*b +// let left = 300i128; +// let right = Decimal256::one() + Decimal256::percent(50); // 1.5 +// assert_eq!(left * right, 450i128); +// +// // a*0 +// let left = 300i128; +// let right = Decimal256::zero(); +// assert_eq!(left * right, 0i128); +// +// // 0*a +// let left = 0i128; +// let right = Decimal256::one() + Decimal256::percent(50); // 1.5 +// assert_eq!(left * right, 0i128); +// +// assert_eq!(0i128 * Decimal256::one(), 0i128); +// assert_eq!(1i128 * Decimal256::one(), 1i128); +// assert_eq!(2i128 * Decimal256::one(), 2i128); +// +// assert_eq!(1i128 * Decimal256::percent(10), 0i128); +// assert_eq!(10i128 * Decimal256::percent(10), 1i128); +// assert_eq!(100i128 * Decimal256::percent(10), 10i128); +// +// assert_eq!(1i128 * Decimal256::percent(50), 0i128); +// assert_eq!(100i128 * Decimal256::percent(50), 50i128); +// assert_eq!(3200i128 * Decimal256::percent(50), 1600i128); +// assert_eq!(999i128 * Decimal256::percent(50), 499i128); // default rounding down +// +// assert_eq!(1i128 * Decimal256::percent(200), 2i128); +// assert_eq!(1000i128 * Decimal256::percent(200), 2000i128); +// } +// +// #[test] +// // in this test the Decimal256 is on the left +// fn decimal_i128_multiply() { +// // a*b +// let left = Decimal256::one() + Decimal256::percent(50); // 1.5 +// let right = 300i128; +// assert_eq!(left * right, 450i128); +// +// // 0*a +// let left = Decimal256::zero(); +// let right = 300i128; +// assert_eq!(left * right, 0i128); +// +// // a*0 +// let left = Decimal256::one() + Decimal256::percent(50); // 1.5 +// let right = 0i128; +// assert_eq!(left * right, 0i128); +// } +// +// #[test] +// fn decimal_implements_div() { +// let one = Decimal256::one(); +// let two = one + one; +// let half = Decimal256::percent(50); +// +// // 1/x and x/1 +// assert_eq!(one / Decimal256::percent(1), Decimal256::percent(10_000)); +// assert_eq!(one / Decimal256::percent(10), Decimal256::percent(1_000)); +// assert_eq!(one / Decimal256::percent(100), Decimal256::percent(100)); +// assert_eq!(one / Decimal256::percent(1000), Decimal256::percent(10)); +// assert_eq!(Decimal256::percent(0) / one, Decimal256::percent(0)); +// assert_eq!(Decimal256::percent(1) / one, Decimal256::percent(1)); +// assert_eq!(Decimal256::percent(10) / one, Decimal256::percent(10)); +// assert_eq!(Decimal256::percent(100) / one, Decimal256::percent(100)); +// assert_eq!(Decimal256::percent(1000) / one, Decimal256::percent(1000)); +// +// // double +// assert_eq!(two / Decimal256::percent(1), Decimal256::percent(20_000)); +// assert_eq!(two / Decimal256::percent(10), Decimal256::percent(2_000)); +// assert_eq!(two / Decimal256::percent(100), Decimal256::percent(200)); +// assert_eq!(two / Decimal256::percent(1000), Decimal256::percent(20)); +// assert_eq!(Decimal256::percent(0) / two, Decimal256::percent(0)); +// assert_eq!(Decimal256::percent(10) / two, Decimal256::percent(5)); +// assert_eq!(Decimal256::percent(100) / two, Decimal256::percent(50)); +// assert_eq!(Decimal256::percent(1000) / two, Decimal256::percent(500)); +// +// // half +// assert_eq!(half / Decimal256::percent(1), Decimal256::percent(5_000)); +// assert_eq!(half / Decimal256::percent(10), Decimal256::percent(500)); +// assert_eq!(half / Decimal256::percent(100), Decimal256::percent(50)); +// assert_eq!(half / Decimal256::percent(1000), Decimal256::percent(5)); +// assert_eq!(Decimal256::percent(0) / half, Decimal256::percent(0)); +// assert_eq!(Decimal256::percent(1) / half, Decimal256::percent(2)); +// assert_eq!(Decimal256::percent(10) / half, Decimal256::percent(20)); +// assert_eq!(Decimal256::percent(100) / half, Decimal256::percent(200)); +// assert_eq!(Decimal256::percent(1000) / half, Decimal256::percent(2000)); +// +// assert_eq!( +// Decimal256::percent(15) / Decimal256::percent(60), +// Decimal256::percent(25) +// ); +// } +// +// #[test] +// #[should_panic(expected = "attempt to multiply with overflow")] +// fn decimal_div_overflow_panics() { +// let _value = Decimal256::MAX / Decimal256::percent(10); +// } +// +// #[test] +// #[should_panic(expected = "Division failed - denominator must not be zero")] +// fn decimal_div_by_zero_panics() { +// let _value = Decimal256::one() / Decimal256::zero(); +// } +// +// #[test] +// fn decimal_i128_division() { +// // a/b +// let left = Decimal256::percent(150); // 1.5 +// let right = 3i128; +// assert_eq!(left / right, Decimal256::percent(50)); +// +// // 0/a +// let left = Decimal256::zero(); +// let right = 300i128; +// assert_eq!(left / right, Decimal256::zero()); +// } +// +// #[test] +// #[should_panic(expected = "attempt to divide by zero")] +// fn decimal_uint128_divide_by_zero() { +// let left = Decimal256::percent(150); // 1.5 +// let right = 0i128; +// let _result = left / right; +// } +// +// #[test] +// fn decimal_pow_works() { +// assert_eq!(Decimal256::percent(200).pow(2), Decimal256::percent(400)); +// assert_eq!(Decimal256::percent(100).pow(10), Decimal256::percent(100)); +// } +// +// #[test] +// #[should_panic] +// fn decimal_pow_overflow_panics() { +// _ = Decimal256::MAX.pow(2u32); +// } +// +// #[test] +// fn decimal_abs_with_negative_number() { +// let decimal = Decimal256::new(128); +// +// assert_eq!(decimal.abs(), Decimal256(128)); +// } +// +// #[test] +// fn decimal_abs_with_positive_number() { +// let decimal = Decimal256::new(128); +// +// assert_eq!(decimal.abs(), Decimal256(128)); +// } +// +// #[test] +// fn decimal_displayed_as_string() { +// let env = Env::default(); +// let decimal = Decimal256::percent(128); +// +// // Convert expected string to Soroban SDK String +// let expected_msg = "1.28"; +// let expected_string = String::from_str(&env, expected_msg); +// +// // Convert decimal to String and get its byte representation +// let result_string = decimal.to_string(&env); +// let result_string_len = result_string.len() as usize; +// let mut result_bytes = alloc::vec![0u8; result_string_len]; +// result_string.copy_into_slice(&mut result_bytes); +// +// // Get byte representation of expected string +// let expected_string_len = expected_string.len() as usize; +// let mut expected_bytes = alloc::vec![0u8; expected_string_len]; +// expected_string.copy_into_slice(&mut expected_bytes); +// +// assert_eq!(result_bytes, expected_bytes); +// } +// +// #[test] +// fn decimal_fmt_without_fractional_part() { +// let value = Decimal256::from_atomics(100, 0); +// assert_eq!(format!("{}", value), "100"); +// } +// +// #[test] +// fn decimal_fmt_fractional_part() { +// let value = Decimal256::from_atomics(123456789, 5); +// assert_eq!(format!("{}", value), "1234.56789"); +// } +// +// #[test] +// fn decimal_fmt_fractional_part_with_trailing_zeros() { +// // 12345.6 +// let value = Decimal256::from_atomics(123456, 1); +// assert_eq!(format!("{}", value), "12345.6"); +// } +// +// #[test] +// fn decimal_fmt_only_fractional() { +// // 0.0789 +// let value = Decimal256::from_atomics(789, 4); +// assert_eq!(format!("{}", value), "0.0789"); +// } +// } From 5191d6c7a5f2699e62dcb9536280f83796418b79 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 10 Jun 2024 10:22:31 +0200 Subject: [PATCH 09/25] Decimal256: Implementation progress --- packages/decimal/src/decimal256.rs | 2151 ++++++++++++++++------------ 1 file changed, 1202 insertions(+), 949 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index a6f9cc515..9acc5c207 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -1,7 +1,7 @@ // A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache // License 2.0 - https://github.com/CosmWasm/cosmwasm. -use soroban_sdk::{Bytes, Env, String, I256}; +use soroban_sdk::{Env, String, I256}; use core::{ cmp::{Ordering, PartialEq, PartialOrd}, @@ -21,393 +21,237 @@ enum Error { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] pub struct Decimal256(I256); -/// This implementation faced a lot of issues. -/// 1. U/I256 doesn't have a const contructors for some reason -/// 2. U/I256 requires an environment variable to be passed through -/// ...which results in a worse quality of the code. We'll try to work on that. - #[allow(dead_code)] impl Decimal256 { - // Number of decimal places - pub const DECIMAL_PLACES: i32 = 18; + const DECIMAL_PLACES: i32 = 18; - // Function to get DECIMAL_FRACTIONAL - pub fn decimal_fractional(env: &Env) -> I256 { - I256::from_i128(env, 1_000_000_000_000_000_000i128) // 1*10**18 + pub fn new(env: &Env, value: i128) -> Self { + Decimal256(I256::from_i128(env, value)) } - // Function to get DECIMAL_FRACTIONAL_SQUARED - pub fn decimal_fractional_squared(env: &Env) -> I256 { - I256::from_i128(env, 1_000_000_000_000_000_000_000_000_000_000_000_000i128) - // (1*10**18)**2 = 1*10**36 + pub fn raw(env: &Env, value: i128) -> Self { + Self(I256::from_i128(env, value)) } - // // Function to get MAX value - // pub fn max(env: &Env) -> Self { - // Self(I256::from_i128(env, (1i128 << 127) - 1)) - // } - - // // Function to get MIN value - // pub fn min(env: &Env) -> Self { - // Self(I256::from_i128(env, -1i128 << 127)) - // } + pub fn one(env: &Env) -> Self { + Self(I256::from_i128(env, 1_000_000_000_000_000_000)) + } - pub fn new(value: I256) -> Self { - Decimal256(value) + pub fn zero(env: &Env) -> Self { + Self(I256::from_i128(env, 0)) } - pub const fn raw(value: I256) -> Self { - Self(value) + pub fn percent(env: &Env, x: i64) -> Self { + Self(I256::from_i128(env, (x as i128) * 10_000_000_000_000_000)) } - // /// Create a 1.0 Decimal256 - #[inline] - pub fn one(env: &Env) -> Self { - Self(Self::decimal_fractional(env)) + pub fn permille(env: &Env, x: i64) -> Self { + Self(I256::from_i128(env, (x as i128) * 1_000_000_000_000_000)) } - // /// Create a 0.0 Decimal256 - #[inline] - pub fn zero() -> Self { - Self(I256::from_i32(&Env::default(), 0i32)) + pub fn bps(env: &Env, x: i64) -> Self { + Self(I256::from_i128(env, (x as i128) * 100_000_000_000_000)) } - /// Convert x% into Decimal256 - pub fn percent(x: i64) -> Self { - Self(I256::from_i128( - &Env::default(), - (x as i128) * 10_000_000_000_000_000, - )) + pub fn decimal_places(&self) -> i32 { + Self::DECIMAL_PLACES } - // /// Convert permille (x/1000) into Decimal256 - pub fn permille(x: i64) -> Self { - Self(I256::from_i128( - &Env::default(), - (x as i128) * 1_000_000_000_000_000, - )) + fn numerator(&self) -> I256 { + self.0.clone() } - // /// Convert basis points (x/10000) into Decimal256 - pub fn bps(x: i64) -> Self { - Self(I256::from_i128( - &Env::default(), - (x as i128) * 100_000_000_000_000, - )) + fn denominator(&self, env: &Env) -> I256 { + I256::from_i128(env, 1_000_000_000_000_000_000) } - // /// The number of decimal places. This is a constant value for now - // /// but this could potentially change as the type evolves. - // /// - // /// See also [`Decimal256::atomics()`]. - // #[must_use] - // #[inline] - // pub const fn decimal_places(&self) -> i32 { - // Self::DECIMAL_PLACES - // } + pub fn is_zero(&self, env: &Env) -> bool { + self.0 == I256::from_i128(env, 0) + } - #[inline] - fn numerator(&self) -> I256 { - self.0.clone() + pub fn atomics(&self) -> Option { + self.0.to_i128() } - #[inline] - fn denominator(&self, env: &Env) -> I256 { - Self::decimal_fractional(env) + pub fn from_atomics(env: &Env, atomics: i128, decimal_places: i32) -> Self { + const TEN: i128 = 10; + match decimal_places.cmp(&Self::DECIMAL_PLACES) { + Ordering::Less => { + let digits = Self::DECIMAL_PLACES - decimal_places; + let factor = TEN.pow(digits as u32); + Self(I256::from_i128(env, atomics * factor)) + } + Ordering::Equal => Self(I256::from_i128(env, atomics)), + Ordering::Greater => { + let digits = decimal_places - Self::DECIMAL_PLACES; + let factor = TEN.pow(digits as u32); + Self(I256::from_i128(env, atomics / factor)) + } + } } - // #[must_use] - // pub const fn is_zero(&self) -> bool { - // self.0 == I256::from_i32(&Env::default(), 0i32) - // } + pub fn pow(self, env: &Env, exp: u32) -> Self { + fn inner(env: &Env, mut x: Decimal256, mut n: u32) -> Decimal256 { + if n == 0 { + return Decimal256::one(env); + } - // /// A decimal is an integer of atomic units plus a number that specifies the - // /// position of the decimal dot. So any decimal can be expressed as two numbers. - // /// - // /// ## Examples - // /// - // /// ``` - // /// use decimal::Decimal256; - // /// // Value with whole and fractional part - // /// let a = Decimal256::percent(123); - // /// assert_eq!(a.decimal_places(), 18); - // /// assert_eq!(a.atomics(), 1230000000000000000); - // /// - // /// // Smallest possible value - // /// let b = Decimal256::new(1); - // /// assert_eq!(b.decimal_places(), 18); - // /// assert_eq!(b.atomics(), 1); - // /// ``` - // #[must_use] - // #[inline] - // pub const fn atomics(&self) -> I256 { - // self.0 - // } + let mut y = Decimal256::one(env); - // /// Creates a decimal from a number of atomic units and the number - // /// of decimal places. The inputs will be converted internally to form - // /// a decimal with 18 decimal places. So the input 1234 and 3 will create - // /// the decimal 1.234. - // /// - // /// Using 18 decimal places is slightly more efficient than other values - // /// as no internal conversion is necessary. - // /// - // /// ## Examples - // /// - // /// ``` - // /// use decimal::Decimal256; - // /// use soroban_sdk::{String, Env}; - // /// - // /// let e = Env::default(); - // /// let a = Decimal256::from_atomics(1234, 3); - // /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1.234")); - // /// - // /// let a = Decimal256::from_atomics(1234, 0); - // /// assert_eq!(a.to_string(&e), String::from_slice(&e, "1234")); - // /// - // /// let a = Decimal256::from_atomics(1, 18); - // /// assert_eq!(a.to_string(&e), String::from_slice(&e, "0.000000000000000001")); - // /// ``` - // pub fn from_atomics(atomics: impl Into, decimal_places: i32) -> Self { - // let atomics: I256 = atomics.into(); - // const TEN: I256 = I256::from_be_bytes(&Env::default(), &Bytes::from_slice(&Env::default(), &[ - // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // 0, 0, 10, - // ])); - // match decimal_places.cmp(&Self::DECIMAL_PLACES) { - // Ordering::Less => { - // let digits = Self::DECIMAL_PLACES - decimal_places; - // let factor: I256 = TEN.pow(digits as u32); - // Self(atomics * factor) - // } - // Ordering::Equal => Self(atomics), - // Ordering::Greater => { - // let digits = decimal_places - Self::DECIMAL_PLACES; - // let factor = TEN.pow(digits as u32); - // // Since factor cannot be zero, the division is safe. - // Self(atomics / factor) - // } - // } - // } + while n > 1 { + if n % 2 == 0 { + x = x.clone().mul(env, &x); + n /= 2; + } else { + y = x.clone().mul(env, &y); + x = x.clone().mul(env, &x); + n = (n - 1) / 2; + } + } - // /// Raises a value to the power of `exp`, panicking if an overflow occurs. - // pub fn pow(self, exp: u32) -> Self { - // // This uses the exponentiation by squaring algorithm: - // // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method - - // fn inner(mut x: Decimal256, mut n: u32) -> Decimal256 { - // if n == 0 { - // return Decimal256::one(); - // } - - // let mut y = Decimal256::one(); - - // while n > 1 { - // if n % 2 == 0 { - // x = x * x; // Regular multiplication - // n /= 2; - // } else { - // y = x * y; // Regular multiplication - // x = x * x; // Regular multiplication - // n = (n - 1) / 2; - // } - // } - - // x * y - // } - - // inner(self, exp) - // } + x.mul(env, &y) + } - // /// Returns the multiplicative inverse `1/d` for decimal `d`. - // /// - // /// If `d` is zero, none is returned. - // pub fn inv(&self) -> Option { - // if self.is_zero() { - // None - // } else { - // // Let self be p/q with p = self.0 and q = DECIMAL_FRACTIONAL. - // // Now we calculate the inverse a/b = q/p such that b = DECIMAL_FRACTIONAL. Then - // // `a = DECIMAL_FRACTIONAL*DECIMAL_FRACTIONAL / self.0`. - // Some(Decimal256(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) - // } - // } + inner(env, self, exp) + } - // /// Returns the ratio (numerator / denominator) as a Decimal256 - // pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { - // match Decimal256::checked_from_ratio(numerator, denominator) { - // Ok(ratio) => ratio, - // Err(Error::DivideByZero) => panic!("Denominator must not be zero"), - // } - // } + pub fn inv(&self, env: &Env) -> Option { + if self.is_zero(env) { + None + } else { + let fractional_squared = + I256::from_i128(env, 1_000_000_000_000_000_000_000_000_000_000_000_000); + Some(Decimal256(fractional_squared.div(&self.0))) + } + } - // pub fn to_I256_with_precision(&self, precision: impl Into) -> I256 { - // let value = self.atomics(); - // let precision = precision.into(); + pub fn from_ratio(env: &Env, numerator: impl Into, denominator: impl Into) -> Self { + match Decimal256::checked_from_ratio(env, numerator, denominator) { + Ok(ratio) => ratio, + Err(Error::DivideByZero) => panic!("Denominator must not be zero"), + } + } - // let divisor = 10I256.pow((self.decimal_places() - precision) as u32); - // value / divisor - // } + pub fn to_i128_with_precision(&self, precision: impl Into) -> i128 { + let value = self.atomics().unwrap(); + let precision = precision.into(); - // fn multiply_ratio(&self, numerator: Decimal256, denominator: Decimal256) -> Decimal256 { - // Decimal256::from_ratio(self.atomics() * numerator.atomics(), denominator.atomics()) - // } + let divisor = 10i128.pow((self.decimal_places() - precision) as u32); + value / divisor + } - // /// Returns the ratio (numerator / denominator) as a Decimal256 - // fn checked_from_ratio( - // numerator: impl Into, - // denominator: impl Into, - // ) -> Result { - // let numerator = numerator.into(); - // let denominator = denominator.into(); - - // // If denominator is zero, panic. - // if denominator == 0 { - // return Err(Error::DivideByZero); - // } - - // // Convert numerator and denominator to BigInt. - // // unwrap since I256 is always convertible to BigInt - // // let numerator = numerator.to_bigint().unwrap(); - // // let denominator = denominator.to_bigint().unwrap(); - // // let decimal_fractional = Self::DECIMAL_FRACTIONAL.to_bigint().unwrap(); - - // // Compute the ratio: (numerator * DECIMAL_FRACTIONAL) / denominator - // let ratio = (numerator * Self::DECIMAL_FRACTIONAL) / denominator; - - // // Convert back to I256. If conversion fails, panic. - // // let ratio = ratio.to_I256().ok_or(Error::Overflow)?; - - // // Construct and return the Decimal256. - // Ok(Decimal256(ratio)) - // } + fn multiply_ratio( + &self, + env: &Env, + numerator: Decimal256, + denominator: Decimal256, + ) -> Decimal256 { + Decimal256::from_ratio(env, self.0.mul(&numerator.0), denominator.0) + } - // pub fn abs(&self) -> Self { - // if self.0 < 0 { - // Decimal256(-self.0) - // } else { - // *self - // } - // } + fn checked_from_ratio( + env: &Env, + numerator: impl Into, + denominator: impl Into, + ) -> Result { + let numerator = numerator.into(); + let denominator = denominator.into(); + + if denominator == I256::from_i128(env, 0) { + return Err(Error::DivideByZero); + } + + let ratio = numerator + .mul(&I256::from_i128(env, 1_000_000_000_000_000_000)) + .div(&denominator); + + Ok(Decimal256(ratio)) + } + + pub fn abs(&self, env: &Env) -> Self { + if self.0.to_i128().unwrap() < 0 { + Decimal256(I256::from_i128(env, -self.0.to_i128().unwrap())) + } else { + self.clone() + } + } // pub fn to_string(&self, env: &Env) -> String { // String::from_str(env, alloc::format!("{}", self).as_str()) // } - // pub const fn abs_diff(self, other: Self) -> Self { - // Self(self.0.abs_diff(other.0) as I256) - // } + pub fn abs_diff(self, env: &Env, other: Self) -> Self { + let diff = self + .0 + .to_i128() + .unwrap() + .abs_diff(other.0.to_i128().unwrap()); + Self(I256::from_i128(env, diff as i128)) + } + + pub fn div_by_i256(&self, rhs: I256) -> Self { + Decimal256(self.0.div(&rhs)) + } } -// impl Add for Decimal256 { -// type Output = Self; -// -// fn add(self, other: Self) -> Self { -// Decimal256(self.0 + other.0) -// } -// } -// impl Sub for Decimal256 { -// type Output = Self; -// -// fn sub(self, other: Self) -> Self { -// Decimal256(self.0 - other.0) -// } -// } -// -// impl Mul for Decimal256 { -// type Output = Self; -// -// #[allow(clippy::suspicious_arithmetic_impl)] -// fn mul(self, other: Self) -> Self { -// // Decimal256s are fractions. We can multiply two decimals a and b -// // via -// // (a.numerator() * b.numerator()) / (a.denominator() * b.denominator()) -// // = (a.numerator() * b.numerator()) / a.denominator() / b.denominator() -// -// // let self_numerator = self.numerator().to_bigint().unwrap(); -// // let other_numerator = other.numerator().to_bigint().unwrap(); -// -// // Compute the product of the numerators and divide by DECIMAL_FRACTIONAL -// let result = (self.numerator() * other.numerator()) / Self::DECIMAL_FRACTIONAL; -// -// // Convert the result back to I256, and panic on overflow -// // let result = result -// // .to_I256() -// // .unwrap_or_else(|| panic!("attempt to multiply with overflow")); -// -// // Return a new Decimal256 -// Decimal256(result) -// } -// } -// -// impl Div for Decimal256 { -// type Output = Self; -// -// fn div(self, rhs: Self) -> Self { -// match Decimal256::checked_from_ratio(self.numerator(), rhs.numerator()) { -// Ok(ratio) => ratio, -// Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), -// } -// } -// } -// -// impl Mul for Decimal256 { -// type Output = I256; -// -// fn mul(self, rhs: I256) -> Self::Output { -// rhs * self -// } -// } -// -// impl Div for Decimal256 { -// type Output = Self; -// -// fn div(self, rhs: I256) -> Self::Output { -// Decimal256(self.0 / rhs) -// } -// } -// -// impl Mul for I256 { -// type Output = Self; -// -// #[allow(clippy::suspicious_arithmetic_impl)] -// fn mul(self, rhs: Decimal256) -> Self::Output { -// // 0*a and b*0 is always 0 -// if self == 0I256 || rhs.is_zero() { -// return 0I256; -// } -// self * rhs.0 / Decimal256::DECIMAL_FRACTIONAL -// } -// } +impl Add for Decimal256 { + type Output = Self; -// impl FromStr for Decimal256 { -// type Err = (); -// -// fn from_str(input: &str) -> Result { -// let mut parts_iter = input.split('.'); -// -// let whole_part = parts_iter.next().expect("Unexpected input format"); -// let whole: I256 = whole_part.parse().expect("Error parsing whole"); -// let mut atomics = whole * Self::DECIMAL_FRACTIONAL; -// -// if let Some(fractional_part) = parts_iter.next() { -// let fractional: I256 = fractional_part.parse().expect("Error parsing fractional"); -// let exp = Self::DECIMAL_PLACES - fractional_part.len() as i32; -// assert!(exp >= 0, "There must be at least one fractional digit"); -// let fractional_factor = 10i128.pow(exp as u32); -// atomics += fractional * I256::from_i128(env, fractional_factor); -// } -// -// assert!(parts_iter.next().is_none(), "Unexpected number of dots"); -// -// Ok(Decimal256(atomics)) -// } -// } + fn add(self, other: Self) -> Self { + Decimal256(self.0.add(&other.0)) + } +} + +impl Sub for Decimal256 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Decimal256(self.0.sub(&other.0)) + } +} + +impl Decimal256 { + pub fn mul(self, env: &Env, other: &Self) -> Self { + let result = self + .numerator() + .mul(&other.numerator()) + .div(&other.denominator(env)); + Decimal256(result) + } + + pub fn div(self, env: &Env, rhs: Self) -> Self { + match Decimal256::checked_from_ratio(env, self.numerator(), rhs.numerator()) { + Ok(ratio) => ratio, + Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), + } + } + + pub fn from_str_with_env(env: &Env, input: &str) -> Result { + let mut parts_iter = input.split('.'); + + let whole_part = parts_iter.next().expect("Unexpected input format"); + let whole: i128 = whole_part.parse().expect("Error parsing whole"); + let mut atomics = I256::from_i128(env, whole * 1_000_000_000_000_000_000); + + if let Some(fractional_part) = parts_iter.next() { + let fractional: i128 = fractional_part.parse().expect("Error parsing fractional"); + let exp = 18 - fractional_part.len() as i32; + assert!(exp >= 0, "There must be at least one fractional digit"); + let fractional_factor = I256::from_i128(env, 10i128.pow(exp as u32)); + atomics = atomics.add(&I256::from_i128(env, fractional).mul(&fractional_factor)); + } + + assert!(parts_iter.next().is_none(), "Unexpected number of dots"); + + Ok(Decimal256(atomics)) + } +} // impl fmt::Display for Decimal256 { // fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// let whole = self.0 / Self::DECIMAL_FRACTIONAL; -// let fractional = self.0 % Self::DECIMAL_FRACTIONAL; +// let env = Env::default(); +// let whole = self.0.div(&env, &I256::from_i128(&env, 1_000_000_000_000_000_000)).to_i128(); +// let fractional = self.0.rem_euclid(&env, &I256::from_i128(&env, 1_000_000_000_000_000_000)).to_i128(); // // if fractional == 0 { // write!(f, "{}", whole) @@ -415,7 +259,7 @@ impl Decimal256 { // let fractional_string = alloc::format!( // "{:0>padding$}", // fractional, -// padding = Self::DECIMAL_PLACES as usize +// padding = 18 // ); // f.write_fmt(format_args!( // "{}.{}", @@ -426,611 +270,1020 @@ impl Decimal256 { // } // } -// #[cfg(test)] -// mod tests { -// use super::*; -// use alloc::format; -// -// #[test] -// fn decimal_new() { -// let expected = 300i128; -// assert_eq!(Decimal256::new(expected).0, expected); -// } -// -// #[test] -// fn decimal_raw() { -// let value = 300i128; -// assert_eq!(Decimal256::raw(value).0, value); -// } -// -// #[test] -// fn decimal_one() { -// let value = Decimal256::one(); -// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL); -// } -// -// #[test] -// fn decimal_zero() { -// let value = Decimal256::zero(); -// assert_eq!(value.0, 0i128); -// } -// -// #[test] -// fn decimal_percent() { -// let value = Decimal256::percent(50); -// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); -// } -// -// #[test] -// fn decimal_from_atomics_works() { -// let one = Decimal256::one(); -// let two = one + one; -// -// assert_eq!(Decimal256::from_atomics(1i128, 0), one); -// assert_eq!(Decimal256::from_atomics(10i128, 1), one); -// assert_eq!(Decimal256::from_atomics(100i128, 2), one); -// assert_eq!(Decimal256::from_atomics(1000i128, 3), one); -// assert_eq!(Decimal256::from_atomics(1000000000000000000i128, 18), one); -// assert_eq!(Decimal256::from_atomics(10000000000000000000i128, 19), one); -// assert_eq!(Decimal256::from_atomics(100000000000000000000i128, 20), one); -// -// assert_eq!(Decimal256::from_atomics(2i128, 0), two); -// assert_eq!(Decimal256::from_atomics(20i128, 1), two); -// assert_eq!(Decimal256::from_atomics(200i128, 2), two); -// assert_eq!(Decimal256::from_atomics(2000i128, 3), two); -// assert_eq!(Decimal256::from_atomics(2000000000000000000i128, 18), two); -// assert_eq!(Decimal256::from_atomics(20000000000000000000i128, 19), two); -// assert_eq!(Decimal256::from_atomics(200000000000000000000i128, 20), two); -// -// // Cuts decimal digits (20 provided but only 18 can be stored) -// assert_eq!( -// Decimal256::from_atomics(4321i128, 20), -// Decimal256::from_str("0.000000000000000043").unwrap() -// ); -// assert_eq!( -// Decimal256::from_atomics(6789i128, 20), -// Decimal256::from_str("0.000000000000000067").unwrap() -// ); -// assert_eq!( -// Decimal256::from_atomics(i128::MAX, 38), -// Decimal256::from_str("1.701411834604692317").unwrap() -// ); -// assert_eq!( -// Decimal256::from_atomics(i128::MAX, 39), -// Decimal256::from_str("0.170141183460469231").unwrap() -// ); -// assert_eq!( -// Decimal256::from_atomics(i128::MAX, 45), -// Decimal256::from_str("0.000000170141183460").unwrap() -// ); -// assert_eq!( -// Decimal256::from_atomics(i128::MAX, 51), -// Decimal256::from_str("0.000000000000170141").unwrap() -// ); -// assert_eq!( -// Decimal256::from_atomics(i128::MAX, 56), -// Decimal256::from_str("0.000000000000000001").unwrap() -// ); -// } -// -// #[test] -// fn decimal_from_ratio_works() { -// // 1.0 -// assert_eq!(Decimal256::from_ratio(1i128, 1i128), Decimal256::one()); -// assert_eq!(Decimal256::from_ratio(53i128, 53i128), Decimal256::one()); -// assert_eq!(Decimal256::from_ratio(125i128, 125i128), Decimal256::one()); -// -// // 1.5 -// assert_eq!( -// Decimal256::from_ratio(3i128, 2i128), -// Decimal256::percent(150) -// ); -// assert_eq!( -// Decimal256::from_ratio(150i128, 100i128), -// Decimal256::percent(150) -// ); -// assert_eq!( -// Decimal256::from_ratio(333i128, 222i128), -// Decimal256::percent(150) -// ); -// -// // 0.125 -// assert_eq!( -// Decimal256::from_ratio(1i64, 8i64), -// Decimal256::permille(125) -// ); -// assert_eq!( -// Decimal256::from_ratio(125i64, 1000i64), -// Decimal256::permille(125) -// ); -// -// // 1/3 (result floored) -// assert_eq!( -// Decimal256::from_ratio(1i128, 3i128), -// Decimal256(333_333_333_333_333_333i128) -// ); -// -// // 2/3 (result floored) -// assert_eq!( -// Decimal256::from_ratio(2i128, 3i128), -// Decimal256(666_666_666_666_666_666i128) -// ); -// -// // large inputs -// assert_eq!(Decimal256::from_ratio(0i128, i128::MAX), Decimal256::zero()); -// // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); -// -// // due to limited possibilities - we're only allowed to use i128 as input - maximum -// // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL -// // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. -// assert_eq!( -// Decimal256::from_ratio(340282366920938i128, 340282366920938i128), -// Decimal256::one() -// ); -// // This works because of similar orders of magnitude -// assert_eq!( -// Decimal256::from_ratio(34028236692093900000i128, 34028236692093900000i128), -// Decimal256::one() -// ); -// assert_eq!( -// Decimal256::from_ratio(34028236692093900000i128, 1i128), -// Decimal256::new(34028236692093900000i128 * Decimal256::DECIMAL_FRACTIONAL) -// ); -// } -// -// #[test] -// #[should_panic(expected = "Denominator must not be zero")] -// fn decimal_from_ratio_panics_for_zero_denominator() { -// Decimal256::from_ratio(1i128, 0i128); -// } -// -// #[test] -// #[should_panic(expected = "attempt to multiply with overflow")] -// fn decimal_from_ratio_panics_for_mul_overflow() { -// Decimal256::from_ratio(i128::MAX, 1i128); -// } -// -// #[test] -// fn decimal_decimal_places_works() { -// let zero = Decimal256::zero(); -// let one = Decimal256::one(); -// let half = Decimal256::percent(50); -// let two = Decimal256::percent(200); -// let max = Decimal256::MAX; -// -// assert_eq!(zero.decimal_places(), 18); -// assert_eq!(one.decimal_places(), 18); -// assert_eq!(half.decimal_places(), 18); -// assert_eq!(two.decimal_places(), 18); -// assert_eq!(max.decimal_places(), 18); -// } -// -// #[test] -// fn decimal_from_str_works() { -// // Integers -// assert_eq!(Decimal256::from_str("0").unwrap(), Decimal256::percent(0)); -// assert_eq!(Decimal256::from_str("1").unwrap(), Decimal256::percent(100)); -// assert_eq!(Decimal256::from_str("5").unwrap(), Decimal256::percent(500)); -// assert_eq!( -// Decimal256::from_str("42").unwrap(), -// Decimal256::percent(4200) -// ); -// assert_eq!(Decimal256::from_str("000").unwrap(), Decimal256::percent(0)); -// assert_eq!( -// Decimal256::from_str("001").unwrap(), -// Decimal256::percent(100) -// ); -// assert_eq!( -// Decimal256::from_str("005").unwrap(), -// Decimal256::percent(500) -// ); -// assert_eq!( -// Decimal256::from_str("0042").unwrap(), -// Decimal256::percent(4200) -// ); -// -// // Decimal256s -// assert_eq!( -// Decimal256::from_str("1.0").unwrap(), -// Decimal256::percent(100) -// ); -// assert_eq!( -// Decimal256::from_str("1.5").unwrap(), -// Decimal256::percent(150) -// ); -// assert_eq!( -// Decimal256::from_str("0.5").unwrap(), -// Decimal256::percent(50) -// ); -// assert_eq!( -// Decimal256::from_str("0.123").unwrap(), -// Decimal256::permille(123) -// ); -// -// assert_eq!( -// Decimal256::from_str("40.00").unwrap(), -// Decimal256::percent(4000) -// ); -// assert_eq!( -// Decimal256::from_str("04.00").unwrap(), -// Decimal256::percent(400) -// ); -// assert_eq!( -// Decimal256::from_str("00.40").unwrap(), -// Decimal256::percent(40) -// ); -// assert_eq!( -// Decimal256::from_str("00.04").unwrap(), -// Decimal256::percent(4) -// ); -// -// // Can handle DECIMAL_PLACES fractional digits -// assert_eq!( -// Decimal256::from_str("7.123456789012345678").unwrap(), -// Decimal256(7123456789012345678i128) -// ); -// assert_eq!( -// Decimal256::from_str("7.999999999999999999").unwrap(), -// Decimal256(7999999999999999999i128) -// ); -// } -// -// #[test] -// fn decimal_is_zero_works() { -// assert!(Decimal256::zero().is_zero()); -// assert!(Decimal256::percent(0).is_zero()); -// assert!(Decimal256::permille(0).is_zero()); -// -// assert!(!Decimal256::one().is_zero()); -// assert!(!Decimal256::percent(123).is_zero()); -// assert!(!Decimal256::permille(1234).is_zero()); -// } -// -// #[test] -// fn decimal_inv_works() { -// // d = 0 -// assert_eq!(Decimal256::zero().inv(), None); -// -// // d == 1 -// assert_eq!(Decimal256::one().inv(), Some(Decimal256::one())); -// -// // d > 1 exact -// assert_eq!( -// Decimal256::percent(200).inv(), -// Some(Decimal256::percent(50)) -// ); -// assert_eq!( -// Decimal256::percent(2_000).inv(), -// Some(Decimal256::percent(5)) -// ); -// assert_eq!( -// Decimal256::percent(20_000).inv(), -// Some(Decimal256::permille(5)) -// ); -// assert_eq!(Decimal256::percent(200_000).inv(), Some(Decimal256::bps(5))); -// -// // d > 1 rounded -// assert_eq!( -// Decimal256::percent(300).inv(), -// Some(Decimal256::from_ratio(1i128, 3i128)) -// ); -// assert_eq!( -// Decimal256::percent(600).inv(), -// Some(Decimal256::from_ratio(1i128, 6i128)) -// ); -// -// // d < 1 exact -// assert_eq!( -// Decimal256::percent(50).inv(), -// Some(Decimal256::percent(200)) -// ); -// assert_eq!( -// Decimal256::percent(5).inv(), -// Some(Decimal256::percent(2_000)) -// ); -// assert_eq!( -// Decimal256::permille(5).inv(), -// Some(Decimal256::percent(20_000)) -// ); -// assert_eq!(Decimal256::bps(5).inv(), Some(Decimal256::percent(200_000))); -// } -// -// #[test] -// fn decimal_add_works() { -// let value = Decimal256::one() + Decimal256::percent(50); // 1.5 -// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL * 3i128 / 2i128); -// -// assert_eq!( -// Decimal256::percent(5) + Decimal256::percent(4), -// Decimal256::percent(9) -// ); -// assert_eq!( -// Decimal256::percent(5) + Decimal256::zero(), -// Decimal256::percent(5) -// ); -// assert_eq!(Decimal256::zero() + Decimal256::zero(), Decimal256::zero()); -// } -// -// #[test] -// #[should_panic(expected = "attempt to add with overflow")] -// fn decimal_add_overflow_panics() { -// let _value = Decimal256::MAX + Decimal256::percent(50); -// } -// -// #[test] -// fn decimal_sub_works() { -// let value = Decimal256::one() - Decimal256::percent(50); // 0.5 -// assert_eq!(value.0, Decimal256::DECIMAL_FRACTIONAL / 2i128); -// -// assert_eq!( -// Decimal256::percent(9) - Decimal256::percent(4), -// Decimal256::percent(5) -// ); -// assert_eq!( -// Decimal256::percent(16) - Decimal256::zero(), -// Decimal256::percent(16) -// ); -// assert_eq!( -// Decimal256::percent(16) - Decimal256::percent(16), -// Decimal256::zero() -// ); -// assert_eq!(Decimal256::zero() - Decimal256::zero(), Decimal256::zero()); -// } -// -// #[test] -// fn decimal_implements_mul() { -// let one = Decimal256::one(); -// let two = one + one; -// let half = Decimal256::percent(50); -// -// // 1*x and x*1 -// assert_eq!(one * Decimal256::percent(0), Decimal256::percent(0)); -// assert_eq!(one * Decimal256::percent(1), Decimal256::percent(1)); -// assert_eq!(one * Decimal256::percent(10), Decimal256::percent(10)); -// assert_eq!(one * Decimal256::percent(100), Decimal256::percent(100)); -// assert_eq!(one * Decimal256::percent(1000), Decimal256::percent(1000)); -// // assert_eq!(one * Decimal256::MAX, Decimal256::MAX); -// assert_eq!(Decimal256::percent(0) * one, Decimal256::percent(0)); -// assert_eq!(Decimal256::percent(1) * one, Decimal256::percent(1)); -// assert_eq!(Decimal256::percent(10) * one, Decimal256::percent(10)); -// assert_eq!(Decimal256::percent(100) * one, Decimal256::percent(100)); -// assert_eq!(Decimal256::percent(1000) * one, Decimal256::percent(1000)); -// // assert_eq!(Decimal256::MAX * one, Decimal256::MAX); -// -// // double -// assert_eq!(two * Decimal256::percent(0), Decimal256::percent(0)); -// assert_eq!(two * Decimal256::percent(1), Decimal256::percent(2)); -// assert_eq!(two * Decimal256::percent(10), Decimal256::percent(20)); -// assert_eq!(two * Decimal256::percent(100), Decimal256::percent(200)); -// assert_eq!(two * Decimal256::percent(1000), Decimal256::percent(2000)); -// assert_eq!(Decimal256::percent(0) * two, Decimal256::percent(0)); -// assert_eq!(Decimal256::percent(1) * two, Decimal256::percent(2)); -// assert_eq!(Decimal256::percent(10) * two, Decimal256::percent(20)); -// assert_eq!(Decimal256::percent(100) * two, Decimal256::percent(200)); -// assert_eq!(Decimal256::percent(1000) * two, Decimal256::percent(2000)); -// -// // half -// assert_eq!(half * Decimal256::percent(0), Decimal256::percent(0)); -// assert_eq!(half * Decimal256::percent(1), Decimal256::permille(5)); -// assert_eq!(half * Decimal256::percent(10), Decimal256::percent(5)); -// assert_eq!(half * Decimal256::percent(100), Decimal256::percent(50)); -// assert_eq!(half * Decimal256::percent(1000), Decimal256::percent(500)); -// assert_eq!(Decimal256::percent(0) * half, Decimal256::percent(0)); -// assert_eq!(Decimal256::percent(1) * half, Decimal256::permille(5)); -// assert_eq!(Decimal256::percent(10) * half, Decimal256::percent(5)); -// assert_eq!(Decimal256::percent(100) * half, Decimal256::percent(50)); -// assert_eq!(Decimal256::percent(1000) * half, Decimal256::percent(500)); -// } -// -// #[test] -// #[should_panic(expected = "attempt to multiply with overflow")] -// fn decimal_mul_overflow_panics() { -// let _value = Decimal256::MAX * Decimal256::percent(101); -// } -// -// #[test] -// // in this test the Decimal256 is on the right -// fn i128_decimal_multiply() { -// // a*b -// let left = 300i128; -// let right = Decimal256::one() + Decimal256::percent(50); // 1.5 -// assert_eq!(left * right, 450i128); -// -// // a*0 -// let left = 300i128; -// let right = Decimal256::zero(); -// assert_eq!(left * right, 0i128); -// -// // 0*a -// let left = 0i128; -// let right = Decimal256::one() + Decimal256::percent(50); // 1.5 -// assert_eq!(left * right, 0i128); -// -// assert_eq!(0i128 * Decimal256::one(), 0i128); -// assert_eq!(1i128 * Decimal256::one(), 1i128); -// assert_eq!(2i128 * Decimal256::one(), 2i128); -// -// assert_eq!(1i128 * Decimal256::percent(10), 0i128); -// assert_eq!(10i128 * Decimal256::percent(10), 1i128); -// assert_eq!(100i128 * Decimal256::percent(10), 10i128); -// -// assert_eq!(1i128 * Decimal256::percent(50), 0i128); -// assert_eq!(100i128 * Decimal256::percent(50), 50i128); -// assert_eq!(3200i128 * Decimal256::percent(50), 1600i128); -// assert_eq!(999i128 * Decimal256::percent(50), 499i128); // default rounding down -// -// assert_eq!(1i128 * Decimal256::percent(200), 2i128); -// assert_eq!(1000i128 * Decimal256::percent(200), 2000i128); -// } -// -// #[test] -// // in this test the Decimal256 is on the left -// fn decimal_i128_multiply() { -// // a*b -// let left = Decimal256::one() + Decimal256::percent(50); // 1.5 -// let right = 300i128; -// assert_eq!(left * right, 450i128); -// -// // 0*a -// let left = Decimal256::zero(); -// let right = 300i128; -// assert_eq!(left * right, 0i128); -// -// // a*0 -// let left = Decimal256::one() + Decimal256::percent(50); // 1.5 -// let right = 0i128; -// assert_eq!(left * right, 0i128); -// } -// -// #[test] -// fn decimal_implements_div() { -// let one = Decimal256::one(); -// let two = one + one; -// let half = Decimal256::percent(50); -// -// // 1/x and x/1 -// assert_eq!(one / Decimal256::percent(1), Decimal256::percent(10_000)); -// assert_eq!(one / Decimal256::percent(10), Decimal256::percent(1_000)); -// assert_eq!(one / Decimal256::percent(100), Decimal256::percent(100)); -// assert_eq!(one / Decimal256::percent(1000), Decimal256::percent(10)); -// assert_eq!(Decimal256::percent(0) / one, Decimal256::percent(0)); -// assert_eq!(Decimal256::percent(1) / one, Decimal256::percent(1)); -// assert_eq!(Decimal256::percent(10) / one, Decimal256::percent(10)); -// assert_eq!(Decimal256::percent(100) / one, Decimal256::percent(100)); -// assert_eq!(Decimal256::percent(1000) / one, Decimal256::percent(1000)); -// -// // double -// assert_eq!(two / Decimal256::percent(1), Decimal256::percent(20_000)); -// assert_eq!(two / Decimal256::percent(10), Decimal256::percent(2_000)); -// assert_eq!(two / Decimal256::percent(100), Decimal256::percent(200)); -// assert_eq!(two / Decimal256::percent(1000), Decimal256::percent(20)); -// assert_eq!(Decimal256::percent(0) / two, Decimal256::percent(0)); -// assert_eq!(Decimal256::percent(10) / two, Decimal256::percent(5)); -// assert_eq!(Decimal256::percent(100) / two, Decimal256::percent(50)); -// assert_eq!(Decimal256::percent(1000) / two, Decimal256::percent(500)); -// -// // half -// assert_eq!(half / Decimal256::percent(1), Decimal256::percent(5_000)); -// assert_eq!(half / Decimal256::percent(10), Decimal256::percent(500)); -// assert_eq!(half / Decimal256::percent(100), Decimal256::percent(50)); -// assert_eq!(half / Decimal256::percent(1000), Decimal256::percent(5)); -// assert_eq!(Decimal256::percent(0) / half, Decimal256::percent(0)); -// assert_eq!(Decimal256::percent(1) / half, Decimal256::percent(2)); -// assert_eq!(Decimal256::percent(10) / half, Decimal256::percent(20)); -// assert_eq!(Decimal256::percent(100) / half, Decimal256::percent(200)); -// assert_eq!(Decimal256::percent(1000) / half, Decimal256::percent(2000)); -// -// assert_eq!( -// Decimal256::percent(15) / Decimal256::percent(60), -// Decimal256::percent(25) -// ); -// } -// -// #[test] -// #[should_panic(expected = "attempt to multiply with overflow")] -// fn decimal_div_overflow_panics() { -// let _value = Decimal256::MAX / Decimal256::percent(10); -// } -// -// #[test] -// #[should_panic(expected = "Division failed - denominator must not be zero")] -// fn decimal_div_by_zero_panics() { -// let _value = Decimal256::one() / Decimal256::zero(); -// } -// -// #[test] -// fn decimal_i128_division() { -// // a/b -// let left = Decimal256::percent(150); // 1.5 -// let right = 3i128; -// assert_eq!(left / right, Decimal256::percent(50)); -// -// // 0/a -// let left = Decimal256::zero(); -// let right = 300i128; -// assert_eq!(left / right, Decimal256::zero()); -// } -// -// #[test] -// #[should_panic(expected = "attempt to divide by zero")] -// fn decimal_uint128_divide_by_zero() { -// let left = Decimal256::percent(150); // 1.5 -// let right = 0i128; -// let _result = left / right; -// } -// -// #[test] -// fn decimal_pow_works() { -// assert_eq!(Decimal256::percent(200).pow(2), Decimal256::percent(400)); -// assert_eq!(Decimal256::percent(100).pow(10), Decimal256::percent(100)); -// } -// -// #[test] -// #[should_panic] -// fn decimal_pow_overflow_panics() { -// _ = Decimal256::MAX.pow(2u32); -// } -// -// #[test] -// fn decimal_abs_with_negative_number() { -// let decimal = Decimal256::new(128); -// -// assert_eq!(decimal.abs(), Decimal256(128)); -// } -// -// #[test] -// fn decimal_abs_with_positive_number() { -// let decimal = Decimal256::new(128); -// -// assert_eq!(decimal.abs(), Decimal256(128)); -// } -// -// #[test] -// fn decimal_displayed_as_string() { -// let env = Env::default(); -// let decimal = Decimal256::percent(128); -// -// // Convert expected string to Soroban SDK String -// let expected_msg = "1.28"; -// let expected_string = String::from_str(&env, expected_msg); -// -// // Convert decimal to String and get its byte representation -// let result_string = decimal.to_string(&env); -// let result_string_len = result_string.len() as usize; -// let mut result_bytes = alloc::vec![0u8; result_string_len]; -// result_string.copy_into_slice(&mut result_bytes); -// -// // Get byte representation of expected string -// let expected_string_len = expected_string.len() as usize; -// let mut expected_bytes = alloc::vec![0u8; expected_string_len]; -// expected_string.copy_into_slice(&mut expected_bytes); -// -// assert_eq!(result_bytes, expected_bytes); -// } -// -// #[test] -// fn decimal_fmt_without_fractional_part() { -// let value = Decimal256::from_atomics(100, 0); -// assert_eq!(format!("{}", value), "100"); -// } -// -// #[test] -// fn decimal_fmt_fractional_part() { -// let value = Decimal256::from_atomics(123456789, 5); -// assert_eq!(format!("{}", value), "1234.56789"); -// } -// -// #[test] -// fn decimal_fmt_fractional_part_with_trailing_zeros() { -// // 12345.6 -// let value = Decimal256::from_atomics(123456, 1); -// assert_eq!(format!("{}", value), "12345.6"); -// } -// -// #[test] -// fn decimal_fmt_only_fractional() { -// // 0.0789 -// let value = Decimal256::from_atomics(789, 4); -// assert_eq!(format!("{}", value), "0.0789"); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + + #[test] + fn decimal_new() { + let env = Env::default(); + let expected = 300i128; + assert_eq!(Decimal256::new(&env, expected).0.to_i128(), expected); + } + + #[test] + fn decimal_raw() { + let env = Env::default(); + let value = 300i128; + assert_eq!(Decimal256::raw(&env, value).0.to_i128(), value); + } + + #[test] + fn decimal_one() { + let env = Env::default(); + let value = Decimal256::one(&env); + assert_eq!(value.0.to_i128(), 1_000_000_000_000_000_000); + } + + #[test] + fn decimal_zero() { + let env = Env::default(); + let value = Decimal256::zero(&env); + assert_eq!(value.0.to_i128(), 0); + } + + #[test] + fn decimal_percent() { + let env = Env::default(); + let value = Decimal256::percent(&env, 50); + assert_eq!(value.0.to_i128(), 500_000_000_000_000_000); + } + + #[test] + fn decimal_from_atomics_works() { + let env = Env::default(); + let one = Decimal256::one(&env); + let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); + + assert_eq!(Decimal256::from_atomics(&env, 1, 0), one); + assert_eq!(Decimal256::from_atomics(&env, 10, 1), one); + assert_eq!(Decimal256::from_atomics(&env, 100, 2), one); + assert_eq!(Decimal256::from_atomics(&env, 1000, 3), one); + assert_eq!( + Decimal256::from_atomics(&env, 1_000_000_000_000_000_000, 18), + one + ); + assert_eq!( + Decimal256::from_atomics(&env, 10_000_000_000_000_000_000, 19), + one + ); + assert_eq!( + Decimal256::from_atomics(&env, 100_000_000_000_000_000_000, 20), + one + ); + + assert_eq!(Decimal256::from_atomics(&env, 2, 0), two); + assert_eq!(Decimal256::from_atomics(&env, 20, 1), two); + assert_eq!(Decimal256::from_atomics(&env, 200, 2), two); + assert_eq!(Decimal256::from_atomics(&env, 2000, 3), two); + assert_eq!( + Decimal256::from_atomics(&env, 2_000_000_000_000_000_000, 18), + two + ); + assert_eq!( + Decimal256::from_atomics(&env, 20_000_000_000_000_000_000, 19), + two + ); + assert_eq!( + Decimal256::from_atomics(&env, 200_000_000_000_000_000_000, 20), + two + ); + + // Cuts decimal digits (20 provided but only 18 can be stored) + assert_eq!( + Decimal256::from_atomics(&env, 4321, 20), + Decimal256::from_str_with_env(&env, "0.000000000000000043").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(&env, 6789, 20), + Decimal256::from_str_with_env(&env, "0.000000000000000067").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(&env, i128::MAX, 38), + Decimal256::from_str_with_env(&env, "1.701411834604692317").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(&env, i128::MAX, 39), + Decimal256::from_str_with_env(&env, "0.170141183460469231").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(&env, i128::MAX, 45), + Decimal256::from_str_with_env(&env, "0.000000170141183460").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(&env, i128::MAX, 51), + Decimal256::from_str_with_env(&env, "0.000000000000170141").unwrap() + ); + assert_eq!( + Decimal256::from_atomics(&env, i128::MAX, 56), + Decimal256::from_str_with_env(&env, "0.000000000000000001").unwrap() + ); + } + + #[test] + fn decimal_from_ratio_works() { + let env = Env::default(); + + // 1.0 + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 1)), + Decimal256::one(&env) + ); + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 53), I256::from_i128(&env, 53)), + Decimal256::one(&env) + ); + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 125), I256::from_i128(&env, 125)), + Decimal256::one(&env) + ); + + // 1.5 + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 3), I256::from_i128(&env, 2)), + Decimal256::percent(&env, 150) + ); + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 150), I256::from_i128(&env, 100)), + Decimal256::percent(&env, 150) + ); + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 333), I256::from_i128(&env, 222)), + Decimal256::percent(&env, 150) + ); + + // 0.125 + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 8)), + Decimal256::permille(&env, 125) + ); + assert_eq!( + Decimal256::from_ratio( + &env, + I256::from_i128(&env, 125), + I256::from_i128(&env, 1000) + ), + Decimal256::permille(&env, 125) + ); + + // 1/3 (result floored) + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 3)), + Decimal256(I256::from_i128(&env, 333_333_333_333_333_333)) + ); + + // 2/3 (result floored) + assert_eq!( + Decimal256::from_ratio(&env, I256::from_i128(&env, 2), I256::from_i128(&env, 3)), + Decimal256(I256::from_i128(&env, 666_666_666_666_666_666)) + ); + + // large inputs + assert_eq!( + Decimal256::from_ratio( + &env, + I256::from_i128(&env, 0), + I256::from_i128(&env, i128::MAX) + ), + Decimal256::zero(&env) + ); + // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); + + // due to limited possibilities - we're only allowed to use i128 as input - maximum + // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL + // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. + assert_eq!( + Decimal256::from_ratio( + &env, + I256::from_i128(&env, 340282366920938), + I256::from_i128(&env, 340282366920938) + ), + Decimal256::one(&env) + ); + // This works because of similar orders of magnitude + assert_eq!( + Decimal256::from_ratio( + &env, + I256::from_i128(&env, 34028236692093900000), + I256::from_i128(&env, 34028236692093900000) + ), + Decimal256::one(&env) + ); + assert_eq!( + Decimal256::from_ratio( + &env, + I256::from_i128(&env, 34028236692093900000), + I256::from_i128(&env, 1) + ), + Decimal256::new(&env, 34028236692093900000 * 1_000_000_000_000_000_000) + ); + } + + #[test] + #[should_panic(expected = "Denominator must not be zero")] + fn decimal_from_ratio_panics_for_zero_denominator() { + let env = Env::default(); + Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 0)); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_from_ratio_panics_for_mul_overflow() { + let env = Env::default(); + Decimal256::from_ratio( + &env, + I256::from_i128(&env, i128::MAX), + I256::from_i128(&env, 1), + ); + } + + #[test] + fn decimal_decimal_places_works() { + let env = Env::default(); + let zero = Decimal256::zero(&env); + let one = Decimal256::one(&env); + let half = Decimal256::percent(&env, 50); + let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); + let max = Decimal256(I256::from_parts(i128::MAX, 0)); + + assert_eq!(zero.decimal_places(), 18); + assert_eq!(one.decimal_places(), 18); + assert_eq!(half.decimal_places(), 18); + assert_eq!(two.decimal_places(), 18); + assert_eq!(max.decimal_places(), 18); + } + + #[test] + fn decimal_from_str_works() { + let env = Env::default(); + + // Integers + assert_eq!( + Decimal256::from_str_with_env(&env, "0").unwrap(), + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "1").unwrap(), + Decimal256::percent(&env, 100) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "5").unwrap(), + Decimal256::percent(&env, 500) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "42").unwrap(), + Decimal256::percent(&env, 4200) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "000").unwrap(), + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "001").unwrap(), + Decimal256::percent(&env, 100) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "005").unwrap(), + Decimal256::percent(&env, 500) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "0042").unwrap(), + Decimal256::percent(&env, 4200) + ); + + // Decimal256s + assert_eq!( + Decimal256::from_str_with_env(&env, "1.0").unwrap(), + Decimal256::percent(&env, 100) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "1.5").unwrap(), + Decimal256::percent(&env, 150) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "0.5").unwrap(), + Decimal256::percent(&env, 50) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "0.123").unwrap(), + Decimal256::permille(&env, 123) + ); + + assert_eq!( + Decimal256::from_str_with_env(&env, "40.00").unwrap(), + Decimal256::percent(&env, 4000) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "04.00").unwrap(), + Decimal256::percent(&env, 400) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "00.40").unwrap(), + Decimal256::percent(&env, 40) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "00.04").unwrap(), + Decimal256::percent(&env, 4) + ); + + // Can handle DECIMAL_PLACES fractional digits + assert_eq!( + Decimal256::from_str_with_env(&env, "7.123456789012345678").unwrap(), + Decimal256(I256::from_i128(&env, 7123456789012345678)) + ); + assert_eq!( + Decimal256::from_str_with_env(&env, "7.999999999999999999").unwrap(), + Decimal256(I256::from_i128(&env, 7999999999999999999)) + ); + } + + #[test] + fn decimal_is_zero_works() { + let env = Env::default(); + assert!(Decimal256::zero(&env).is_zero(&env)); + assert!(Decimal256::percent(&env, 0).is_zero(&env)); + assert!(Decimal256::permille(&env, 0).is_zero(&env)); + + assert!(!Decimal256::one(&env).is_zero(&env)); + assert!(!Decimal256::percent(&env, 123).is_zero(&env)); + assert!(!Decimal256::permille(&env, 1234).is_zero(&env)); + } + + #[test] + fn decimal_inv_works() { + let env = Env::default(); + + // d = 0 + assert_eq!(Decimal256::zero(&env).inv(&env), None); + + // d == 1 + assert_eq!(Decimal256::one(&env).inv(&env), Some(Decimal256::one(&env))); + + // d > 1 exact + assert_eq!( + Decimal256::percent(&env, 200).inv(&env), + Some(Decimal256::percent(&env, 50)) + ); + assert_eq!( + Decimal256::percent(&env, 2_000).inv(&env), + Some(Decimal256::percent(&env, 5)) + ); + assert_eq!( + Decimal256::percent(&env, 20_000).inv(&env), + Some(Decimal256::permille(&env, 5)) + ); + assert_eq!( + Decimal256::percent(&env, 200_000).inv(&env), + Some(Decimal256::bps(&env, 5)) + ); + + // d > 1 rounded + assert_eq!( + Decimal256::percent(&env, 300).inv(&env), + Some(Decimal256::from_ratio( + &env, + I256::from_i128(&env, 1), + I256::from_i128(&env, 3) + )) + ); + assert_eq!( + Decimal256::percent(&env, 600).inv(&env), + Some(Decimal256::from_ratio( + &env, + I256::from_i128(&env, 1), + I256::from_i128(&env, 6) + )) + ); + + // d < 1 exact + assert_eq!( + Decimal256::percent(&env, 50).inv(&env), + Some(Decimal256::percent(&env, 200)) + ); + assert_eq!( + Decimal256::percent(&env, 5).inv(&env), + Some(Decimal256::percent(&env, 2_000)) + ); + assert_eq!( + Decimal256::permille(&env, 5).inv(&env), + Some(Decimal256::percent(&env, 20_000)) + ); + assert_eq!( + Decimal256::bps(&env, 5).inv(&env), + Some(Decimal256::percent(&env, 200_000)) + ); + } + + #[test] + fn decimal_add_works() { + let env = Env::default(); + + let value = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + assert_eq!(value.0.to_i128(), 1_500_000_000_000_000_000); + + assert_eq!( + Decimal256::percent(&env, 5) + Decimal256::percent(&env, 4), + Decimal256::percent(&env, 9) + ); + assert_eq!( + Decimal256::percent(&env, 5) + Decimal256::zero(&env), + Decimal256::percent(&env, 5) + ); + assert_eq!( + Decimal256::zero(&env) + Decimal256::zero(&env), + Decimal256::zero(&env) + ); + } + + #[test] + #[should_panic(expected = "attempt to add with overflow")] + fn decimal_add_overflow_panics() { + let env = Env::default(); + let _value = Decimal256(I256::from_parts(i128::MAX, 0)) + Decimal256::percent(&env, 50); + } + + #[test] + fn decimal_sub_works() { + let env = Env::default(); + + let value = Decimal256::one(&env) - Decimal256::percent(&env, 50); // 0.5 + assert_eq!(value.0.to_i128(), 500_000_000_000_000_000); + + assert_eq!( + Decimal256::percent(&env, 9) - Decimal256::percent(&env, 4), + Decimal256::percent(&env, 5) + ); + assert_eq!( + Decimal256::percent(&env, 16) - Decimal256::zero(&env), + Decimal256::percent(&env, 16) + ); + assert_eq!( + Decimal256::percent(&env, 16) - Decimal256::percent(&env, 16), + Decimal256::zero(&env) + ); + assert_eq!( + Decimal256::zero(&env) - Decimal256::zero(&env), + Decimal256::zero(&env) + ); + } + + #[test] + fn decimal_implements_mul() { + let env = Env::default(); + let one = Decimal256::one(&env); + let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); + let half = Decimal256::percent(&env, 50); + + // 1*x and x*1 + assert_eq!( + one * Decimal256::percent(&env, 0), + Decimal256::percent(&env, 0) + ); + assert_eq!( + one * Decimal256::percent(&env, 1), + Decimal256::percent(&env, 1) + ); + assert_eq!( + one * Decimal256::percent(&env, 10), + Decimal256::percent(&env, 10) + ); + assert_eq!( + one * Decimal256::percent(&env, 100), + Decimal256::percent(&env, 100) + ); + assert_eq!( + one * Decimal256::percent(&env, 1000), + Decimal256::percent(&env, 1000) + ); + assert_eq!( + Decimal256::percent(&env, 0) * one, + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::percent(&env, 1) * one, + Decimal256::percent(&env, 1) + ); + assert_eq!( + Decimal256::percent(&env, 10) * one, + Decimal256::percent(&env, 10) + ); + assert_eq!( + Decimal256::percent(&env, 100) * one, + Decimal256::percent(&env, 100) + ); + assert_eq!( + Decimal256::percent(&env, 1000) * one, + Decimal256::percent(&env, 1000) + ); + + // double + assert_eq!( + two * Decimal256::percent(&env, 0), + Decimal256::percent(&env, 0) + ); + assert_eq!( + two * Decimal256::percent(&env, 1), + Decimal256::percent(&env, 2) + ); + assert_eq!( + two * Decimal256::percent(&env, 10), + Decimal256::percent(&env, 20) + ); + assert_eq!( + two * Decimal256::percent(&env, 100), + Decimal256::percent(&env, 200) + ); + assert_eq!( + two * Decimal256::percent(&env, 1000), + Decimal256::percent(&env, 2000) + ); + assert_eq!( + Decimal256::percent(&env, 0) * two, + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::percent(&env, 1) * two, + Decimal256::percent(&env, 2) + ); + assert_eq!( + Decimal256::percent(&env, 10) * two, + Decimal256::percent(&env, 20) + ); + assert_eq!( + Decimal256::percent(&env, 100) * two, + Decimal256::percent(&env, 200) + ); + assert_eq!( + Decimal256::percent(&env, 1000) * two, + Decimal256::percent(&env, 2000) + ); + + // half + assert_eq!( + half * Decimal256::percent(&env, 0), + Decimal256::percent(&env, 0) + ); + assert_eq!( + half * Decimal256::percent(&env, 1), + Decimal256::permille(&env, 5) + ); + assert_eq!( + half * Decimal256::percent(&env, 10), + Decimal256::percent(&env, 5) + ); + assert_eq!( + half * Decimal256::percent(&env, 100), + Decimal256::percent(&env, 50) + ); + assert_eq!( + half * Decimal256::percent(&env, 1000), + Decimal256::percent(&env, 500) + ); + assert_eq!( + Decimal256::percent(&env, 0) * half, + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::percent(&env, 1) * half, + Decimal256::permille(&env, 5) + ); + assert_eq!( + Decimal256::percent(&env, 10) * half, + Decimal256::percent(&env, 5) + ); + assert_eq!( + Decimal256::percent(&env, 100) * half, + Decimal256::percent(&env, 50) + ); + assert_eq!( + Decimal256::percent(&env, 1000) * half, + Decimal256::percent(&env, 500) + ); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_mul_overflow_panics() { + let env = Env::default(); + let _value = Decimal256(I256::from_parts(i128::MAX, 0)) * Decimal256::percent(&env, 101); + } + + #[test] + // in this test the Decimal256 is on the right + fn i128_decimal_multiply() { + let env = Env::default(); + + // a*b + let left = I256::from_i128(&env, 300); + let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + assert_eq!(left * right, I256::from_i128(&env, 450)); + + // a*0 + let left = I256::from_i128(&env, 300); + let right = Decimal256::zero(&env); + assert_eq!(left * right, I256::from_i128(&env, 0)); + + // 0*a + let left = I256::from_i128(&env, 0); + let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + assert_eq!(left * right, I256::from_i128(&env, 0)); + + assert_eq!( + I256::from_i128(&env, 0) * Decimal256::one(&env), + I256::from_i128(&env, 0) + ); + assert_eq!( + I256::from_i128(&env, 1) * Decimal256::one(&env), + I256::from_i128(&env, 1) + ); + assert_eq!( + I256::from_i128(&env, 2) * Decimal256::one(&env), + I256::from_i128(&env, 2) + ); + + assert_eq!( + I256::from_i128(&env, 1) * Decimal256::percent(&env, 10), + I256::from_i128(&env, 0) + ); + assert_eq!( + I256::from_i128(&env, 10) * Decimal256::percent(&env, 10), + I256::from_i128(&env, 1) + ); + assert_eq!( + I256::from_i128(&env, 100) * Decimal256::percent(&env, 10), + I256::from_i128(&env, 10) + ); + + assert_eq!( + I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), + I256::from_i128(&env, 0) + ); + assert_eq!( + I256::from_i128(&env, 100) * Decimal256::percent(&env, 50), + I256::from_i128(&env, 50) + ); + assert_eq!( + I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), + I256::from_i128(&env, 1600) + ); + assert_eq!( + I256::from_i128(&env, 999) * Decimal256::percent(&env, 50), + I256::from_i128(&env, 499) + ); // default rounding down + + assert_eq!( + I256::from_i128(&env, 1) * Decimal256::percent(&env, 200), + I256::from_i128(&env, 2) + ); + assert_eq!( + I256::from_i128(&env, 1000) * Decimal256::percent(&env, 200), + I256::from_i128(&env, 2000) + ); + } + + #[test] + // in this test the Decimal256 is on the left + fn decimal_i128_multiply() { + let env = Env::default(); + + // a*b + let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + let right = I256::from_i128(&env, 300); + assert_eq!(left * right, I256::from_i128(&env, 450)); + + // 0*a + let left = Decimal256::zero(&env); + let right = I256::from_i128(&env, 300); + assert_eq!(left * right, I256::from_i128(&env, 0)); + + // a*0 + let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + let right = I256::from_i128(&env, 0); + assert_eq!(left * right, I256::from_i128(&env, 0)); + } + + #[test] + fn decimal_implements_div() { + let env = Env::default(); + let one = Decimal256::one(&env); + let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); + let half = Decimal256::percent(&env, 50); + + // 1/x and x/1 + assert_eq!( + one.div(&env, Decimal256::percent(&env, 1)), + Decimal256::percent(&env, 10_000) + ); + assert_eq!( + one.div(&env, Decimal256::percent(&env, 10)), + Decimal256::percent(&env, 1_000) + ); + assert_eq!( + one.div(&env, Decimal256::percent(&env, 100)), + Decimal256::percent(&env, 100) + ); + assert_eq!( + one.div(&env, Decimal256::percent(&env, 1000)), + Decimal256::percent(&env, 10) + ); + assert_eq!( + Decimal256::percent(&env, 0).div(&env, one), + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::percent(&env, 1).div(&env, one), + Decimal256::percent(&env, 1) + ); + assert_eq!( + Decimal256::percent(&env, 10).div(&env, one), + Decimal256::percent(&env, 10) + ); + assert_eq!( + Decimal256::percent(&env, 100).div(&env, one), + Decimal256::percent(&env, 100) + ); + assert_eq!( + Decimal256::percent(&env, 1000).div(&env, one), + Decimal256::percent(&env, 1000) + ); + + // double + assert_eq!( + two.div(&env, Decimal256::percent(&env, 1)), + Decimal256::percent(&env, 20_000) + ); + assert_eq!( + two.div(&env, Decimal256::percent(&env, 10)), + Decimal256::percent(&env, 2_000) + ); + assert_eq!( + two.div(&env, Decimal256::percent(&env, 100)), + Decimal256::percent(&env, 200) + ); + assert_eq!( + two.div(&env, Decimal256::percent(&env, 1000)), + Decimal256::percent(&env, 20) + ); + assert_eq!( + Decimal256::percent(&env, 0).div(&env, two), + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::percent(&env, 10).div(&env, two), + Decimal256::percent(&env, 5) + ); + assert_eq!( + Decimal256::percent(&env, 100).div(&env, two), + Decimal256::percent(&env, 50) + ); + assert_eq!( + Decimal256::percent(&env, 1000).div(&env, two), + Decimal256::percent(&env, 500) + ); + + // half + assert_eq!( + half.div(&env, Decimal256::percent(&env, 1)), + Decimal256::percent(&env, 5_000) + ); + assert_eq!( + half.div(&env, Decimal256::percent(&env, 10)), + Decimal256::percent(&env, 500) + ); + assert_eq!( + half.div(&env, Decimal256::percent(&env, 100)), + Decimal256::percent(&env, 50) + ); + assert_eq!( + half.div(&env, Decimal256::percent(&env, 1000)), + Decimal256::percent(&env, 5) + ); + assert_eq!( + Decimal256::percent(&env, 0).div(&env, half), + Decimal256::percent(&env, 0) + ); + assert_eq!( + Decimal256::percent(&env, 1).div(&env, half), + Decimal256::percent(&env, 2) + ); + assert_eq!( + Decimal256::percent(&env, 10).div(&env, half), + Decimal256::percent(&env, 20) + ); + assert_eq!( + Decimal256::percent(&env, 100).div(&env, half), + Decimal256::percent(&env, 200) + ); + assert_eq!( + Decimal256::percent(&env, 1000).div(&env, half), + Decimal256::percent(&env, 2000) + ); + + assert_eq!( + Decimal256::percent(&env, 15).div(&env, Decimal256::percent(&env, 60)), + Decimal256::percent(&env, 25) + ); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_div_overflow_panics() { + let env = Env::default(); + let _value = + Decimal256(I256::from_parts(i128::MAX, 0)).div(&env, Decimal256::percent(&env, 10)); + } + + #[test] + #[should_panic(expected = "Division failed - denominator must not be zero")] + fn decimal_div_by_zero_panics() { + let env = Env::default(); + let _value = Decimal256::one(&env).div(&env, Decimal256::zero(&env)); + } + + #[test] + fn decimal_i128_division() { + let env = Env::default(); + + // a/b + let left = Decimal256::percent(&env, 150); // 1.5 + let right = I256::from_i128(&env, 3); + assert_eq!(left.div_by_i256(&env, right), Decimal256::percent(&env, 50)); + + // 0/a + let left = Decimal256::zero(&env); + let right = I256::from_i128(&env, 300); + assert_eq!(left.div_by_i256(&env, right), Decimal256::zero(&env)); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn decimal_i128_divide_by_zero() { + let env = Env::default(); + let left = Decimal256::percent(&env, 150); // 1.5 + let right = I256::from_i128(&env, 0); + let _result = left.div_by_i256(&env, right); + } + + #[test] + fn decimal_pow_works() { + let env = Env::default(); + assert_eq!( + Decimal256::percent(&env, 200).pow(&env, 2), + Decimal256::percent(&env, 400) + ); + assert_eq!( + Decimal256::percent(&env, 100).pow(&env, 10), + Decimal256::percent(&env, 100) + ); + } + + #[test] + #[should_panic] + fn decimal_pow_overflow_panics() { + let env = Env::default(); + _ = Decimal256(I256::from_parts(i128::MAX, 0)).pow(&env, 2u32); + } + + #[test] + fn decimal_abs_with_negative_number() { + let env = Env::default(); + let decimal = Decimal256::new(&env, -128); + + assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); + } + + #[test] + fn decimal_abs_with_positive_number() { + let env = Env::default(); + let decimal = Decimal256::new(&env, 128); + + assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); + } + + #[test] + fn decimal_displayed_as_string() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 128); + + // Convert expected string to Soroban SDK String + let expected_msg = "1.28"; + let expected_string = String::from_str(&env, expected_msg); + + // Convert decimal to String and get its byte representation + let result_string = decimal.to_string(&env); + let result_string_len = result_string.len() as usize; + let mut result_bytes = alloc::vec![0u8; result_string_len]; + result_string.copy_into_slice(&mut result_bytes); + + // Get byte representation of expected string + let expected_string_len = expected_string.len() as usize; + let mut expected_bytes = alloc::vec![0u8; expected_string_len]; + expected_string.copy_into_slice(&mut expected_bytes); + + assert_eq!(result_bytes, expected_bytes); + } + + #[test] + fn decimal_fmt_without_fractional_part() { + let env = Env::default(); + let value = Decimal256::from_atomics(&env, 100, 0); + assert_eq!(format!("{}", value), "100"); + } + + #[test] + fn decimal_fmt_fractional_part() { + let env = Env::default(); + let value = Decimal256::from_atomics(&env, 123456789, 5); + assert_eq!(format!("{}", value), "1234.56789"); + } + + #[test] + fn decimal_fmt_fractional_part_with_trailing_zeros() { + // 12345.6 + let env = Env::default(); + let value = Decimal256::from_atomics(&env, 123456, 1); + assert_eq!(format!("{}", value), "12345.6"); + } + + #[test] + fn decimal_fmt_only_fractional() { + // 0.0789 + let env = Env::default(); + let value = Decimal256::from_atomics(&env, 789, 4); + assert_eq!(format!("{}", value), "0.0789"); + } + + #[test] + fn test_denominator() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 123); + assert_eq!(decimal.denominator(&env), Decimal256::denominator(&env)); + } + + #[test] + fn test_atomics() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 123); + assert_eq!(decimal.atomics(), 1230000000000000000); + } + + #[test] + fn test_to_i128_with_precision() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 124); + assert_eq!(decimal.to_i128_with_precision(1), 12); + assert_eq!(decimal.to_i128_with_precision(2), 124); + } + + #[test] + fn test_multiply_ratio() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 1); + let numerator = Decimal256::new(&env, 2); + let denominator = Decimal256::new(&env, 5); + + // decimal is 10_000_000_000_000_000, atomics would be same + // numerator is 20_000_000_000_000_000, atomics would be same + // denominator is 50_000_000_000_000_000, amount would be same + // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 + // decimal from ratio + // numerator 200_000_000_000_000_000_000_000_000_000 + // denominator = 50_000_000_000_000_000 + // numerator * DECIMAL_FRACTIONAL / denominator is the result + assert_eq!( + decimal.multiply_ratio(&env, numerator, denominator), + Decimal256::new(&env, 4000000000000000000000000000000000) + ); + } + + #[test] + fn test_abs_difference() { + let env = Env::default(); + let a = Decimal256::new(&env, 100); + let b = Decimal256::new(&env, 200); + + assert_eq!(a.abs_diff(&env, b), Decimal256::new(&env, 100)); + assert_eq!(b.abs_diff(&env, a), Decimal256::new(&env, 100)); + } + + #[test] + fn test_checked_from_ratio() { + let env = Env::default(); + let numerator = Decimal256::new(&env, 100); + let denominator = Decimal256::new(&env, 200); + + assert_eq!( + Decimal256::checked_from_ratio(&env, numerator.0, denominator.0), + Ok(Decimal256::new(&env, 500_000_000_000_000_000)) + ); + } + + #[test] + fn test_decimal_places() { + let env = Env::default(); + let a = Decimal256::percent(&env, 50); + + assert_eq!(a.decimal_places(), 18); + } +} From 6d5d4dedd2a5bf1a1f871852caed405c8e681686 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 10 Jun 2024 10:25:31 +0200 Subject: [PATCH 10/25] Decimal256: Include refactored format implementation --- packages/decimal/src/decimal256.rs | 61 +++++++++++++++++------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 9acc5c207..e4acd2fe9 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -247,28 +247,32 @@ impl Decimal256 { } } -// impl fmt::Display for Decimal256 { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// let env = Env::default(); -// let whole = self.0.div(&env, &I256::from_i128(&env, 1_000_000_000_000_000_000)).to_i128(); -// let fractional = self.0.rem_euclid(&env, &I256::from_i128(&env, 1_000_000_000_000_000_000)).to_i128(); -// -// if fractional == 0 { -// write!(f, "{}", whole) -// } else { -// let fractional_string = alloc::format!( -// "{:0>padding$}", -// fractional, -// padding = 18 -// ); -// f.write_fmt(format_args!( -// "{}.{}", -// whole, -// fractional_string.trim_end_matches('0') -// )) -// } -// } -// } +impl fmt::Display for Decimal256 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let env = Env::default(); + let whole = self + .0 + .div(&I256::from_i128(&env, 1_000_000_000_000_000_000)) + .to_i128() + .unwrap(); + let fractional = self + .0 + .rem_euclid(&I256::from_i128(&env, 1_000_000_000_000_000_000)) + .to_i128() + .unwrap(); + + if fractional == 0 { + write!(f, "{}", whole) + } else { + let fractional_string = alloc::format!("{:0>padding$}", fractional, padding = 18); + f.write_fmt(format_args!( + "{}.{}", + whole, + fractional_string.trim_end_matches('0') + )) + } + } +} #[cfg(test)] mod tests { @@ -279,35 +283,38 @@ mod tests { fn decimal_new() { let env = Env::default(); let expected = 300i128; - assert_eq!(Decimal256::new(&env, expected).0.to_i128(), expected); + assert_eq!( + Decimal256::new(&env, expected).0.to_i128().unwrap(), + expected + ); } #[test] fn decimal_raw() { let env = Env::default(); let value = 300i128; - assert_eq!(Decimal256::raw(&env, value).0.to_i128(), value); + assert_eq!(Decimal256::raw(&env, value).0.to_i128().unwrap(), value); } #[test] fn decimal_one() { let env = Env::default(); let value = Decimal256::one(&env); - assert_eq!(value.0.to_i128(), 1_000_000_000_000_000_000); + assert_eq!(value.0.to_i128().unwrap(), 1_000_000_000_000_000_000); } #[test] fn decimal_zero() { let env = Env::default(); let value = Decimal256::zero(&env); - assert_eq!(value.0.to_i128(), 0); + assert_eq!(value.0.to_i128().unwrap(), 0); } #[test] fn decimal_percent() { let env = Env::default(); let value = Decimal256::percent(&env, 50); - assert_eq!(value.0.to_i128(), 500_000_000_000_000_000); + assert_eq!(value.0.to_i128().unwrap(), 500_000_000_000_000_000); } #[test] From c1938da80cd7afcd53f3ce6e5281661d40905b72 Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 10 Jun 2024 10:53:01 +0200 Subject: [PATCH 11/25] Decimal256: Refactor more test cases and add a couple of FIXME --- packages/decimal/src/decimal256.rs | 874 +++++++++++++++-------------- 1 file changed, 446 insertions(+), 428 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index e4acd2fe9..c2c14878b 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -13,7 +13,7 @@ use core::{ extern crate alloc; #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, PartialEq)] enum Error { DivideByZero, } @@ -41,6 +41,16 @@ impl Decimal256 { Self(I256::from_i128(env, 0)) } + pub fn max(env: &Env) -> Self { + Decimal256(I256::from_parts( + env, + i64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + )) + } + pub fn percent(env: &Env, x: i64) -> Self { Self(I256::from_i128(env, (x as i128) * 10_000_000_000_000_000)) } @@ -176,9 +186,9 @@ impl Decimal256 { } } - // pub fn to_string(&self, env: &Env) -> String { - // String::from_str(env, alloc::format!("{}", self).as_str()) - // } + pub fn to_string(&self, env: &Env) -> String { + String::from_str(env, alloc::format!("{}", self).as_str()) + } pub fn abs_diff(self, env: &Env, other: Self) -> Self { let diff = self @@ -211,7 +221,7 @@ impl Sub for Decimal256 { } impl Decimal256 { - pub fn mul(self, env: &Env, other: &Self) -> Self { + pub fn mul(&self, env: &Env, other: &Self) -> Self { let result = self .numerator() .mul(&other.numerator()) @@ -219,7 +229,7 @@ impl Decimal256 { Decimal256(result) } - pub fn div(self, env: &Env, rhs: Self) -> Self { + pub fn div(&self, env: &Env, rhs: Self) -> Self { match Decimal256::checked_from_ratio(env, self.numerator(), rhs.numerator()) { Ok(ratio) => ratio, Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), @@ -280,7 +290,7 @@ mod tests { use alloc::format; #[test] - fn decimal_new() { + fn decimal256_new() { let env = Env::default(); let expected = 300i128; assert_eq!( @@ -290,35 +300,35 @@ mod tests { } #[test] - fn decimal_raw() { + fn decimal256_raw() { let env = Env::default(); let value = 300i128; assert_eq!(Decimal256::raw(&env, value).0.to_i128().unwrap(), value); } #[test] - fn decimal_one() { + fn decimal256_one() { let env = Env::default(); let value = Decimal256::one(&env); assert_eq!(value.0.to_i128().unwrap(), 1_000_000_000_000_000_000); } #[test] - fn decimal_zero() { + fn decimal256_zero() { let env = Env::default(); let value = Decimal256::zero(&env); assert_eq!(value.0.to_i128().unwrap(), 0); } #[test] - fn decimal_percent() { + fn decimal256_percent() { let env = Env::default(); let value = Decimal256::percent(&env, 50); assert_eq!(value.0.to_i128().unwrap(), 500_000_000_000_000_000); } #[test] - fn decimal_from_atomics_works() { + fn decimal256_from_atomics_works() { let env = Env::default(); let one = Decimal256::one(&env); let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); @@ -389,7 +399,7 @@ mod tests { } #[test] - fn decimal_from_ratio_works() { + fn decimal256_from_ratio_works() { let env = Env::default(); // 1.0 @@ -458,7 +468,7 @@ mod tests { // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); // due to limited possibilities - we're only allowed to use i128 as input - maximum - // number this implementation supports without overflow is u128 / DECIMAL_FRACTIONAL + // number this implementation supports without overflow is u128 / decimal256_FRACTIONAL // 340282366920938463463374607431768211455 / 10^18 is approximately 340282366920938. assert_eq!( Decimal256::from_ratio( @@ -489,14 +499,15 @@ mod tests { #[test] #[should_panic(expected = "Denominator must not be zero")] - fn decimal_from_ratio_panics_for_zero_denominator() { + fn decimal256_from_ratio_panics_for_zero_denominator() { let env = Env::default(); Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 0)); } + #[ignore = "FIXME: Why is I256 not panicking?"] #[test] #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_from_ratio_panics_for_mul_overflow() { + fn decimal256_from_ratio_panics_for_mul_overflow() { let env = Env::default(); Decimal256::from_ratio( &env, @@ -506,13 +517,13 @@ mod tests { } #[test] - fn decimal_decimal_places_works() { + fn decimal256_decimal256_places_works() { let env = Env::default(); let zero = Decimal256::zero(&env); let one = Decimal256::one(&env); let half = Decimal256::percent(&env, 50); let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); - let max = Decimal256(I256::from_parts(i128::MAX, 0)); + let max = Decimal256::max(&env); assert_eq!(zero.decimal_places(), 18); assert_eq!(one.decimal_places(), 18); @@ -522,7 +533,7 @@ mod tests { } #[test] - fn decimal_from_str_works() { + fn decimal256_from_str_works() { let env = Env::default(); // Integers @@ -594,7 +605,7 @@ mod tests { Decimal256::percent(&env, 4) ); - // Can handle DECIMAL_PLACES fractional digits + // Can handle decimal256_PLACES fractional digits assert_eq!( Decimal256::from_str_with_env(&env, "7.123456789012345678").unwrap(), Decimal256(I256::from_i128(&env, 7123456789012345678)) @@ -606,7 +617,7 @@ mod tests { } #[test] - fn decimal_is_zero_works() { + fn decimal256_is_zero_works() { let env = Env::default(); assert!(Decimal256::zero(&env).is_zero(&env)); assert!(Decimal256::percent(&env, 0).is_zero(&env)); @@ -618,7 +629,7 @@ mod tests { } #[test] - fn decimal_inv_works() { + fn decimal256_inv_works() { let env = Env::default(); // d = 0 @@ -683,11 +694,11 @@ mod tests { } #[test] - fn decimal_add_works() { + fn decimal256_add_works() { let env = Env::default(); let value = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - assert_eq!(value.0.to_i128(), 1_500_000_000_000_000_000); + assert_eq!(value.0.to_i128().unwrap(), 1_500_000_000_000_000_000); assert_eq!( Decimal256::percent(&env, 5) + Decimal256::percent(&env, 4), @@ -704,18 +715,20 @@ mod tests { } #[test] - #[should_panic(expected = "attempt to add with overflow")] - fn decimal_add_overflow_panics() { + // #[should_panic(expected = "attempt to add with overflow")] + // FIXME: Add proper panics + #[should_panic(expected = "Error(Object, ArithDomain)")] + fn decimal256_add_overflow_panics() { let env = Env::default(); - let _value = Decimal256(I256::from_parts(i128::MAX, 0)) + Decimal256::percent(&env, 50); + let _value = Decimal256::max(&env) + Decimal256::percent(&env, 50); } #[test] - fn decimal_sub_works() { + fn decimal256_sub_works() { let env = Env::default(); let value = Decimal256::one(&env) - Decimal256::percent(&env, 50); // 0.5 - assert_eq!(value.0.to_i128(), 500_000_000_000_000_000); + assert_eq!(value.0.to_i128().unwrap(), 500_000_000_000_000_000); assert_eq!( Decimal256::percent(&env, 9) - Decimal256::percent(&env, 4), @@ -736,7 +749,7 @@ mod tests { } #[test] - fn decimal_implements_mul() { + fn decimal256_implements_mul() { let env = Env::default(); let one = Decimal256::one(&env); let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); @@ -744,525 +757,527 @@ mod tests { // 1*x and x*1 assert_eq!( - one * Decimal256::percent(&env, 0), + one.clone().mul(&env, &Decimal256::percent(&env, 0)), Decimal256::percent(&env, 0) ); assert_eq!( - one * Decimal256::percent(&env, 1), + one.clone().mul(&env, &Decimal256::percent(&env, 1)), Decimal256::percent(&env, 1) ); assert_eq!( - one * Decimal256::percent(&env, 10), + one.clone().mul(&env, &Decimal256::percent(&env, 10)), Decimal256::percent(&env, 10) ); assert_eq!( - one * Decimal256::percent(&env, 100), + one.clone().mul(&env, &Decimal256::percent(&env, 100)), Decimal256::percent(&env, 100) ); assert_eq!( - one * Decimal256::percent(&env, 1000), + one.clone().mul(&env, &Decimal256::percent(&env, 1000)), Decimal256::percent(&env, 1000) ); assert_eq!( - Decimal256::percent(&env, 0) * one, + Decimal256::percent(&env, 0).mul(&env, &one), Decimal256::percent(&env, 0) ); assert_eq!( - Decimal256::percent(&env, 1) * one, + Decimal256::percent(&env, 1).mul(&env, &one), Decimal256::percent(&env, 1) ); assert_eq!( - Decimal256::percent(&env, 10) * one, + Decimal256::percent(&env, 10).mul(&env, &one), Decimal256::percent(&env, 10) ); assert_eq!( - Decimal256::percent(&env, 100) * one, + Decimal256::percent(&env, 100).mul(&env, &one), Decimal256::percent(&env, 100) ); assert_eq!( - Decimal256::percent(&env, 1000) * one, + Decimal256::percent(&env, 1000).mul(&env, &one), Decimal256::percent(&env, 1000) ); // double assert_eq!( - two * Decimal256::percent(&env, 0), + two.clone().mul(&env, &Decimal256::percent(&env, 0)), Decimal256::percent(&env, 0) ); assert_eq!( - two * Decimal256::percent(&env, 1), + two.clone().mul(&env, &Decimal256::percent(&env, 1)), Decimal256::percent(&env, 2) ); assert_eq!( - two * Decimal256::percent(&env, 10), + two.clone().mul(&env, &Decimal256::percent(&env, 10)), Decimal256::percent(&env, 20) ); assert_eq!( - two * Decimal256::percent(&env, 100), + two.clone().mul(&env, &Decimal256::percent(&env, 100)), Decimal256::percent(&env, 200) ); assert_eq!( - two * Decimal256::percent(&env, 1000), + two.clone().mul(&env, &Decimal256::percent(&env, 1000)), Decimal256::percent(&env, 2000) ); assert_eq!( - Decimal256::percent(&env, 0) * two, + Decimal256::percent(&env, 0).mul(&env, &two), Decimal256::percent(&env, 0) ); assert_eq!( - Decimal256::percent(&env, 1) * two, + Decimal256::percent(&env, 1).mul(&env, &two), Decimal256::percent(&env, 2) ); assert_eq!( - Decimal256::percent(&env, 10) * two, + Decimal256::percent(&env, 10).mul(&env, &two), Decimal256::percent(&env, 20) ); assert_eq!( - Decimal256::percent(&env, 100) * two, + Decimal256::percent(&env, 100).mul(&env, &two), Decimal256::percent(&env, 200) ); assert_eq!( - Decimal256::percent(&env, 1000) * two, + Decimal256::percent(&env, 1000).mul(&env, &two), Decimal256::percent(&env, 2000) ); // half assert_eq!( - half * Decimal256::percent(&env, 0), + half.clone().mul(&env, &Decimal256::percent(&env, 0)), Decimal256::percent(&env, 0) ); assert_eq!( - half * Decimal256::percent(&env, 1), + half.clone().mul(&env, &Decimal256::percent(&env, 1)), Decimal256::permille(&env, 5) ); assert_eq!( - half * Decimal256::percent(&env, 10), + half.clone().mul(&env, &Decimal256::percent(&env, 10)), Decimal256::percent(&env, 5) ); assert_eq!( - half * Decimal256::percent(&env, 100), + half.clone().mul(&env, &Decimal256::percent(&env, 100)), Decimal256::percent(&env, 50) ); assert_eq!( - half * Decimal256::percent(&env, 1000), + half.clone().mul(&env, &Decimal256::percent(&env, 1000)), Decimal256::percent(&env, 500) ); assert_eq!( - Decimal256::percent(&env, 0) * half, + Decimal256::percent(&env, 0).mul(&env, &half), Decimal256::percent(&env, 0) ); assert_eq!( - Decimal256::percent(&env, 1) * half, + Decimal256::percent(&env, 1).mul(&env, &half), Decimal256::permille(&env, 5) ); assert_eq!( - Decimal256::percent(&env, 10) * half, + Decimal256::percent(&env, 10).mul(&env, &half), Decimal256::percent(&env, 5) ); assert_eq!( - Decimal256::percent(&env, 100) * half, + Decimal256::percent(&env, 100).mul(&env, &half), Decimal256::percent(&env, 50) ); assert_eq!( - Decimal256::percent(&env, 1000) * half, + Decimal256::percent(&env, 1000).mul(&env, &half), Decimal256::percent(&env, 500) ); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_mul_overflow_panics() { - let env = Env::default(); - let _value = Decimal256(I256::from_parts(i128::MAX, 0)) * Decimal256::percent(&env, 101); - } - - #[test] - // in this test the Decimal256 is on the right - fn i128_decimal_multiply() { - let env = Env::default(); - - // a*b - let left = I256::from_i128(&env, 300); - let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - assert_eq!(left * right, I256::from_i128(&env, 450)); - - // a*0 - let left = I256::from_i128(&env, 300); - let right = Decimal256::zero(&env); - assert_eq!(left * right, I256::from_i128(&env, 0)); - - // 0*a - let left = I256::from_i128(&env, 0); - let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - assert_eq!(left * right, I256::from_i128(&env, 0)); - - assert_eq!( - I256::from_i128(&env, 0) * Decimal256::one(&env), - I256::from_i128(&env, 0) - ); - assert_eq!( - I256::from_i128(&env, 1) * Decimal256::one(&env), - I256::from_i128(&env, 1) - ); - assert_eq!( - I256::from_i128(&env, 2) * Decimal256::one(&env), - I256::from_i128(&env, 2) - ); - - assert_eq!( - I256::from_i128(&env, 1) * Decimal256::percent(&env, 10), - I256::from_i128(&env, 0) - ); - assert_eq!( - I256::from_i128(&env, 10) * Decimal256::percent(&env, 10), - I256::from_i128(&env, 1) - ); - assert_eq!( - I256::from_i128(&env, 100) * Decimal256::percent(&env, 10), - I256::from_i128(&env, 10) - ); - - assert_eq!( - I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), - I256::from_i128(&env, 0) - ); - assert_eq!( - I256::from_i128(&env, 100) * Decimal256::percent(&env, 50), - I256::from_i128(&env, 50) - ); - assert_eq!( - I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), - I256::from_i128(&env, 1600) - ); - assert_eq!( - I256::from_i128(&env, 999) * Decimal256::percent(&env, 50), - I256::from_i128(&env, 499) - ); // default rounding down - - assert_eq!( - I256::from_i128(&env, 1) * Decimal256::percent(&env, 200), - I256::from_i128(&env, 2) - ); - assert_eq!( - I256::from_i128(&env, 1000) * Decimal256::percent(&env, 200), - I256::from_i128(&env, 2000) - ); - } - - #[test] - // in this test the Decimal256 is on the left - fn decimal_i128_multiply() { + // #[should_panic(expected = "attempt to multiply with overflow")] + // FIXME: Add proper panics + #[should_panic(expected = "Error(Object, ArithDomain)")] + fn decimal256_mul_overflow_panics() { let env = Env::default(); + let _value = Decimal256::max(&env).mul(&env, &Decimal256::percent(&env, 101)); + } + + // #[test] + // // in this test the Decimal256 is on the right + // fn i128_decimal256_multiply() { + // let env = Env::default(); + + // // a*b + // let left = I256::from_i128(&env, 300); + // let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + // assert_eq!(left * right, I256::from_i128(&env, 450)); + + // // a*0 + // let left = I256::from_i128(&env, 300); + // let right = Decimal256::zero(&env); + // assert_eq!(left * right, I256::from_i128(&env, 0)); + + // // 0*a + // let left = I256::from_i128(&env, 0); + // let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + // assert_eq!(left * right, I256::from_i128(&env, 0)); + + // assert_eq!( + // I256::from_i128(&env, 0) * Decimal256::one(&env), + // I256::from_i128(&env, 0) + // ); + // assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::one(&env), + // I256::from_i128(&env, 1) + // ); + // assert_eq!( + // I256::from_i128(&env, 2) * Decimal256::one(&env), + // I256::from_i128(&env, 2) + // ); + + // assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::percent(&env, 10), + // I256::from_i128(&env, 0) + // ); + // assert_eq!( + // I256::from_i128(&env, 10) * Decimal256::percent(&env, 10), + // I256::from_i128(&env, 1) + // ); + // assert_eq!( + // I256::from_i128(&env, 100) * Decimal256::percent(&env, 10), + // I256::from_i128(&env, 10) + // ); + + // assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 0) + // ); + // assert_eq!( + // I256::from_i128(&env, 100) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 50) + // ); + // assert_eq!( + // I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 1600) + // ); + // assert_eq!( + // I256::from_i128(&env, 999) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 499) + // ); // default rounding down + + // assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::percent(&env, 200), + // I256::from_i128(&env, 2) + // ); + // assert_eq!( + // I256::from_i128(&env, 1000) * Decimal256::percent(&env, 200), + // I256::from_i128(&env, 2000) + // ); + // } - // a*b - let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - let right = I256::from_i128(&env, 300); - assert_eq!(left * right, I256::from_i128(&env, 450)); - - // 0*a - let left = Decimal256::zero(&env); - let right = I256::from_i128(&env, 300); - assert_eq!(left * right, I256::from_i128(&env, 0)); - - // a*0 - let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - let right = I256::from_i128(&env, 0); - assert_eq!(left * right, I256::from_i128(&env, 0)); - } - - #[test] - fn decimal_implements_div() { - let env = Env::default(); - let one = Decimal256::one(&env); - let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); - let half = Decimal256::percent(&env, 50); - - // 1/x and x/1 - assert_eq!( - one.div(&env, Decimal256::percent(&env, 1)), - Decimal256::percent(&env, 10_000) - ); - assert_eq!( - one.div(&env, Decimal256::percent(&env, 10)), - Decimal256::percent(&env, 1_000) - ); - assert_eq!( - one.div(&env, Decimal256::percent(&env, 100)), - Decimal256::percent(&env, 100) - ); - assert_eq!( - one.div(&env, Decimal256::percent(&env, 1000)), - Decimal256::percent(&env, 10) - ); - assert_eq!( - Decimal256::percent(&env, 0).div(&env, one), - Decimal256::percent(&env, 0) - ); - assert_eq!( - Decimal256::percent(&env, 1).div(&env, one), - Decimal256::percent(&env, 1) - ); - assert_eq!( - Decimal256::percent(&env, 10).div(&env, one), - Decimal256::percent(&env, 10) - ); - assert_eq!( - Decimal256::percent(&env, 100).div(&env, one), - Decimal256::percent(&env, 100) - ); - assert_eq!( - Decimal256::percent(&env, 1000).div(&env, one), - Decimal256::percent(&env, 1000) - ); - - // double - assert_eq!( - two.div(&env, Decimal256::percent(&env, 1)), - Decimal256::percent(&env, 20_000) - ); - assert_eq!( - two.div(&env, Decimal256::percent(&env, 10)), - Decimal256::percent(&env, 2_000) - ); - assert_eq!( - two.div(&env, Decimal256::percent(&env, 100)), - Decimal256::percent(&env, 200) - ); - assert_eq!( - two.div(&env, Decimal256::percent(&env, 1000)), - Decimal256::percent(&env, 20) - ); - assert_eq!( - Decimal256::percent(&env, 0).div(&env, two), - Decimal256::percent(&env, 0) - ); - assert_eq!( - Decimal256::percent(&env, 10).div(&env, two), - Decimal256::percent(&env, 5) - ); - assert_eq!( - Decimal256::percent(&env, 100).div(&env, two), - Decimal256::percent(&env, 50) - ); - assert_eq!( - Decimal256::percent(&env, 1000).div(&env, two), - Decimal256::percent(&env, 500) - ); - - // half - assert_eq!( - half.div(&env, Decimal256::percent(&env, 1)), - Decimal256::percent(&env, 5_000) - ); - assert_eq!( - half.div(&env, Decimal256::percent(&env, 10)), - Decimal256::percent(&env, 500) - ); - assert_eq!( - half.div(&env, Decimal256::percent(&env, 100)), - Decimal256::percent(&env, 50) - ); - assert_eq!( - half.div(&env, Decimal256::percent(&env, 1000)), - Decimal256::percent(&env, 5) - ); - assert_eq!( - Decimal256::percent(&env, 0).div(&env, half), - Decimal256::percent(&env, 0) - ); - assert_eq!( - Decimal256::percent(&env, 1).div(&env, half), - Decimal256::percent(&env, 2) - ); - assert_eq!( - Decimal256::percent(&env, 10).div(&env, half), - Decimal256::percent(&env, 20) - ); - assert_eq!( - Decimal256::percent(&env, 100).div(&env, half), - Decimal256::percent(&env, 200) - ); - assert_eq!( - Decimal256::percent(&env, 1000).div(&env, half), - Decimal256::percent(&env, 2000) - ); + // #[test] + // // in this test the Decimal256 is on the left + // fn decimal256_i128_multiply() { + // let env = Env::default(); + + // // a*b + // let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + // let right = I256::from_i128(&env, 300); + // assert_eq!(left * right, I256::from_i128(&env, 450)); + + // // 0*a + // let left = Decimal256::zero(&env); + // let right = I256::from_i128(&env, 300); + // assert_eq!(left * right, I256::from_i128(&env, 0)); + + // // a*0 + // let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + // let right = I256::from_i128(&env, 0); + // assert_eq!(left * right, I256::from_i128(&env, 0)); + // } - assert_eq!( - Decimal256::percent(&env, 15).div(&env, Decimal256::percent(&env, 60)), - Decimal256::percent(&env, 25) - ); - } + // #[test] + // fn decimal256_implements_div() { + // let env = Env::default(); + // let one = Decimal256::one(&env); + // let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); + // let half = Decimal256::percent(&env, 50); + + // // 1/x and x/1 + // assert_eq!( + // one.div(&env, Decimal256::percent(&env, 1)), + // Decimal256::percent(&env, 10_000) + // ); + // assert_eq!( + // one.div(&env, Decimal256::percent(&env, 10)), + // Decimal256::percent(&env, 1_000) + // ); + // assert_eq!( + // one.div(&env, Decimal256::percent(&env, 100)), + // Decimal256::percent(&env, 100) + // ); + // assert_eq!( + // one.div(&env, Decimal256::percent(&env, 1000)), + // Decimal256::percent(&env, 10) + // ); + // assert_eq!( + // Decimal256::percent(&env, 0).div(&env, one), + // Decimal256::percent(&env, 0) + // ); + // assert_eq!( + // Decimal256::percent(&env, 1).div(&env, one), + // Decimal256::percent(&env, 1) + // ); + // assert_eq!( + // Decimal256::percent(&env, 10).div(&env, one), + // Decimal256::percent(&env, 10) + // ); + // assert_eq!( + // Decimal256::percent(&env, 100).div(&env, one), + // Decimal256::percent(&env, 100) + // ); + // assert_eq!( + // Decimal256::percent(&env, 1000).div(&env, one), + // Decimal256::percent(&env, 1000) + // ); + + // // double + // assert_eq!( + // two.div(&env, Decimal256::percent(&env, 1)), + // Decimal256::percent(&env, 20_000) + // ); + // assert_eq!( + // two.div(&env, Decimal256::percent(&env, 10)), + // Decimal256::percent(&env, 2_000) + // ); + // assert_eq!( + // two.div(&env, Decimal256::percent(&env, 100)), + // Decimal256::percent(&env, 200) + // ); + // assert_eq!( + // two.div(&env, Decimal256::percent(&env, 1000)), + // Decimal256::percent(&env, 20) + // ); + // assert_eq!( + // Decimal256::percent(&env, 0).div(&env, two), + // Decimal256::percent(&env, 0) + // ); + // assert_eq!( + // Decimal256::percent(&env, 10).div(&env, two), + // Decimal256::percent(&env, 5) + // ); + // assert_eq!( + // Decimal256::percent(&env, 100).div(&env, two), + // Decimal256::percent(&env, 50) + // ); + // assert_eq!( + // Decimal256::percent(&env, 1000).div(&env, two), + // Decimal256::percent(&env, 500) + // ); + + // // half + // assert_eq!( + // half.div(&env, Decimal256::percent(&env, 1)), + // Decimal256::percent(&env, 5_000) + // ); + // assert_eq!( + // half.div(&env, Decimal256::percent(&env, 10)), + // Decimal256::percent(&env, 500) + // ); + // assert_eq!( + // half.div(&env, Decimal256::percent(&env, 100)), + // Decimal256::percent(&env, 50) + // ); + // assert_eq!( + // half.div(&env, Decimal256::percent(&env, 1000)), + // Decimal256::percent(&env, 5) + // ); + // assert_eq!( + // Decimal256::percent(&env, 0).div(&env, half), + // Decimal256::percent(&env, 0) + // ); + // assert_eq!( + // Decimal256::percent(&env, 1).div(&env, half), + // Decimal256::percent(&env, 2) + // ); + // assert_eq!( + // Decimal256::percent(&env, 10).div(&env, half), + // Decimal256::percent(&env, 20) + // ); + // assert_eq!( + // Decimal256::percent(&env, 100).div(&env, half), + // Decimal256::percent(&env, 200) + // ); + // assert_eq!( + // Decimal256::percent(&env, 1000).div(&env, half), + // Decimal256::percent(&env, 2000) + // ); + + // assert_eq!( + // Decimal256::percent(&env, 15).div(&env, Decimal256::percent(&env, 60)), + // Decimal256::percent(&env, 25) + // ); + // } - #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_div_overflow_panics() { - let env = Env::default(); - let _value = - Decimal256(I256::from_parts(i128::MAX, 0)).div(&env, Decimal256::percent(&env, 10)); - } + // #[test] + // #[should_panic(expected = "attempt to multiply with overflow")] + // fn decimal256_div_overflow_panics() { + // let env = Env::default(); + // let _value = + // Decimal256(I256::from_parts(i128::MAX, 0)).div(&env, Decimal256::percent(&env, 10)); + // } - #[test] - #[should_panic(expected = "Division failed - denominator must not be zero")] - fn decimal_div_by_zero_panics() { - let env = Env::default(); - let _value = Decimal256::one(&env).div(&env, Decimal256::zero(&env)); - } + // #[test] + // #[should_panic(expected = "Division failed - denominator must not be zero")] + // fn decimal256_div_by_zero_panics() { + // let env = Env::default(); + // let _value = Decimal256::one(&env).div(&env, Decimal256::zero(&env)); + // } - #[test] - fn decimal_i128_division() { - let env = Env::default(); + // #[test] + // fn decimal256_i128_division() { + // let env = Env::default(); - // a/b - let left = Decimal256::percent(&env, 150); // 1.5 - let right = I256::from_i128(&env, 3); - assert_eq!(left.div_by_i256(&env, right), Decimal256::percent(&env, 50)); + // // a/b + // let left = Decimal256::percent(&env, 150); // 1.5 + // let right = I256::from_i128(&env, 3); + // assert_eq!(left.div_by_i256(&env, right), Decimal256::percent(&env, 50)); - // 0/a - let left = Decimal256::zero(&env); - let right = I256::from_i128(&env, 300); - assert_eq!(left.div_by_i256(&env, right), Decimal256::zero(&env)); - } + // // 0/a + // let left = Decimal256::zero(&env); + // let right = I256::from_i128(&env, 300); + // assert_eq!(left.div_by_i256(&env, right), Decimal256::zero(&env)); + // } - #[test] - #[should_panic(expected = "attempt to divide by zero")] - fn decimal_i128_divide_by_zero() { - let env = Env::default(); - let left = Decimal256::percent(&env, 150); // 1.5 - let right = I256::from_i128(&env, 0); - let _result = left.div_by_i256(&env, right); - } + // #[test] + // #[should_panic(expected = "attempt to divide by zero")] + // fn decimal256_i128_divide_by_zero() { + // let env = Env::default(); + // let left = Decimal256::percent(&env, 150); // 1.5 + // let right = I256::from_i128(&env, 0); + // let _result = left.div_by_i256(&env, right); + // } - #[test] - fn decimal_pow_works() { - let env = Env::default(); - assert_eq!( - Decimal256::percent(&env, 200).pow(&env, 2), - Decimal256::percent(&env, 400) - ); - assert_eq!( - Decimal256::percent(&env, 100).pow(&env, 10), - Decimal256::percent(&env, 100) - ); - } + // #[test] + // fn decimal256_pow_works() { + // let env = Env::default(); + // assert_eq!( + // Decimal256::percent(&env, 200).pow(&env, 2), + // Decimal256::percent(&env, 400) + // ); + // assert_eq!( + // Decimal256::percent(&env, 100).pow(&env, 10), + // Decimal256::percent(&env, 100) + // ); + // } - #[test] - #[should_panic] - fn decimal_pow_overflow_panics() { - let env = Env::default(); - _ = Decimal256(I256::from_parts(i128::MAX, 0)).pow(&env, 2u32); - } + // #[test] + // #[should_panic] + // fn decimal256_pow_overflow_panics() { + // let env = Env::default(); + // _ = Decimal256(I256::from_parts(i128::MAX, 0)).pow(&env, 2u32); + // } - #[test] - fn decimal_abs_with_negative_number() { - let env = Env::default(); - let decimal = Decimal256::new(&env, -128); + // #[test] + // fn decimal256_abs_with_negative_number() { + // let env = Env::default(); + // let decimal = Decimal256::new(&env, -128); - assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); - } + // assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); + // } - #[test] - fn decimal_abs_with_positive_number() { - let env = Env::default(); - let decimal = Decimal256::new(&env, 128); + // #[test] + // fn decimal256_abs_with_positive_number() { + // let env = Env::default(); + // let decimal = Decimal256::new(&env, 128); - assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); - } + // assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); + // } - #[test] - fn decimal_displayed_as_string() { - let env = Env::default(); - let decimal = Decimal256::percent(&env, 128); + // #[test] + // fn decimal256_displayed_as_string() { + // let env = Env::default(); + // let decimal = Decimal256::percent(&env, 128); - // Convert expected string to Soroban SDK String - let expected_msg = "1.28"; - let expected_string = String::from_str(&env, expected_msg); + // // Convert expected string to Soroban SDK String + // let expected_msg = "1.28"; + // let expected_string = String::from_str(&env, expected_msg); - // Convert decimal to String and get its byte representation - let result_string = decimal.to_string(&env); - let result_string_len = result_string.len() as usize; - let mut result_bytes = alloc::vec![0u8; result_string_len]; - result_string.copy_into_slice(&mut result_bytes); + // // Convert decimal to String and get its byte representation + // let result_string = decimal.to_string(&env); + // let result_string_len = result_string.len() as usize; + // let mut result_bytes = alloc::vec![0u8; result_string_len]; + // result_string.copy_into_slice(&mut result_bytes); - // Get byte representation of expected string - let expected_string_len = expected_string.len() as usize; - let mut expected_bytes = alloc::vec![0u8; expected_string_len]; - expected_string.copy_into_slice(&mut expected_bytes); + // // Get byte representation of expected string + // let expected_string_len = expected_string.len() as usize; + // let mut expected_bytes = alloc::vec![0u8; expected_string_len]; + // expected_string.copy_into_slice(&mut expected_bytes); - assert_eq!(result_bytes, expected_bytes); - } + // assert_eq!(result_bytes, expected_bytes); + // } - #[test] - fn decimal_fmt_without_fractional_part() { - let env = Env::default(); - let value = Decimal256::from_atomics(&env, 100, 0); - assert_eq!(format!("{}", value), "100"); - } + // #[test] + // fn decimal256_fmt_without_fractional_part() { + // let env = Env::default(); + // let value = Decimal256::from_atomics(&env, 100, 0); + // assert_eq!(format!("{}", value), "100"); + // } - #[test] - fn decimal_fmt_fractional_part() { - let env = Env::default(); - let value = Decimal256::from_atomics(&env, 123456789, 5); - assert_eq!(format!("{}", value), "1234.56789"); - } + // #[test] + // fn decimal256_fmt_fractional_part() { + // let env = Env::default(); + // let value = Decimal256::from_atomics(&env, 123456789, 5); + // assert_eq!(format!("{}", value), "1234.56789"); + // } - #[test] - fn decimal_fmt_fractional_part_with_trailing_zeros() { - // 12345.6 - let env = Env::default(); - let value = Decimal256::from_atomics(&env, 123456, 1); - assert_eq!(format!("{}", value), "12345.6"); - } + // #[test] + // fn decimal256_fmt_fractional_part_with_trailing_zeros() { + // // 12345.6 + // let env = Env::default(); + // let value = Decimal256::from_atomics(&env, 123456, 1); + // assert_eq!(format!("{}", value), "12345.6"); + // } - #[test] - fn decimal_fmt_only_fractional() { - // 0.0789 - let env = Env::default(); - let value = Decimal256::from_atomics(&env, 789, 4); - assert_eq!(format!("{}", value), "0.0789"); - } + // #[test] + // fn decimal256_fmt_only_fractional() { + // // 0.0789 + // let env = Env::default(); + // let value = Decimal256::from_atomics(&env, 789, 4); + // assert_eq!(format!("{}", value), "0.0789"); + // } - #[test] - fn test_denominator() { - let env = Env::default(); - let decimal = Decimal256::percent(&env, 123); - assert_eq!(decimal.denominator(&env), Decimal256::denominator(&env)); - } + // #[test] + // fn test_denominator() { + // let env = Env::default(); + // let decimal = Decimal256::percent(&env, 123); + // assert_eq!(decimal.denominator(&env), Decimal256::denominator(&env)); + // } - #[test] - fn test_atomics() { - let env = Env::default(); - let decimal = Decimal256::percent(&env, 123); - assert_eq!(decimal.atomics(), 1230000000000000000); - } + // #[test] + // fn test_atomics() { + // let env = Env::default(); + // let decimal = Decimal256::percent(&env, 123); + // assert_eq!(decimal.atomics(), 1230000000000000000); + // } - #[test] - fn test_to_i128_with_precision() { - let env = Env::default(); - let decimal = Decimal256::percent(&env, 124); - assert_eq!(decimal.to_i128_with_precision(1), 12); - assert_eq!(decimal.to_i128_with_precision(2), 124); - } + // #[test] + // fn test_to_i128_with_precision() { + // let env = Env::default(); + // let decimal = Decimal256::percent(&env, 124); + // assert_eq!(decimal.to_i128_with_precision(1), 12); + // assert_eq!(decimal.to_i128_with_precision(2), 124); + // } - #[test] - fn test_multiply_ratio() { - let env = Env::default(); - let decimal = Decimal256::percent(&env, 1); - let numerator = Decimal256::new(&env, 2); - let denominator = Decimal256::new(&env, 5); - - // decimal is 10_000_000_000_000_000, atomics would be same - // numerator is 20_000_000_000_000_000, atomics would be same - // denominator is 50_000_000_000_000_000, amount would be same - // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 - // decimal from ratio - // numerator 200_000_000_000_000_000_000_000_000_000 - // denominator = 50_000_000_000_000_000 - // numerator * DECIMAL_FRACTIONAL / denominator is the result - assert_eq!( - decimal.multiply_ratio(&env, numerator, denominator), - Decimal256::new(&env, 4000000000000000000000000000000000) - ); - } + // #[test] + // fn test_multiply_ratio() { + // let env = Env::default(); + // let decimal = Decimal256::percent(&env, 1); + // let numerator = Decimal256::new(&env, 2); + // let denominator = Decimal256::new(&env, 5); + + // // decimal is 10_000_000_000_000_000, atomics would be same + // // numerator is 20_000_000_000_000_000, atomics would be same + // // denominator is 50_000_000_000_000_000, amount would be same + // // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 + // // decimal from ratio + // // numerator 200_000_000_000_000_000_000_000_000_000 + // // denominator = 50_000_000_000_000_000 + // // numerator * decimal256_FRACTIONAL / denominator is the result + // assert_eq!( + // decimal.multiply_ratio(&env, numerator, denominator), + // Decimal256::new(&env, 4000000000000000000000000000000000) + // ); + // } #[test] fn test_abs_difference() { @@ -1270,8 +1285,11 @@ mod tests { let a = Decimal256::new(&env, 100); let b = Decimal256::new(&env, 200); - assert_eq!(a.abs_diff(&env, b), Decimal256::new(&env, 100)); - assert_eq!(b.abs_diff(&env, a), Decimal256::new(&env, 100)); + assert_eq!( + a.clone().abs_diff(&env, b.clone()), + Decimal256::new(&env, 100) + ); + assert_eq!(b.clone().abs_diff(&env, a), Decimal256::new(&env, 100)); } #[test] @@ -1287,7 +1305,7 @@ mod tests { } #[test] - fn test_decimal_places() { + fn test_decimal256_places() { let env = Env::default(); let a = Decimal256::percent(&env, 50); From c9a19952492e70012600859d74c7072a4f21f34c Mon Sep 17 00:00:00 2001 From: Jakub Date: Mon, 10 Jun 2024 11:37:04 +0200 Subject: [PATCH 12/25] Decimal256: Test minor methods --- packages/decimal/src/decimal256.rs | 82 ++++++++++++++++-------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index c2c14878b..69558a4b0 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -33,6 +33,10 @@ impl Decimal256 { Self(I256::from_i128(env, value)) } + pub fn decimal_fractional(env: &Env) -> I256 { + I256::from_i128(env, 1_000_000_000_000_000_000i128) // 1*10**18 + } + pub fn one(env: &Env) -> Self { Self(I256::from_i128(env, 1_000_000_000_000_000_000)) } @@ -1236,48 +1240,48 @@ mod tests { // assert_eq!(format!("{}", value), "0.0789"); // } - // #[test] - // fn test_denominator() { - // let env = Env::default(); - // let decimal = Decimal256::percent(&env, 123); - // assert_eq!(decimal.denominator(&env), Decimal256::denominator(&env)); - // } + #[test] + fn test_denominator() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 123); + assert_eq!(decimal.denominator(&env), Decimal256::decimal_fractional(&env)); + } - // #[test] - // fn test_atomics() { - // let env = Env::default(); - // let decimal = Decimal256::percent(&env, 123); - // assert_eq!(decimal.atomics(), 1230000000000000000); - // } + #[test] + fn test_atomics() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 123); + assert_eq!(decimal.atomics().unwrap(), 1230000000000000000); + } - // #[test] - // fn test_to_i128_with_precision() { - // let env = Env::default(); - // let decimal = Decimal256::percent(&env, 124); - // assert_eq!(decimal.to_i128_with_precision(1), 12); - // assert_eq!(decimal.to_i128_with_precision(2), 124); - // } + #[test] + fn test_to_i128_with_precision() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 124); + assert_eq!(decimal.to_i128_with_precision(1), 12); + assert_eq!(decimal.to_i128_with_precision(2), 124); + } - // #[test] - // fn test_multiply_ratio() { - // let env = Env::default(); - // let decimal = Decimal256::percent(&env, 1); - // let numerator = Decimal256::new(&env, 2); - // let denominator = Decimal256::new(&env, 5); - - // // decimal is 10_000_000_000_000_000, atomics would be same - // // numerator is 20_000_000_000_000_000, atomics would be same - // // denominator is 50_000_000_000_000_000, amount would be same - // // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 - // // decimal from ratio - // // numerator 200_000_000_000_000_000_000_000_000_000 - // // denominator = 50_000_000_000_000_000 - // // numerator * decimal256_FRACTIONAL / denominator is the result - // assert_eq!( - // decimal.multiply_ratio(&env, numerator, denominator), - // Decimal256::new(&env, 4000000000000000000000000000000000) - // ); - // } + #[test] + fn test_multiply_ratio() { + let env = Env::default(); + let decimal = Decimal256::percent(&env, 1); + let numerator = Decimal256::new(&env, 2); + let denominator = Decimal256::new(&env, 5); + + // decimal is 10_000_000_000_000_000, atomics would be same + // numerator is 20_000_000_000_000_000, atomics would be same + // denominator is 50_000_000_000_000_000, amount would be same + // decimal * numerator = 200_000_000_000_000_000_000_000_000_000 + // decimal from ratio + // numerator 200_000_000_000_000_000_000_000_000_000 + // denominator = 50_000_000_000_000_000 + // numerator * decimal256_FRACTIONAL / denominator is the result + assert_eq!( + decimal.multiply_ratio(&env, numerator, denominator), + Decimal256::new(&env, 4000000000000000000000000000000000) + ); + } #[test] fn test_abs_difference() { From b81c66d1d69a01e60cdb0958bb89e8859e9f541f Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Wed, 31 Jul 2024 17:43:53 +0300 Subject: [PATCH 13/25] removes unnecessary functionality --- packages/decimal/src/decimal256.rs | 41 ++++-------------------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 69558a4b0..8346280a0 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -1,13 +1,11 @@ // A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache // License 2.0 - https://github.com/CosmWasm/cosmwasm. -use soroban_sdk::{Env, String, I256}; +use soroban_sdk::{Env, I256}; use core::{ cmp::{Ordering, PartialEq, PartialOrd}, - fmt, ops::{Add, Div, Mul, Sub}, - str::FromStr, }; extern crate alloc; @@ -190,10 +188,6 @@ impl Decimal256 { } } - pub fn to_string(&self, env: &Env) -> String { - String::from_str(env, alloc::format!("{}", self).as_str()) - } - pub fn abs_diff(self, env: &Env, other: Self) -> Self { let diff = self .0 @@ -261,37 +255,9 @@ impl Decimal256 { } } -impl fmt::Display for Decimal256 { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let env = Env::default(); - let whole = self - .0 - .div(&I256::from_i128(&env, 1_000_000_000_000_000_000)) - .to_i128() - .unwrap(); - let fractional = self - .0 - .rem_euclid(&I256::from_i128(&env, 1_000_000_000_000_000_000)) - .to_i128() - .unwrap(); - - if fractional == 0 { - write!(f, "{}", whole) - } else { - let fractional_string = alloc::format!("{:0>padding$}", fractional, padding = 18); - f.write_fmt(format_args!( - "{}.{}", - whole, - fractional_string.trim_end_matches('0') - )) - } - } -} - #[cfg(test)] mod tests { use super::*; - use alloc::format; #[test] fn decimal256_new() { @@ -1244,7 +1210,10 @@ mod tests { fn test_denominator() { let env = Env::default(); let decimal = Decimal256::percent(&env, 123); - assert_eq!(decimal.denominator(&env), Decimal256::decimal_fractional(&env)); + assert_eq!( + decimal.denominator(&env), + Decimal256::decimal_fractional(&env) + ); } #[test] From 5efd29aa86290874df236a44f065afb52df73aee Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Wed, 31 Jul 2024 20:00:59 +0300 Subject: [PATCH 14/25] fixes some of the tests --- packages/decimal/src/decimal256.rs | 188 ++++++++++++++++------------- 1 file changed, 102 insertions(+), 86 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 8346280a0..d69d5dd7a 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -257,6 +257,9 @@ impl Decimal256 { #[cfg(test)] mod tests { + use alloc::borrow::ToOwned; + use soroban_sdk::FromVal; + use super::*; #[test] @@ -690,7 +693,7 @@ mod tests { #[should_panic(expected = "Error(Object, ArithDomain)")] fn decimal256_add_overflow_panics() { let env = Env::default(); - let _value = Decimal256::max(&env) + Decimal256::percent(&env, 50); + let _ = Decimal256::max(&env) + Decimal256::percent(&env, 50); } #[test] @@ -861,99 +864,112 @@ mod tests { let _value = Decimal256::max(&env).mul(&env, &Decimal256::percent(&env, 101)); } - // #[test] - // // in this test the Decimal256 is on the right - // fn i128_decimal256_multiply() { - // let env = Env::default(); - - // // a*b - // let left = I256::from_i128(&env, 300); - // let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - // assert_eq!(left * right, I256::from_i128(&env, 450)); - - // // a*0 - // let left = I256::from_i128(&env, 300); - // let right = Decimal256::zero(&env); - // assert_eq!(left * right, I256::from_i128(&env, 0)); + #[test] + fn i128_decimal256_multiply() { + let env = Env::default(); - // // 0*a - // let left = I256::from_i128(&env, 0); - // let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - // assert_eq!(left * right, I256::from_i128(&env, 0)); + // a*b + let left = Decimal256::from_str_with_env(&env, "300").unwrap(); + let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + assert_eq!( + left.mul(&env, &right), + Decimal256::from_str_with_env(&env, "450").unwrap() + ); - // assert_eq!( - // I256::from_i128(&env, 0) * Decimal256::one(&env), - // I256::from_i128(&env, 0) - // ); - // assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::one(&env), - // I256::from_i128(&env, 1) - // ); - // assert_eq!( - // I256::from_i128(&env, 2) * Decimal256::one(&env), - // I256::from_i128(&env, 2) - // ); + // a*0 + let left = Decimal256::from_str_with_env(&env, "300").unwrap(); + let right = Decimal256::zero(&env); + assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); - // assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::percent(&env, 10), - // I256::from_i128(&env, 0) - // ); - // assert_eq!( - // I256::from_i128(&env, 10) * Decimal256::percent(&env, 10), - // I256::from_i128(&env, 1) - // ); - // assert_eq!( - // I256::from_i128(&env, 100) * Decimal256::percent(&env, 10), - // I256::from_i128(&env, 10) - // ); + //// 0*a + let left = Decimal256::zero(&env); + let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); - // assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 0) - // ); - // assert_eq!( - // I256::from_i128(&env, 100) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 50) - // ); - // assert_eq!( - // I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 1600) - // ); - // assert_eq!( - // I256::from_i128(&env, 999) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 499) - // ); // default rounding down - - // assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::percent(&env, 200), - // I256::from_i128(&env, 2) - // ); - // assert_eq!( - // I256::from_i128(&env, 1000) * Decimal256::percent(&env, 200), - // I256::from_i128(&env, 2000) - // ); - // } + assert_eq!( + Decimal256::from_str_with_env(&env, "0") + .unwrap() + .mul(&env, &Decimal256::one(&env)), + Decimal256::zero(&env) + ); + assert_eq!( + I256::from_i128(&env, 1) * Decimal256::one(&env), + I256::from_i128(&env, 1) + ); + //assert_eq!( + // I256::from_i128(&env, 2) * Decimal256::one(&env), + // I256::from_i128(&env, 2) + //); + + //assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::percent(&env, 10), + // I256::from_i128(&env, 0) + //); + //assert_eq!( + // I256::from_i128(&env, 10) * Decimal256::percent(&env, 10), + // I256::from_i128(&env, 1) + //); + //assert_eq!( + // I256::from_i128(&env, 100) * Decimal256::percent(&env, 10), + // I256::from_i128(&env, 10) + //); + + //assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 0) + //); + //assert_eq!( + // I256::from_i128(&env, 100) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 50) + //); + //assert_eq!( + // I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 1600) + //); + //assert_eq!( + // I256::from_i128(&env, 999) * Decimal256::percent(&env, 50), + // I256::from_i128(&env, 499) + //); // default rounding down + + //assert_eq!( + // I256::from_i128(&env, 1) * Decimal256::percent(&env, 200), + // I256::from_i128(&env, 2) + //); + //assert_eq!( + // I256::from_i128(&env, 1000) * Decimal256::percent(&env, 200), + // I256::from_i128(&env, 2000) + //); + } - // #[test] - // // in this test the Decimal256 is on the left - // fn decimal256_i128_multiply() { - // let env = Env::default(); + // in this test the Decimal256 is on the left + #[test] + fn decimal256_multiplication() { + let env = Env::default(); - // // a*b - // let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - // let right = I256::from_i128(&env, 300); - // assert_eq!(left * right, I256::from_i128(&env, 450)); + // a*b + let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + let right = Decimal256::from_str_with_env(&env, "300").unwrap(); + assert_eq!( + left.mul(&env, &right), + Decimal256::from_str_with_env(&env, "450").unwrap() + ); - // // 0*a - // let left = Decimal256::zero(&env); - // let right = I256::from_i128(&env, 300); - // assert_eq!(left * right, I256::from_i128(&env, 0)); + // 0*a + let left = Decimal256::zero(&env); + let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + assert_eq!( + left.mul(&env, &right), + Decimal256::from_str_with_env(&env, "0").unwrap() + ); - // // a*0 - // let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - // let right = I256::from_i128(&env, 0); - // assert_eq!(left * right, I256::from_i128(&env, 0)); - // } + // a*0 + let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 + let right = Decimal256::zero(&env); + assert_eq!( + left.mul(&env, &right), + Decimal256::from_str_with_env(&env, "0").unwrap() + ); + } // #[test] // fn decimal256_implements_div() { From a09513e955f2473a74bfee75d6abc77277c916f4 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 13:43:41 +0300 Subject: [PATCH 15/25] refacots Decimal256 to be a wrapper around U256 and fixes the test --- packages/decimal/Cargo.toml | 5 +- packages/decimal/src/decimal256.rs | 392 ++++++++++++----------------- 2 files changed, 171 insertions(+), 226 deletions(-) diff --git a/packages/decimal/Cargo.toml b/packages/decimal/Cargo.toml index 89306a88a..05aa921ea 100644 --- a/packages/decimal/Cargo.toml +++ b/packages/decimal/Cargo.toml @@ -8,4 +8,7 @@ license = { workspace = true } description = "A precise decimal arithmetic package for Soroban contracts" [dependencies] -soroban-sdk = { workspace = true, features = ["alloc"]} +soroban-sdk = { workspace = true, features = ["alloc"] } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index d69d5dd7a..ccb3758da 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -1,7 +1,7 @@ // A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache // License 2.0 - https://github.com/CosmWasm/cosmwasm. -use soroban_sdk::{Env, I256}; +use soroban_sdk::{Env, I256, U256}; use core::{ cmp::{Ordering, PartialEq, PartialOrd}, @@ -17,87 +17,88 @@ enum Error { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] -pub struct Decimal256(I256); +//TODO: stays with U256 +pub struct Decimal256(U256); #[allow(dead_code)] impl Decimal256 { const DECIMAL_PLACES: i32 = 18; - pub fn new(env: &Env, value: i128) -> Self { - Decimal256(I256::from_i128(env, value)) + pub fn new(env: &Env, value: u128) -> Self { + Decimal256(U256::from_u128(env, value)) } - pub fn raw(env: &Env, value: i128) -> Self { - Self(I256::from_i128(env, value)) + pub fn raw(env: &Env, value: u128) -> Self { + Self(U256::from_u128(env, value)) } - pub fn decimal_fractional(env: &Env) -> I256 { - I256::from_i128(env, 1_000_000_000_000_000_000i128) // 1*10**18 + pub fn decimal_fractional(env: &Env) -> U256 { + U256::from_u128(env, 1_000_000_000_000_000_000u128) // 1*10**18 } pub fn one(env: &Env) -> Self { - Self(I256::from_i128(env, 1_000_000_000_000_000_000)) + Self(U256::from_u128(env, 1_000_000_000_000_000_000)) } pub fn zero(env: &Env) -> Self { - Self(I256::from_i128(env, 0)) + Self(U256::from_u128(env, 0)) } pub fn max(env: &Env) -> Self { - Decimal256(I256::from_parts( + Decimal256(U256::from_parts( env, - i64::MAX, + u64::MAX, u64::MAX, u64::MAX, u64::MAX, )) } - pub fn percent(env: &Env, x: i64) -> Self { - Self(I256::from_i128(env, (x as i128) * 10_000_000_000_000_000)) + pub fn percent(env: &Env, x: u64) -> Self { + Self(U256::from_u128(env, (x as u128) * 10_000_000_000_000_000)) } - pub fn permille(env: &Env, x: i64) -> Self { - Self(I256::from_i128(env, (x as i128) * 1_000_000_000_000_000)) + pub fn permille(env: &Env, x: u64) -> Self { + Self(U256::from_u128(env, (x as u128) * 1_000_000_000_000_000)) } - pub fn bps(env: &Env, x: i64) -> Self { - Self(I256::from_i128(env, (x as i128) * 100_000_000_000_000)) + pub fn bps(env: &Env, x: u64) -> Self { + Self(U256::from_u128(env, (x as u128) * 100_000_000_000_000)) } pub fn decimal_places(&self) -> i32 { Self::DECIMAL_PLACES } - fn numerator(&self) -> I256 { + fn numerator(&self) -> U256 { self.0.clone() } - fn denominator(&self, env: &Env) -> I256 { - I256::from_i128(env, 1_000_000_000_000_000_000) + fn denominator(&self, env: &Env) -> U256 { + U256::from_u128(env, 1_000_000_000_000_000_000) } pub fn is_zero(&self, env: &Env) -> bool { - self.0 == I256::from_i128(env, 0) + self.0 == U256::from_u128(env, 0) } - pub fn atomics(&self) -> Option { - self.0.to_i128() + pub fn atomics(&self) -> Option { + self.0.to_u128() } - pub fn from_atomics(env: &Env, atomics: i128, decimal_places: i32) -> Self { - const TEN: i128 = 10; + pub fn from_atomics(env: &Env, atomics: u128, decimal_places: i32) -> Self { + const TEN: u128 = 10; match decimal_places.cmp(&Self::DECIMAL_PLACES) { Ordering::Less => { let digits = Self::DECIMAL_PLACES - decimal_places; let factor = TEN.pow(digits as u32); - Self(I256::from_i128(env, atomics * factor)) + Self(U256::from_u128(env, atomics * factor)) } - Ordering::Equal => Self(I256::from_i128(env, atomics)), + Ordering::Equal => Self(U256::from_u128(env, atomics)), Ordering::Greater => { let digits = decimal_places - Self::DECIMAL_PLACES; let factor = TEN.pow(digits as u32); - Self(I256::from_i128(env, atomics / factor)) + Self(U256::from_u128(env, atomics / factor)) } } } @@ -132,23 +133,23 @@ impl Decimal256 { None } else { let fractional_squared = - I256::from_i128(env, 1_000_000_000_000_000_000_000_000_000_000_000_000); + U256::from_u128(env, 1_000_000_000_000_000_000_000_000_000_000_000_000); Some(Decimal256(fractional_squared.div(&self.0))) } } - pub fn from_ratio(env: &Env, numerator: impl Into, denominator: impl Into) -> Self { + pub fn from_ratio(env: &Env, numerator: impl Into, denominator: impl Into) -> Self { match Decimal256::checked_from_ratio(env, numerator, denominator) { Ok(ratio) => ratio, Err(Error::DivideByZero) => panic!("Denominator must not be zero"), } } - pub fn to_i128_with_precision(&self, precision: impl Into) -> i128 { + pub fn to_i128_with_precision(&self, precision: impl Into) -> u128 { let value = self.atomics().unwrap(); let precision = precision.into(); - let divisor = 10i128.pow((self.decimal_places() - precision) as u32); + let divisor = 10u128.pow((self.decimal_places() - precision) as u32); value / divisor } @@ -163,41 +164,33 @@ impl Decimal256 { fn checked_from_ratio( env: &Env, - numerator: impl Into, - denominator: impl Into, + numerator: impl Into, + denominator: impl Into, ) -> Result { let numerator = numerator.into(); let denominator = denominator.into(); - if denominator == I256::from_i128(env, 0) { + if denominator == U256::from_u128(env, 0) { return Err(Error::DivideByZero); } let ratio = numerator - .mul(&I256::from_i128(env, 1_000_000_000_000_000_000)) + .mul(&U256::from_u128(env, 1_000_000_000_000_000_000)) .div(&denominator); Ok(Decimal256(ratio)) } - pub fn abs(&self, env: &Env) -> Self { - if self.0.to_i128().unwrap() < 0 { - Decimal256(I256::from_i128(env, -self.0.to_i128().unwrap())) - } else { - self.clone() - } - } - pub fn abs_diff(self, env: &Env, other: Self) -> Self { let diff = self .0 - .to_i128() + .to_u128() .unwrap() - .abs_diff(other.0.to_i128().unwrap()); - Self(I256::from_i128(env, diff as i128)) + .abs_diff(other.0.to_u128().unwrap()); + Self(U256::from_u128(env, diff)) } - pub fn div_by_i256(&self, rhs: I256) -> Self { + pub fn div_by_i256(&self, rhs: U256) -> Self { Decimal256(self.0.div(&rhs)) } } @@ -233,26 +226,6 @@ impl Decimal256 { Err(Error::DivideByZero) => panic!("Division failed - denominator must not be zero"), } } - - pub fn from_str_with_env(env: &Env, input: &str) -> Result { - let mut parts_iter = input.split('.'); - - let whole_part = parts_iter.next().expect("Unexpected input format"); - let whole: i128 = whole_part.parse().expect("Error parsing whole"); - let mut atomics = I256::from_i128(env, whole * 1_000_000_000_000_000_000); - - if let Some(fractional_part) = parts_iter.next() { - let fractional: i128 = fractional_part.parse().expect("Error parsing fractional"); - let exp = 18 - fractional_part.len() as i32; - assert!(exp >= 0, "There must be at least one fractional digit"); - let fractional_factor = I256::from_i128(env, 10i128.pow(exp as u32)); - atomics = atomics.add(&I256::from_i128(env, fractional).mul(&fractional_factor)); - } - - assert!(parts_iter.next().is_none(), "Unexpected number of dots"); - - Ok(Decimal256(atomics)) - } } #[cfg(test)] @@ -260,14 +233,16 @@ mod tests { use alloc::borrow::ToOwned; use soroban_sdk::FromVal; + use crate::Decimal; + use super::*; #[test] fn decimal256_new() { let env = Env::default(); - let expected = 300i128; + let expected = 300u128; assert_eq!( - Decimal256::new(&env, expected).0.to_i128().unwrap(), + Decimal256::new(&env, expected).0.to_u128().unwrap(), expected ); } @@ -275,29 +250,29 @@ mod tests { #[test] fn decimal256_raw() { let env = Env::default(); - let value = 300i128; - assert_eq!(Decimal256::raw(&env, value).0.to_i128().unwrap(), value); + let value = 300u128; + assert_eq!(Decimal256::raw(&env, value).0.to_u128().unwrap(), value); } #[test] fn decimal256_one() { let env = Env::default(); let value = Decimal256::one(&env); - assert_eq!(value.0.to_i128().unwrap(), 1_000_000_000_000_000_000); + assert_eq!(value.0.to_u128().unwrap(), 1_000_000_000_000_000_000); } #[test] fn decimal256_zero() { let env = Env::default(); let value = Decimal256::zero(&env); - assert_eq!(value.0.to_i128().unwrap(), 0); + assert_eq!(value.0.to_u128().unwrap(), 0); } #[test] fn decimal256_percent() { let env = Env::default(); let value = Decimal256::percent(&env, 50); - assert_eq!(value.0.to_i128().unwrap(), 500_000_000_000_000_000); + assert_eq!(value.0.to_u128().unwrap(), 500_000_000_000_000_000); } #[test] @@ -343,31 +318,60 @@ mod tests { // Cuts decimal digits (20 provided but only 18 can be stored) assert_eq!( Decimal256::from_atomics(&env, 4321, 20), - Decimal256::from_str_with_env(&env, "0.000000000000000043").unwrap() + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 43), + U256::from_u128(&env, 1000000000000000000) + ), + //Decimal256::from_str_with_env(&env, "0.000000000000000043").unwrap() ); assert_eq!( Decimal256::from_atomics(&env, 6789, 20), - Decimal256::from_str_with_env(&env, "0.000000000000000067").unwrap() + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 67), + U256::from_u128(&env, 1000000000000000000) + ), ); assert_eq!( - Decimal256::from_atomics(&env, i128::MAX, 38), - Decimal256::from_str_with_env(&env, "1.701411834604692317").unwrap() + Decimal256::from_atomics(&env, u128::MAX, 38), + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 67), + U256::from_u128(&env, 1000000000000000000) + ), ); assert_eq!( - Decimal256::from_atomics(&env, i128::MAX, 39), - Decimal256::from_str_with_env(&env, "0.170141183460469231").unwrap() + Decimal256::from_atomics(&env, u128::MAX, 39), + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 67), + U256::from_u128(&env, 1000000000000000000) + ), ); assert_eq!( - Decimal256::from_atomics(&env, i128::MAX, 45), - Decimal256::from_str_with_env(&env, "0.000000170141183460").unwrap() + Decimal256::from_atomics(&env, u128::MAX, 45), + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 67), + U256::from_u128(&env, 1000000000000000000) + ), ); assert_eq!( - Decimal256::from_atomics(&env, i128::MAX, 51), - Decimal256::from_str_with_env(&env, "0.000000000000170141").unwrap() + Decimal256::from_atomics(&env, u128::MAX, 51), + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 67), + U256::from_u128(&env, 1000000000000000000) + ), ); assert_eq!( - Decimal256::from_atomics(&env, i128::MAX, 56), - Decimal256::from_str_with_env(&env, "0.000000000000000001").unwrap() + Decimal256::from_atomics(&env, u128::MAX, 56), + Decimal256::from_ratio( + &env, + U256::from_u128(&env, 67), + U256::from_u128(&env, 1000000000000000000) + ), ); } @@ -377,64 +381,64 @@ mod tests { // 1.0 assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 1)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 1)), Decimal256::one(&env) ); assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 53), I256::from_i128(&env, 53)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 53), U256::from_u128(&env, 53)), Decimal256::one(&env) ); assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 125), I256::from_i128(&env, 125)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 125), U256::from_u128(&env, 125)), Decimal256::one(&env) ); // 1.5 assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 3), I256::from_i128(&env, 2)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 3), U256::from_u128(&env, 2)), Decimal256::percent(&env, 150) ); assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 150), I256::from_i128(&env, 100)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 150), U256::from_u128(&env, 100)), Decimal256::percent(&env, 150) ); assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 333), I256::from_i128(&env, 222)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 333), U256::from_u128(&env, 222)), Decimal256::percent(&env, 150) ); // 0.125 assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 8)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 8)), Decimal256::permille(&env, 125) ); assert_eq!( Decimal256::from_ratio( &env, - I256::from_i128(&env, 125), - I256::from_i128(&env, 1000) + U256::from_u128(&env, 125), + U256::from_u128(&env, 1000) ), Decimal256::permille(&env, 125) ); // 1/3 (result floored) assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 3)), - Decimal256(I256::from_i128(&env, 333_333_333_333_333_333)) + Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 3)), + Decimal256(U256::from_u128(&env, 333_333_333_333_333_333)) ); // 2/3 (result floored) assert_eq!( - Decimal256::from_ratio(&env, I256::from_i128(&env, 2), I256::from_i128(&env, 3)), - Decimal256(I256::from_i128(&env, 666_666_666_666_666_666)) + Decimal256::from_ratio(&env, U256::from_u128(&env, 2), U256::from_u128(&env, 3)), + Decimal256(U256::from_u128(&env, 666_666_666_666_666_666)) ); // large inputs assert_eq!( Decimal256::from_ratio( &env, - I256::from_i128(&env, 0), - I256::from_i128(&env, i128::MAX) + U256::from_u128(&env, 0), + U256::from_u128(&env, u128::MAX) ), Decimal256::zero(&env) ); @@ -446,8 +450,8 @@ mod tests { assert_eq!( Decimal256::from_ratio( &env, - I256::from_i128(&env, 340282366920938), - I256::from_i128(&env, 340282366920938) + U256::from_u128(&env, 340282366920938), + U256::from_u128(&env, 340282366920938) ), Decimal256::one(&env) ); @@ -455,16 +459,16 @@ mod tests { assert_eq!( Decimal256::from_ratio( &env, - I256::from_i128(&env, 34028236692093900000), - I256::from_i128(&env, 34028236692093900000) + U256::from_u128(&env, 34028236692093900000), + U256::from_u128(&env, 34028236692093900000) ), Decimal256::one(&env) ); assert_eq!( Decimal256::from_ratio( &env, - I256::from_i128(&env, 34028236692093900000), - I256::from_i128(&env, 1) + U256::from_u128(&env, 34028236692093900000), + U256::from_u128(&env, 1) ), Decimal256::new(&env, 34028236692093900000 * 1_000_000_000_000_000_000) ); @@ -474,7 +478,7 @@ mod tests { #[should_panic(expected = "Denominator must not be zero")] fn decimal256_from_ratio_panics_for_zero_denominator() { let env = Env::default(); - Decimal256::from_ratio(&env, I256::from_i128(&env, 1), I256::from_i128(&env, 0)); + Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 0)); } #[ignore = "FIXME: Why is I256 not panicking?"] @@ -484,8 +488,8 @@ mod tests { let env = Env::default(); Decimal256::from_ratio( &env, - I256::from_i128(&env, i128::MAX), - I256::from_i128(&env, 1), + U256::from_u128(&env, u128::MAX), + U256::from_u128(&env, 1), ); } @@ -506,87 +510,20 @@ mod tests { } #[test] - fn decimal256_from_str_works() { + fn decimal256_from_fraction_compared_to_percent() { let env = Env::default(); // Integers + assert_eq!(Decimal256::zero(&env), Decimal256::percent(&env, 0)); + assert_eq!(Decimal256::one(&env), Decimal256::percent(&env, 100)); assert_eq!( - Decimal256::from_str_with_env(&env, "0").unwrap(), - Decimal256::percent(&env, 0) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "1").unwrap(), - Decimal256::percent(&env, 100) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "5").unwrap(), + Decimal256::from_ratio(&env, U256::from_u128(&env, 5), U256::from_u128(&env, 1)), Decimal256::percent(&env, 500) ); assert_eq!( - Decimal256::from_str_with_env(&env, "42").unwrap(), + Decimal256::from_ratio(&env, U256::from_u128(&env, 42), U256::from_u128(&env, 1)), Decimal256::percent(&env, 4200) ); - assert_eq!( - Decimal256::from_str_with_env(&env, "000").unwrap(), - Decimal256::percent(&env, 0) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "001").unwrap(), - Decimal256::percent(&env, 100) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "005").unwrap(), - Decimal256::percent(&env, 500) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "0042").unwrap(), - Decimal256::percent(&env, 4200) - ); - - // Decimal256s - assert_eq!( - Decimal256::from_str_with_env(&env, "1.0").unwrap(), - Decimal256::percent(&env, 100) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "1.5").unwrap(), - Decimal256::percent(&env, 150) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "0.5").unwrap(), - Decimal256::percent(&env, 50) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "0.123").unwrap(), - Decimal256::permille(&env, 123) - ); - - assert_eq!( - Decimal256::from_str_with_env(&env, "40.00").unwrap(), - Decimal256::percent(&env, 4000) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "04.00").unwrap(), - Decimal256::percent(&env, 400) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "00.40").unwrap(), - Decimal256::percent(&env, 40) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "00.04").unwrap(), - Decimal256::percent(&env, 4) - ); - - // Can handle decimal256_PLACES fractional digits - assert_eq!( - Decimal256::from_str_with_env(&env, "7.123456789012345678").unwrap(), - Decimal256(I256::from_i128(&env, 7123456789012345678)) - ); - assert_eq!( - Decimal256::from_str_with_env(&env, "7.999999999999999999").unwrap(), - Decimal256(I256::from_i128(&env, 7999999999999999999)) - ); } #[test] @@ -634,16 +571,16 @@ mod tests { Decimal256::percent(&env, 300).inv(&env), Some(Decimal256::from_ratio( &env, - I256::from_i128(&env, 1), - I256::from_i128(&env, 3) + U256::from_u128(&env, 1), + U256::from_u128(&env, 3) )) ); assert_eq!( Decimal256::percent(&env, 600).inv(&env), Some(Decimal256::from_ratio( &env, - I256::from_i128(&env, 1), - I256::from_i128(&env, 6) + U256::from_u128(&env, 1), + U256::from_u128(&env, 6) )) ); @@ -671,7 +608,7 @@ mod tests { let env = Env::default(); let value = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - assert_eq!(value.0.to_i128().unwrap(), 1_500_000_000_000_000_000); + assert_eq!(value.0.to_u128().unwrap(), 1_500_000_000_000_000_000); assert_eq!( Decimal256::percent(&env, 5) + Decimal256::percent(&env, 4), @@ -701,7 +638,7 @@ mod tests { let env = Env::default(); let value = Decimal256::one(&env) - Decimal256::percent(&env, 50); // 0.5 - assert_eq!(value.0.to_i128().unwrap(), 500_000_000_000_000_000); + assert_eq!(value.0.to_u128().unwrap(), 500_000_000_000_000_000); assert_eq!( Decimal256::percent(&env, 9) - Decimal256::percent(&env, 4), @@ -869,15 +806,17 @@ mod tests { let env = Env::default(); // a*b - let left = Decimal256::from_str_with_env(&env, "300").unwrap(); + let left = + Decimal256::from_ratio(&env, U256::from_u128(&env, 300), U256::from_u128(&env, 1)); let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 assert_eq!( left.mul(&env, &right), - Decimal256::from_str_with_env(&env, "450").unwrap() + Decimal256::from_ratio(&env, U256::from_u128(&env, 450), U256::from_u128(&env, 1)), ); // a*0 - let left = Decimal256::from_str_with_env(&env, "300").unwrap(); + let left = + Decimal256::from_ratio(&env, U256::from_u128(&env, 300), U256::from_u128(&env, 1)); let right = Decimal256::zero(&env); assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); @@ -887,40 +826,47 @@ mod tests { assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); assert_eq!( - Decimal256::from_str_with_env(&env, "0") - .unwrap() + Decimal256::from_ratio(&env, U256::from_u128(&env, 300), U256::from_u128(&env, 0)) .mul(&env, &Decimal256::one(&env)), Decimal256::zero(&env) ); assert_eq!( - I256::from_i128(&env, 1) * Decimal256::one(&env), - I256::from_i128(&env, 1) + Decimal256::one(&env).mul(&env, &Decimal256::one(&env)), + Decimal256::one(&env) + ); + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 2), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::one(&env)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 2), U256::from_u128(&env, 1)) ); - //assert_eq!( - // I256::from_i128(&env, 2) * Decimal256::one(&env), - // I256::from_i128(&env, 2) - //); - //assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::percent(&env, 10), - // I256::from_i128(&env, 0) - //); - //assert_eq!( - // I256::from_i128(&env, 10) * Decimal256::percent(&env, 10), - // I256::from_i128(&env, 1) - //); - //assert_eq!( - // I256::from_i128(&env, 100) * Decimal256::percent(&env, 10), - // I256::from_i128(&env, 10) - //); + // 1 * %10 + assert_eq!( + Decimal256::one(&env,).mul(&env, &Decimal256::percent(&env, 10)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 10)) + ); + + // 10 * %10 + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 10), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::percent(&env, 10)), + Decimal256::one(&env) + ); + + // 100 * %10 + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 100), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::percent(&env, 10)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 10), U256::from_u128(&env, 1)) + ); //assert_eq!( // I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), // I256::from_i128(&env, 0) //); //assert_eq!( - // I256::from_i128(&env, 100) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 50) + // U256::from_u128(&env, 100) * Decimal256::percent(&env, 50), + // U256::from_u128(&env, 50) //); //assert_eq!( // I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), @@ -948,27 +894,23 @@ mod tests { // a*b let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - let right = Decimal256::from_str_with_env(&env, "300").unwrap(); + let right = + Decimal256::from_ratio(&env, U256::from_u128(&env, 300), U256::from_u128(&env, 1)); + assert_eq!( left.mul(&env, &right), - Decimal256::from_str_with_env(&env, "450").unwrap() + Decimal256::from_ratio(&env, U256::from_u128(&env, 450), U256::from_u128(&env, 1)) ); // 0*a let left = Decimal256::zero(&env); let right = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 - assert_eq!( - left.mul(&env, &right), - Decimal256::from_str_with_env(&env, "0").unwrap() - ); + assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); // a*0 let left = Decimal256::one(&env) + Decimal256::percent(&env, 50); // 1.5 let right = Decimal256::zero(&env); - assert_eq!( - left.mul(&env, &right), - Decimal256::from_str_with_env(&env, "0").unwrap() - ); + assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); } // #[test] From 5f23192fa4c6a936494ac0bf05be567961169c3f Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 14:41:23 +0300 Subject: [PATCH 16/25] fixes multiplication by zero --- packages/decimal/src/decimal256.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index ccb3758da..688df0607 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -337,7 +337,7 @@ mod tests { Decimal256::from_atomics(&env, u128::MAX, 38), Decimal256::from_ratio( &env, - U256::from_u128(&env, 67), + U256::from_u128(&env, 34), U256::from_u128(&env, 1000000000000000000) ), ); @@ -826,8 +826,7 @@ mod tests { assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); assert_eq!( - Decimal256::from_ratio(&env, U256::from_u128(&env, 300), U256::from_u128(&env, 0)) - .mul(&env, &Decimal256::one(&env)), + Decimal256::zero(&env).mul(&env, &Decimal256::one(&env)), Decimal256::zero(&env) ); assert_eq!( From c7b91fa3e632e56e7b0397f60ae870b1db444c02 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 16:44:47 +0300 Subject: [PATCH 17/25] updates the README.md of package decimal and fixes tests --- packages/decimal/README.md | 23 +++++++- packages/decimal/src/decimal256.rs | 93 ++++++++++++++++++------------ 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/packages/decimal/README.md b/packages/decimal/README.md index 1b42e1242..0e0cd47be 100644 --- a/packages/decimal/README.md +++ b/packages/decimal/README.md @@ -2,7 +2,8 @@ This code is taken from the [cosmwasm-std crate](https://github.com/CosmWasm/cosmwasm.), which is licensed under the Apache License 2.0 The contract provides a `Decimal` struct for arithmetic operations, suitable for blockchain De-Fi operations, where precision is of highest importance. It ensures that calculations are accurate up to 18 decimal places. -## Messages +# Decimal(i128) +## Methods - `new(value: i128) -> Self`: Creates a new Decimal. - `raw(value: i128) -> Self`: Returns the raw value from `i128`. @@ -16,3 +17,23 @@ The contract provides a `Decimal` struct for arithmetic operations, suitable for - `from_ratio(numerator: impl Into, denominator: impl Into) -> Self`: Returns the ratio (numerator / denominator) as a Decimal. - `abs(&self) -> Self`: Returns the absolute value of the Decimal. - `to_string(&self, env: &Env) -> String`: Converts the Decimal to a string. + + +# Decimal256 +## Methods + +- `new(value: u128) -> Self`: Creates a new Decimal. +- `raw(value: u128) -> Self`: Returns the raw value from `u128`. +- `one() -> Self`: Create a `1.0` Decimal. +- `zero() -> Self`: Create a `0.0` Decimal. +- `percent(x: u64) -> Self`: Convert `x%` into Decimal. +- `permille(x: u64) -> Self`: Convert permille `(x/1000)` into Decimal. +- `bps(x: u64) -> Self`: Convert basis points `(x/10000)` into Decimal. +- `from_atomics(atomics: u128, decimal_places: i32) -> Self`: Creates a Decimal from atomic units and decimal places. +- `inv(&self) -> Option`: Returns the multiplicative inverse `1/d` for decimal `d`. +- `from_ratio(numerator: impl Into, denominator: impl Into) -> Self`: Returns the ratio (numerator / denominator) as a Decimal. +- `abs(&self) -> Self`: Returns the absolute value of the Decimal. +- `to_string(&self, env: &Env) -> String`: Converts the Decimal to a string. + + +N.B.: `from_atomics(atomics: u128, decimal_places: i32) -> Self` currently supports maximum `38` as input for `decimcal_places` diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 688df0607..5be8110dc 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -17,7 +17,6 @@ enum Error { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] -//TODO: stays with U256 pub struct Decimal256(U256); #[allow(dead_code)] @@ -86,16 +85,22 @@ impl Decimal256 { self.0.to_u128() } + // TODO: Allow for `decimal_places` larger than 38 pub fn from_atomics(env: &Env, atomics: u128, decimal_places: i32) -> Self { const TEN: u128 = 10; match decimal_places.cmp(&Self::DECIMAL_PLACES) { Ordering::Less => { + soroban_sdk::testutils::arbitrary::std::dbg!(); let digits = Self::DECIMAL_PLACES - decimal_places; let factor = TEN.pow(digits as u32); Self(U256::from_u128(env, atomics * factor)) } - Ordering::Equal => Self(U256::from_u128(env, atomics)), + Ordering::Equal => { + soroban_sdk::testutils::arbitrary::std::dbg!(); + Self(U256::from_u128(env, atomics)) + } Ordering::Greater => { + soroban_sdk::testutils::arbitrary::std::dbg!(); let digits = decimal_places - Self::DECIMAL_PLACES; let factor = TEN.pow(digits as u32); Self(U256::from_u128(env, atomics / factor)) @@ -145,7 +150,7 @@ impl Decimal256 { } } - pub fn to_i128_with_precision(&self, precision: impl Into) -> u128 { + pub fn to_u128_with_precision(&self, precision: impl Into) -> u128 { let value = self.atomics().unwrap(); let precision = precision.into(); @@ -177,7 +182,6 @@ impl Decimal256 { let ratio = numerator .mul(&U256::from_u128(env, 1_000_000_000_000_000_000)) .div(&denominator); - Ok(Decimal256(ratio)) } @@ -190,7 +194,7 @@ impl Decimal256 { Self(U256::from_u128(env, diff)) } - pub fn div_by_i256(&self, rhs: U256) -> Self { + pub fn div_by_u256(&self, rhs: U256) -> Self { Decimal256(self.0.div(&rhs)) } } @@ -323,7 +327,6 @@ mod tests { U256::from_u128(&env, 43), U256::from_u128(&env, 1000000000000000000) ), - //Decimal256::from_str_with_env(&env, "0.000000000000000043").unwrap() ); assert_eq!( Decimal256::from_atomics(&env, 6789, 20), @@ -334,45 +337,59 @@ mod tests { ), ); assert_eq!( - Decimal256::from_atomics(&env, u128::MAX, 38), - Decimal256::from_ratio( - &env, - U256::from_u128(&env, 34), - U256::from_u128(&env, 1000000000000000000) - ), - ); - assert_eq!( - Decimal256::from_atomics(&env, u128::MAX, 39), - Decimal256::from_ratio( - &env, - U256::from_u128(&env, 67), - U256::from_u128(&env, 1000000000000000000) - ), - ); - assert_eq!( - Decimal256::from_atomics(&env, u128::MAX, 45), - Decimal256::from_ratio( - &env, - U256::from_u128(&env, 67), - U256::from_u128(&env, 1000000000000000000) - ), - ); - assert_eq!( - Decimal256::from_atomics(&env, u128::MAX, 51), + // 340282366920938463463374607431768211455 / 10000000000000000000 (10^19) = 3.40282366920938463463374607431768211455 + Decimal256::from_atomics(&env, u128::MAX, 37), Decimal256::from_ratio( &env, - U256::from_u128(&env, 67), - U256::from_u128(&env, 1000000000000000000) + U256::from_u128(&env, 340282366920938463463374607431768211455), + U256::from_u128(&env, 10000000000000000000000000000000000000) ), ); assert_eq!( - Decimal256::from_atomics(&env, u128::MAX, 56), + // 340282366920938463463374607431768211455 / 100000000000000000000 (10^20) = 3.40282366920938463463374607431768211455 + Decimal256::from_atomics(&env, u128::MAX, 38), Decimal256::from_ratio( &env, - U256::from_u128(&env, 67), - U256::from_u128(&env, 1000000000000000000) + U256::from_u128(&env, 340282366920938463463374607431768211455), + U256::from_u128(&env, 100000000000000000000000000000000000000) ), ); + // TODO: we can handle up to 38 `decimal_places` as input in `from_atomics`:w + //assert_eq!( + // // 340282366920938463463374607431768211455 / 1000000000000000000000 (10^21) = 340282366920.938463463374607432 + // Decimal256::from_atomics(&env, u128::MAX, 39), + // Decimal256::from_ratio( + // &env, + // U256::from_u128(&env, 340282366920938463463374607432), + // U256::from_u128(&env, 1000000000000000000) + // ) + //); + //assert_eq!( + // // 340282366920938463463374607431768211455 / 1000000000000000000000000000 (10^27) = ? + // Decimal256::from_atomics(&env, u128::MAX, 45), + // Decimal256::from_ratio( + // &env, + // U256::from_u128(&env, 67), + // U256::from_u128(&env, 1000000000000000000) + // ), + //); + //assert_eq!( + // // 340282366920938463463374607431768211455 / 1000000000000000000000000000000000 (10^33) = ? + // Decimal256::from_atomics(&env, u128::MAX, 51), + // Decimal256::from_ratio( + // &env, + // U256::from_u128(&env, 67), + // U256::from_u128(&env, 1000000000000000000) + // ), + //); + //assert_eq!( + // Decimal256::from_atomics(&env, u128::MAX, 56), + // Decimal256::from_ratio( + // &env, + // U256::from_u128(&env, 67), + // U256::from_u128(&env, 1000000000000000000) + // ), + //); } #[test] @@ -1184,8 +1201,8 @@ mod tests { fn test_to_i128_with_precision() { let env = Env::default(); let decimal = Decimal256::percent(&env, 124); - assert_eq!(decimal.to_i128_with_precision(1), 12); - assert_eq!(decimal.to_i128_with_precision(2), 124); + assert_eq!(decimal.to_u128_with_precision(1), 12); + assert_eq!(decimal.to_u128_with_precision(2), 124); } #[test] From 0d0b9507c363f5c4f9b70e09d72c71cf9a53a831 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 16:55:26 +0300 Subject: [PATCH 18/25] success test --- packages/decimal/src/decimal256.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 5be8110dc..c505e3caf 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -1,7 +1,7 @@ // A lot of this code is taken from the cosmwasm-std crate, which is licensed under the Apache // License 2.0 - https://github.com/CosmWasm/cosmwasm. -use soroban_sdk::{Env, I256, U256}; +use soroban_sdk::{Env, U256}; use core::{ cmp::{Ordering, PartialEq, PartialOrd}, @@ -459,7 +459,14 @@ mod tests { ), Decimal256::zero(&env) ); - // assert_eq!(Decimal256::from_ratio(i128::MAX, i128::MAX), Decimal256::one()); + assert_eq!( + Decimal256::from_ratio( + &env, + U256::from_u128(&env, u128::MAX), + U256::from_u128(&env, u128::MAX) + ), + Decimal256::one(&env) + ); // due to limited possibilities - we're only allowed to use i128 as input - maximum // number this implementation supports without overflow is u128 / decimal256_FRACTIONAL @@ -498,7 +505,7 @@ mod tests { Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 0)); } - #[ignore = "FIXME: Why is I256 not panicking?"] + #[ignore = "FIXME: Why is U256 not panicking?"] #[test] #[should_panic(expected = "attempt to multiply with overflow")] fn decimal256_from_ratio_panics_for_mul_overflow() { @@ -876,10 +883,18 @@ mod tests { Decimal256::from_ratio(&env, U256::from_u128(&env, 10), U256::from_u128(&env, 1)) ); - //assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 0) - //); + // 1 * %50 + assert_eq!( + Decimal256::one(&env).mul(&env, &Decimal256::percent(&env, 50)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 2)) + ); + + // 100 * %50 + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 100), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::percent(&env, 50)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 50), U256::from_u128(&env, 1)) + ); //assert_eq!( // U256::from_u128(&env, 100) * Decimal256::percent(&env, 50), // U256::from_u128(&env, 50) From 7fe03f767b4463f92ca8b39caf70ba2fc81f7ffa Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 17:02:02 +0300 Subject: [PATCH 19/25] more tests and comments for 'em --- packages/decimal/src/decimal256.rs | 46 +++++++++++++++++------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index c505e3caf..4cb7a0070 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -895,27 +895,33 @@ mod tests { .mul(&env, &Decimal256::percent(&env, 50)), Decimal256::from_ratio(&env, U256::from_u128(&env, 50), U256::from_u128(&env, 1)) ); - //assert_eq!( - // U256::from_u128(&env, 100) * Decimal256::percent(&env, 50), - // U256::from_u128(&env, 50) - //); - //assert_eq!( - // I256::from_i128(&env, 3200) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 1600) - //); - //assert_eq!( - // I256::from_i128(&env, 999) * Decimal256::percent(&env, 50), - // I256::from_i128(&env, 499) - //); // default rounding down - //assert_eq!( - // I256::from_i128(&env, 1) * Decimal256::percent(&env, 200), - // I256::from_i128(&env, 2) - //); - //assert_eq!( - // I256::from_i128(&env, 1000) * Decimal256::percent(&env, 200), - // I256::from_i128(&env, 2000) - //); + // 3200 * %50 + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 3_200), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::percent(&env, 50)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 1_600), U256::from_u128(&env, 1)) + ); + + // 999 * %50 + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 999), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::percent(&env, 50)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 4995), U256::from_u128(&env, 10)) + ); + + // 1 * %200 + assert_eq!( + Decimal256::one(&env).mul(&env, &Decimal256::percent(&env, 200)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 2), U256::from_u128(&env, 1)) + ); + + // 1_000 * %200 + assert_eq!( + Decimal256::from_ratio(&env, U256::from_u128(&env, 1_000), U256::from_u128(&env, 1)) + .mul(&env, &Decimal256::percent(&env, 200)), + Decimal256::from_ratio(&env, U256::from_u128(&env, 2_000), U256::from_u128(&env, 1)) + ); } // in this test the Decimal256 is on the left From fd3ba369347147ec7c70b3dce440e461431dbb49 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 17:44:41 +0300 Subject: [PATCH 20/25] division with Decimal256 --- packages/decimal/src/decimal256.rs | 286 +++++++++++++++++------------ 1 file changed, 168 insertions(+), 118 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 4cb7a0070..95ed65d54 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -950,128 +950,178 @@ mod tests { assert_eq!(left.mul(&env, &right), Decimal256::zero(&env)); } - // #[test] - // fn decimal256_implements_div() { - // let env = Env::default(); - // let one = Decimal256::one(&env); - // let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); - // let half = Decimal256::percent(&env, 50); + #[test] + fn decimal256_implements_div() { + let env = Env::default(); + let one = Decimal256::one(&env); + let two = Decimal256::new(&env, 2 * 1_000_000_000_000_000_000); + let half = Decimal256::percent(&env, 50); - // // 1/x and x/1 - // assert_eq!( - // one.div(&env, Decimal256::percent(&env, 1)), - // Decimal256::percent(&env, 10_000) - // ); - // assert_eq!( - // one.div(&env, Decimal256::percent(&env, 10)), - // Decimal256::percent(&env, 1_000) - // ); - // assert_eq!( - // one.div(&env, Decimal256::percent(&env, 100)), - // Decimal256::percent(&env, 100) - // ); - // assert_eq!( - // one.div(&env, Decimal256::percent(&env, 1000)), - // Decimal256::percent(&env, 10) - // ); - // assert_eq!( - // Decimal256::percent(&env, 0).div(&env, one), - // Decimal256::percent(&env, 0) - // ); - // assert_eq!( - // Decimal256::percent(&env, 1).div(&env, one), - // Decimal256::percent(&env, 1) - // ); - // assert_eq!( - // Decimal256::percent(&env, 10).div(&env, one), - // Decimal256::percent(&env, 10) - // ); - // assert_eq!( - // Decimal256::percent(&env, 100).div(&env, one), - // Decimal256::percent(&env, 100) - // ); - // assert_eq!( - // Decimal256::percent(&env, 1000).div(&env, one), - // Decimal256::percent(&env, 1000) - // ); + // 1/x and x/1 + // 1 / %1 + assert_eq!( + one.div(&env, Decimal256::percent(&env, 1)), + Decimal256::percent(&env, 10_000) + ); - // // double - // assert_eq!( - // two.div(&env, Decimal256::percent(&env, 1)), - // Decimal256::percent(&env, 20_000) - // ); - // assert_eq!( - // two.div(&env, Decimal256::percent(&env, 10)), - // Decimal256::percent(&env, 2_000) - // ); - // assert_eq!( - // two.div(&env, Decimal256::percent(&env, 100)), - // Decimal256::percent(&env, 200) - // ); - // assert_eq!( - // two.div(&env, Decimal256::percent(&env, 1000)), - // Decimal256::percent(&env, 20) - // ); - // assert_eq!( - // Decimal256::percent(&env, 0).div(&env, two), - // Decimal256::percent(&env, 0) - // ); - // assert_eq!( - // Decimal256::percent(&env, 10).div(&env, two), - // Decimal256::percent(&env, 5) - // ); - // assert_eq!( - // Decimal256::percent(&env, 100).div(&env, two), - // Decimal256::percent(&env, 50) - // ); - // assert_eq!( - // Decimal256::percent(&env, 1000).div(&env, two), - // Decimal256::percent(&env, 500) - // ); + // 1 / %10 + assert_eq!( + one.div(&env, Decimal256::percent(&env, 10)), + Decimal256::percent(&env, 1_000) + ); - // // half - // assert_eq!( - // half.div(&env, Decimal256::percent(&env, 1)), - // Decimal256::percent(&env, 5_000) - // ); - // assert_eq!( - // half.div(&env, Decimal256::percent(&env, 10)), - // Decimal256::percent(&env, 500) - // ); - // assert_eq!( - // half.div(&env, Decimal256::percent(&env, 100)), - // Decimal256::percent(&env, 50) - // ); - // assert_eq!( - // half.div(&env, Decimal256::percent(&env, 1000)), - // Decimal256::percent(&env, 5) - // ); - // assert_eq!( - // Decimal256::percent(&env, 0).div(&env, half), - // Decimal256::percent(&env, 0) - // ); - // assert_eq!( - // Decimal256::percent(&env, 1).div(&env, half), - // Decimal256::percent(&env, 2) - // ); - // assert_eq!( - // Decimal256::percent(&env, 10).div(&env, half), - // Decimal256::percent(&env, 20) - // ); - // assert_eq!( - // Decimal256::percent(&env, 100).div(&env, half), - // Decimal256::percent(&env, 200) - // ); - // assert_eq!( - // Decimal256::percent(&env, 1000).div(&env, half), - // Decimal256::percent(&env, 2000) - // ); + // 1 / %100 + assert_eq!( + one.div(&env, Decimal256::percent(&env, 100)), + Decimal256::one(&env) + ); - // assert_eq!( - // Decimal256::percent(&env, 15).div(&env, Decimal256::percent(&env, 60)), - // Decimal256::percent(&env, 25) - // ); - // } + // 1 / %1_000 + assert_eq!( + one.div(&env, Decimal256::percent(&env, 1_000)), + Decimal256::percent(&env, 10) + ); + + // %0 / 1 + assert_eq!( + Decimal256::percent(&env, 0).div(&env, one.clone()), + Decimal256::percent(&env, 0) + ); + + // %1 / 1 + assert_eq!( + Decimal256::percent(&env, 1).div(&env, one.clone()), + Decimal256::percent(&env, 1) + ); + + // %10 / 1 + assert_eq!( + Decimal256::percent(&env, 10).div(&env, one.clone()), + Decimal256::percent(&env, 10) + ); + + // %100 / 1 + assert_eq!( + Decimal256::percent(&env, 100).div(&env, one.clone()), + Decimal256::percent(&env, 100) + ); + + // %1_000 / 1 + assert_eq!( + Decimal256::percent(&env, 1_000).div(&env, one.clone()), + Decimal256::percent(&env, 1_000) + ); + + // 2/x and x/2 + // 2 / %1 + assert_eq!( + two.div(&env, Decimal256::percent(&env, 1)), + Decimal256::percent(&env, 20_000) + ); + + // 2 / %10 + assert_eq!( + two.div(&env, Decimal256::percent(&env, 10)), + Decimal256::percent(&env, 2_000) + ); + + // 2 / %100 + assert_eq!( + two.div(&env, Decimal256::percent(&env, 100)), + Decimal256::percent(&env, 200) + ); + + // 2 / %1_000 + assert_eq!( + two.div(&env, Decimal256::percent(&env, 1_000)), + Decimal256::percent(&env, 20) + ); + + // %0 / 2 + assert_eq!( + Decimal256::percent(&env, 0).div(&env, two.clone()), + Decimal256::percent(&env, 0) + ); + + // %10 / 2 + assert_eq!( + Decimal256::percent(&env, 10).div(&env, two.clone()), + Decimal256::percent(&env, 5) + ); + + // %100 / 2 + assert_eq!( + Decimal256::percent(&env, 100).div(&env, two.clone()), + Decimal256::percent(&env, 50) + ); + + // %1_000 / 2 + assert_eq!( + Decimal256::percent(&env, 1_000).div(&env, two.clone()), + Decimal256::percent(&env, 500) + ); + + // half/x and x/half + // half / %1 + assert_eq!( + half.div(&env, Decimal256::percent(&env, 1)), + Decimal256::percent(&env, 5_000) + ); + + // half / %10 + assert_eq!( + half.div(&env, Decimal256::percent(&env, 10)), + Decimal256::percent(&env, 500) + ); + + // half / %100 + assert_eq!( + half.div(&env, Decimal256::percent(&env, 100)), + Decimal256::percent(&env, 50) + ); + + // half / %1_000 + assert_eq!( + half.div(&env, Decimal256::percent(&env, 1_000)), + Decimal256::percent(&env, 5) + ); + + // %0 / half + assert_eq!( + Decimal256::percent(&env, 0).div(&env, half.clone()), + Decimal256::percent(&env, 0) + ); + + // %1 / half + assert_eq!( + Decimal256::percent(&env, 1).div(&env, half.clone()), + Decimal256::percent(&env, 2) + ); + + // %10 / half + assert_eq!( + Decimal256::percent(&env, 10).div(&env, half.clone()), + Decimal256::percent(&env, 20) + ); + + // %100 / half + assert_eq!( + Decimal256::percent(&env, 100).div(&env, half.clone()), + Decimal256::percent(&env, 200) + ); + + // %1_000 / half + assert_eq!( + Decimal256::percent(&env, 1_000).div(&env, half.clone()), + Decimal256::percent(&env, 2_000) + ); + + // %15 / half + assert_eq!( + Decimal256::percent(&env, 15).div(&env, Decimal256::percent(&env, 60)), + Decimal256::percent(&env, 25) + ); + } // #[test] // #[should_panic(expected = "attempt to multiply with overflow")] From a8c78e0c3a31eb9dd8b2e9797d280bfbc8d862c1 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 18:37:41 +0300 Subject: [PATCH 21/25] enables tests --- packages/decimal/src/decimal256.rs | 252 ++++++++++------------------- 1 file changed, 81 insertions(+), 171 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 95ed65d54..6f87e5267 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -90,17 +90,12 @@ impl Decimal256 { const TEN: u128 = 10; match decimal_places.cmp(&Self::DECIMAL_PLACES) { Ordering::Less => { - soroban_sdk::testutils::arbitrary::std::dbg!(); let digits = Self::DECIMAL_PLACES - decimal_places; let factor = TEN.pow(digits as u32); Self(U256::from_u128(env, atomics * factor)) } - Ordering::Equal => { - soroban_sdk::testutils::arbitrary::std::dbg!(); - Self(U256::from_u128(env, atomics)) - } + Ordering::Equal => Self(U256::from_u128(env, atomics)), Ordering::Greater => { - soroban_sdk::testutils::arbitrary::std::dbg!(); let digits = decimal_places - Self::DECIMAL_PLACES; let factor = TEN.pow(digits as u32); Self(U256::from_u128(env, atomics / factor)) @@ -234,11 +229,6 @@ impl Decimal256 { #[cfg(test)] mod tests { - use alloc::borrow::ToOwned; - use soroban_sdk::FromVal; - - use crate::Decimal; - use super::*; #[test] @@ -863,60 +853,60 @@ mod tests { Decimal256::from_ratio(&env, U256::from_u128(&env, 2), U256::from_u128(&env, 1)) ); - // 1 * %10 + // 1 * %0.1 assert_eq!( Decimal256::one(&env,).mul(&env, &Decimal256::percent(&env, 10)), Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 10)) ); - // 10 * %10 + // 10 * %0.1 assert_eq!( Decimal256::from_ratio(&env, U256::from_u128(&env, 10), U256::from_u128(&env, 1)) .mul(&env, &Decimal256::percent(&env, 10)), Decimal256::one(&env) ); - // 100 * %10 + // 100 * %0.1 assert_eq!( Decimal256::from_ratio(&env, U256::from_u128(&env, 100), U256::from_u128(&env, 1)) .mul(&env, &Decimal256::percent(&env, 10)), Decimal256::from_ratio(&env, U256::from_u128(&env, 10), U256::from_u128(&env, 1)) ); - // 1 * %50 + // 1 * %0.5 assert_eq!( Decimal256::one(&env).mul(&env, &Decimal256::percent(&env, 50)), Decimal256::from_ratio(&env, U256::from_u128(&env, 1), U256::from_u128(&env, 2)) ); - // 100 * %50 + // 100 * %0.5 assert_eq!( Decimal256::from_ratio(&env, U256::from_u128(&env, 100), U256::from_u128(&env, 1)) .mul(&env, &Decimal256::percent(&env, 50)), Decimal256::from_ratio(&env, U256::from_u128(&env, 50), U256::from_u128(&env, 1)) ); - // 3200 * %50 + // 3200 * %0.5 assert_eq!( Decimal256::from_ratio(&env, U256::from_u128(&env, 3_200), U256::from_u128(&env, 1)) .mul(&env, &Decimal256::percent(&env, 50)), Decimal256::from_ratio(&env, U256::from_u128(&env, 1_600), U256::from_u128(&env, 1)) ); - // 999 * %50 + // 999 * %0.5 assert_eq!( Decimal256::from_ratio(&env, U256::from_u128(&env, 999), U256::from_u128(&env, 1)) .mul(&env, &Decimal256::percent(&env, 50)), Decimal256::from_ratio(&env, U256::from_u128(&env, 4995), U256::from_u128(&env, 10)) ); - // 1 * %200 + // 1 * %2 assert_eq!( Decimal256::one(&env).mul(&env, &Decimal256::percent(&env, 200)), Decimal256::from_ratio(&env, U256::from_u128(&env, 2), U256::from_u128(&env, 1)) ); - // 1_000 * %200 + // 1_000 * %2 assert_eq!( Decimal256::from_ratio(&env, U256::from_u128(&env, 1_000), U256::from_u128(&env, 1)) .mul(&env, &Decimal256::percent(&env, 200)), @@ -958,25 +948,25 @@ mod tests { let half = Decimal256::percent(&env, 50); // 1/x and x/1 - // 1 / %1 + // 1 / %0.01 assert_eq!( one.div(&env, Decimal256::percent(&env, 1)), Decimal256::percent(&env, 10_000) ); - // 1 / %10 + // 1 / %0.1 assert_eq!( one.div(&env, Decimal256::percent(&env, 10)), Decimal256::percent(&env, 1_000) ); - // 1 / %100 + // 1 / %1 assert_eq!( one.div(&env, Decimal256::percent(&env, 100)), Decimal256::one(&env) ); - // 1 / %1_000 + // 1 / %10 assert_eq!( one.div(&env, Decimal256::percent(&env, 1_000)), Decimal256::percent(&env, 10) @@ -988,50 +978,50 @@ mod tests { Decimal256::percent(&env, 0) ); - // %1 / 1 + // %0.01 / 1 assert_eq!( Decimal256::percent(&env, 1).div(&env, one.clone()), Decimal256::percent(&env, 1) ); - // %10 / 1 + // %0.1 / 1 assert_eq!( Decimal256::percent(&env, 10).div(&env, one.clone()), Decimal256::percent(&env, 10) ); - // %100 / 1 + // %1 / 1 assert_eq!( Decimal256::percent(&env, 100).div(&env, one.clone()), Decimal256::percent(&env, 100) ); - // %1_000 / 1 + // %100 / 1 assert_eq!( Decimal256::percent(&env, 1_000).div(&env, one.clone()), Decimal256::percent(&env, 1_000) ); // 2/x and x/2 - // 2 / %1 + // 2 / %0.01 assert_eq!( two.div(&env, Decimal256::percent(&env, 1)), Decimal256::percent(&env, 20_000) ); - // 2 / %10 + // 2 / %0.1 assert_eq!( two.div(&env, Decimal256::percent(&env, 10)), Decimal256::percent(&env, 2_000) ); - // 2 / %100 + // 2 / %1 assert_eq!( two.div(&env, Decimal256::percent(&env, 100)), Decimal256::percent(&env, 200) ); - // 2 / %1_000 + // 2 / %10 assert_eq!( two.div(&env, Decimal256::percent(&env, 1_000)), Decimal256::percent(&env, 20) @@ -1043,44 +1033,44 @@ mod tests { Decimal256::percent(&env, 0) ); - // %10 / 2 + // %0.1 / 2 assert_eq!( Decimal256::percent(&env, 10).div(&env, two.clone()), Decimal256::percent(&env, 5) ); - // %100 / 2 + // %1 / 2 assert_eq!( Decimal256::percent(&env, 100).div(&env, two.clone()), Decimal256::percent(&env, 50) ); - // %1_000 / 2 + // %10 / 2 assert_eq!( Decimal256::percent(&env, 1_000).div(&env, two.clone()), Decimal256::percent(&env, 500) ); // half/x and x/half - // half / %1 + // half / %0.01 assert_eq!( half.div(&env, Decimal256::percent(&env, 1)), Decimal256::percent(&env, 5_000) ); - // half / %10 + // half / %0.1 assert_eq!( half.div(&env, Decimal256::percent(&env, 10)), Decimal256::percent(&env, 500) ); - // half / %100 + // half / %1 assert_eq!( half.div(&env, Decimal256::percent(&env, 100)), Decimal256::percent(&env, 50) ); - // half / %1_000 + // half / %10 assert_eq!( half.div(&env, Decimal256::percent(&env, 1_000)), Decimal256::percent(&env, 5) @@ -1092,164 +1082,84 @@ mod tests { Decimal256::percent(&env, 0) ); - // %1 / half + // %0.01 / half assert_eq!( Decimal256::percent(&env, 1).div(&env, half.clone()), Decimal256::percent(&env, 2) ); - // %10 / half + // %0.1 / half assert_eq!( Decimal256::percent(&env, 10).div(&env, half.clone()), Decimal256::percent(&env, 20) ); - // %100 / half + // %1 / half assert_eq!( Decimal256::percent(&env, 100).div(&env, half.clone()), Decimal256::percent(&env, 200) ); - // %1_000 / half + // %10 / half assert_eq!( Decimal256::percent(&env, 1_000).div(&env, half.clone()), Decimal256::percent(&env, 2_000) ); - // %15 / half + // %0.15 / %0.6 assert_eq!( Decimal256::percent(&env, 15).div(&env, Decimal256::percent(&env, 60)), Decimal256::percent(&env, 25) ); } - // #[test] - // #[should_panic(expected = "attempt to multiply with overflow")] - // fn decimal256_div_overflow_panics() { - // let env = Env::default(); - // let _value = - // Decimal256(I256::from_parts(i128::MAX, 0)).div(&env, Decimal256::percent(&env, 10)); - // } - - // #[test] - // #[should_panic(expected = "Division failed - denominator must not be zero")] - // fn decimal256_div_by_zero_panics() { - // let env = Env::default(); - // let _value = Decimal256::one(&env).div(&env, Decimal256::zero(&env)); - // } - - // #[test] - // fn decimal256_i128_division() { - // let env = Env::default(); - - // // a/b - // let left = Decimal256::percent(&env, 150); // 1.5 - // let right = I256::from_i128(&env, 3); - // assert_eq!(left.div_by_i256(&env, right), Decimal256::percent(&env, 50)); - - // // 0/a - // let left = Decimal256::zero(&env); - // let right = I256::from_i128(&env, 300); - // assert_eq!(left.div_by_i256(&env, right), Decimal256::zero(&env)); - // } - - // #[test] - // #[should_panic(expected = "attempt to divide by zero")] - // fn decimal256_i128_divide_by_zero() { - // let env = Env::default(); - // let left = Decimal256::percent(&env, 150); // 1.5 - // let right = I256::from_i128(&env, 0); - // let _result = left.div_by_i256(&env, right); - // } - - // #[test] - // fn decimal256_pow_works() { - // let env = Env::default(); - // assert_eq!( - // Decimal256::percent(&env, 200).pow(&env, 2), - // Decimal256::percent(&env, 400) - // ); - // assert_eq!( - // Decimal256::percent(&env, 100).pow(&env, 10), - // Decimal256::percent(&env, 100) - // ); - // } - - // #[test] - // #[should_panic] - // fn decimal256_pow_overflow_panics() { - // let env = Env::default(); - // _ = Decimal256(I256::from_parts(i128::MAX, 0)).pow(&env, 2u32); - // } - - // #[test] - // fn decimal256_abs_with_negative_number() { - // let env = Env::default(); - // let decimal = Decimal256::new(&env, -128); - - // assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); - // } - - // #[test] - // fn decimal256_abs_with_positive_number() { - // let env = Env::default(); - // let decimal = Decimal256::new(&env, 128); - - // assert_eq!(decimal.abs(&env), Decimal256::new(&env, 128)); - // } - - // #[test] - // fn decimal256_displayed_as_string() { - // let env = Env::default(); - // let decimal = Decimal256::percent(&env, 128); - - // // Convert expected string to Soroban SDK String - // let expected_msg = "1.28"; - // let expected_string = String::from_str(&env, expected_msg); - - // // Convert decimal to String and get its byte representation - // let result_string = decimal.to_string(&env); - // let result_string_len = result_string.len() as usize; - // let mut result_bytes = alloc::vec![0u8; result_string_len]; - // result_string.copy_into_slice(&mut result_bytes); - - // // Get byte representation of expected string - // let expected_string_len = expected_string.len() as usize; - // let mut expected_bytes = alloc::vec![0u8; expected_string_len]; - // expected_string.copy_into_slice(&mut expected_bytes); - - // assert_eq!(result_bytes, expected_bytes); - // } - - // #[test] - // fn decimal256_fmt_without_fractional_part() { - // let env = Env::default(); - // let value = Decimal256::from_atomics(&env, 100, 0); - // assert_eq!(format!("{}", value), "100"); - // } - - // #[test] - // fn decimal256_fmt_fractional_part() { - // let env = Env::default(); - // let value = Decimal256::from_atomics(&env, 123456789, 5); - // assert_eq!(format!("{}", value), "1234.56789"); - // } - - // #[test] - // fn decimal256_fmt_fractional_part_with_trailing_zeros() { - // // 12345.6 - // let env = Env::default(); - // let value = Decimal256::from_atomics(&env, 123456, 1); - // assert_eq!(format!("{}", value), "12345.6"); - // } - - // #[test] - // fn decimal256_fmt_only_fractional() { - // // 0.0789 - // let env = Env::default(); - // let value = Decimal256::from_atomics(&env, 789, 4); - // assert_eq!(format!("{}", value), "0.0789"); - // } + #[test] + #[should_panic(expected = "overflow has occured")] + fn decimal256_div_overflow_panics() { + let env = Env::default(); + let _value = Decimal256(U256::from_parts( + &env, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + )) + .div(&env, Decimal256::percent(&env, 10)); + } + + #[test] + #[should_panic(expected = "Division failed - denominator must not be zero")] + fn decimal256_div_by_zero_panics() { + let env = Env::default(); + let _value = Decimal256::one(&env).div(&env, Decimal256::zero(&env)); + } + + #[test] + fn decimal256_pow_works() { + let env = Env::default(); + assert_eq!( + Decimal256::percent(&env, 200).pow(&env, 2), + Decimal256::percent(&env, 400) + ); + assert_eq!( + Decimal256::percent(&env, 100).pow(&env, 10), + Decimal256::percent(&env, 100) + ); + } + + #[test] + #[should_panic(expected = "overflow has occured")] + fn decimal256_pow_overflow_panics() { + let env = Env::default(); + _ = Decimal256(U256::from_parts( + &env, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + )) + .pow(&env, 2u32); + } #[test] fn test_denominator() { From 2292d06d22bed83099cbecf1a2ffc95afbaff00b Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 18:47:39 +0300 Subject: [PATCH 22/25] lints --- packages/decimal/src/decimal256.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 6f87e5267..66c41fdec 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -5,7 +5,7 @@ use soroban_sdk::{Env, U256}; use core::{ cmp::{Ordering, PartialEq, PartialOrd}, - ops::{Add, Div, Mul, Sub}, + ops::{Add, Sub}, }; extern crate alloc; From 71fe9ee462e59ba892d4d6ec94ad8301b679c2d7 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov Date: Thu, 1 Aug 2024 18:54:51 +0300 Subject: [PATCH 23/25] fixes an import path --- packages/decimal/src/decimal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/decimal/src/decimal.rs b/packages/decimal/src/decimal.rs index 794db50da..ed89696bc 100644 --- a/packages/decimal/src/decimal.rs +++ b/packages/decimal/src/decimal.rs @@ -99,7 +99,7 @@ impl Decimal { /// ## Examples /// /// ``` - /// use decimal::Decimal; + /// use soroban_decimal::Decimal; /// // Value with whole and fractional part /// let a = Decimal::percent(123); /// assert_eq!(a.decimal_places(), 18); @@ -127,7 +127,7 @@ impl Decimal { /// ## Examples /// /// ``` - /// use decimal::Decimal; + /// use soroban_decimal::Decimal; /// use soroban_sdk::{String, Env}; /// /// let e = Env::default(); From 506bc1529226b823cc67f0244cd356aa5b5b9df3 Mon Sep 17 00:00:00 2001 From: Gangov Date: Mon, 5 Aug 2024 11:44:24 +0300 Subject: [PATCH 24/25] fixes from PR review --- packages/decimal/src/decimal256.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 66c41fdec..128cd16a8 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -87,18 +87,19 @@ impl Decimal256 { // TODO: Allow for `decimal_places` larger than 38 pub fn from_atomics(env: &Env, atomics: u128, decimal_places: i32) -> Self { - const TEN: u128 = 10; + let ten: U256 = U256::from_u128(env, 10u128); + let atomics = U256::from_u128(env, atomics); match decimal_places.cmp(&Self::DECIMAL_PLACES) { Ordering::Less => { let digits = Self::DECIMAL_PLACES - decimal_places; - let factor = TEN.pow(digits as u32); - Self(U256::from_u128(env, atomics * factor)) + let factor = ten.pow(digits as u32); + Self(atomics.mul(&factor)) } - Ordering::Equal => Self(U256::from_u128(env, atomics)), + Ordering::Equal => Self(atomics), Ordering::Greater => { let digits = decimal_places - Self::DECIMAL_PLACES; - let factor = TEN.pow(digits as u32); - Self(U256::from_u128(env, atomics / factor)) + let factor = ten.pow(digits as u32); + Self(atomics.div(&factor)) } } } @@ -215,7 +216,7 @@ impl Decimal256 { let result = self .numerator() .mul(&other.numerator()) - .div(&other.denominator(env)); + .div(&U256::from_u128(env, 1_000_000_000_000_000_000)); Decimal256(result) } @@ -816,7 +817,7 @@ mod tests { } #[test] - fn i128_decimal256_multiply() { + fn u128_decimal256_multiply() { let env = Env::default(); // a*b From 68adc323a57165c91d4394868586758761ba8331 Mon Sep 17 00:00:00 2001 From: Gangov Date: Mon, 5 Aug 2024 11:51:59 +0300 Subject: [PATCH 25/25] to satisfy linter --- packages/decimal/src/decimal256.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/decimal/src/decimal256.rs b/packages/decimal/src/decimal256.rs index 128cd16a8..c3c38b5d2 100644 --- a/packages/decimal/src/decimal256.rs +++ b/packages/decimal/src/decimal256.rs @@ -220,6 +220,7 @@ impl Decimal256 { Decimal256(result) } + #[allow(dead_code)] pub fn div(&self, env: &Env, rhs: Self) -> Self { match Decimal256::checked_from_ratio(env, self.numerator(), rhs.numerator()) { Ok(ratio) => ratio,