From ad72e62c1b1b044bf90f7fbeade9b1b29103c0a4 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 4 Aug 2023 12:41:47 +0200 Subject: [PATCH 01/26] Implement signed decimal --- packages/std/src/math/mod.rs | 2 + packages/std/src/math/signed_decimal.rs | 2265 +++++++++++++++++++++++ 2 files changed, 2267 insertions(+) create mode 100644 packages/std/src/math/signed_decimal.rs diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs index fdff7647f8..5defdb142b 100644 --- a/packages/std/src/math/mod.rs +++ b/packages/std/src/math/mod.rs @@ -8,6 +8,7 @@ mod int512; mod int64; mod isqrt; mod num_consts; +mod signed_decimal; mod uint128; mod uint256; mod uint512; @@ -21,6 +22,7 @@ pub use int256::Int256; pub use int512::Int512; pub use int64::Int64; pub use isqrt::Isqrt; +pub use signed_decimal::{SignedDecimal, SignedDecimalRangeExceeded}; pub use uint128::Uint128; pub use uint256::Uint256; pub use uint512::Uint512; diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs new file mode 100644 index 0000000000..ecce917b70 --- /dev/null +++ b/packages/std/src/math/signed_decimal.rs @@ -0,0 +1,2265 @@ +use core::cmp::Ordering; +use core::fmt::{self, Write}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign}; +use core::str::FromStr; +use forward_ref::{forward_ref_binop, forward_ref_op_assign}; +use schemars::JsonSchema; +use serde::{de, ser, Deserialize, Deserializer, Serialize}; +use thiserror::Error; + +use crate::errors::{ + CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + OverflowOperation, RoundUpOverflowError, StdError, +}; +use crate::{forward_ref_partial_eq, Decimal, Int256}; + +use super::Fraction; +use super::Int128; + +/// A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 +/// +/// The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18) +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] +pub struct SignedDecimal(#[schemars(with = "String")] Int128); + +forward_ref_partial_eq!(SignedDecimal, SignedDecimal); + +#[derive(Error, Debug, PartialEq, Eq)] +#[error("SignedDecimal range exceeded")] +pub struct SignedDecimalRangeExceeded; + +impl SignedDecimal { + const DECIMAL_FRACTIONAL: Int128 = Int128::new(1_000_000_000_000_000_000i128); // 1*10**18 + const DECIMAL_FRACTIONAL_SQUARED: Int128 = + Int128::new(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: u32 = 18; // This needs to be an even number. + /// The largest value that can be represented by this decimal type. + pub const MAX: Self = Self(Int128::MAX); + /// The smallest value that can be represented by this decimal type. + pub const MIN: Self = Self(Int128::MIN); + + /// Creates a Decimal(value) + /// This is equivalent to `Decimal::from_atomics(value, 18)` but usable in a const context. + pub const fn new(value: Int128) -> Self { + Self(value) + } + + /// Creates a Decimal(Int128(value)) + /// This is equivalent to `Decimal::from_atomics(value, 18)` but usable in a const context. + pub const fn raw(value: i128) -> Self { + Self(Int128::new(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(Int128::zero()) + } + + /// Convert x% into Decimal + pub fn percent(x: i64) -> Self { + Self(((x as i128) * 10_000_000_000_000_000).into()) + } + + /// Convert permille (x/1000) into Decimal + pub fn permille(x: i64) -> Self { + Self(((x as i128) * 1_000_000_000_000_000).into()) + } + + /// Convert basis points (x/10000) into Decimal + pub fn bps(x: i64) -> Self { + Self(((x as i128) * 100_000_000_000_000).into()) + } + + /// 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 123 and 2 will create + /// the decimal 1.23. + /// + /// Using 18 decimal places is slightly more efficient than other values + /// as no internal conversion is necessary. + /// + /// ## Examples + /// + /// ``` + /// # use cosmwasm_std::{Decimal, Int128}; + /// let a = Decimal::from_atomics(Int128::new(1234), 3).unwrap(); + /// assert_eq!(a.to_string(), "1.234"); + /// + /// let a = Decimal::from_atomics(1234i128, 0).unwrap(); + /// assert_eq!(a.to_string(), "1234"); + /// + /// let a = Decimal::from_atomics(1i64, 18).unwrap(); + /// assert_eq!(a.to_string(), "0.000000000000000001"); + /// ``` + pub fn from_atomics( + atomics: impl Into, + decimal_places: u32, + ) -> Result { + let atomics = atomics.into(); + const TEN: Int128 = Int128::new(10); + Ok(match decimal_places.cmp(&(Self::DECIMAL_PLACES)) { + Ordering::Less => { + let digits = (Self::DECIMAL_PLACES) - decimal_places; // No overflow because decimal_places < DECIMAL_PLACES + let factor = TEN.checked_pow(digits).unwrap(); // Safe because digits <= 17 + Self( + atomics + .checked_mul(factor) + .map_err(|_| SignedDecimalRangeExceeded)?, + ) + } + Ordering::Equal => Self(atomics), + Ordering::Greater => { + let digits = decimal_places - (Self::DECIMAL_PLACES); // No overflow because decimal_places > DECIMAL_PLACES + if let Ok(factor) = TEN.checked_pow(digits) { + Self(atomics.checked_div(factor).unwrap()) // Safe because factor cannot be zero + } else { + // In this case `factor` exceeds the Int128 range. + // Any Int128 `x` divided by `factor` with `factor > Int128::MAX` is 0. + // Try e.g. Python3: `(2**128-1) // 2**128` + Self(Int128::zero()) + } + } + }) + } + + /// Returns the ratio (numerator / denominator) as a Decimal + pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + match SignedDecimal::checked_from_ratio(numerator, denominator) { + Ok(value) => value, + Err(CheckedFromRatioError::DivideByZero) => { + panic!("Denominator must not be zero") + } + Err(CheckedFromRatioError::Overflow) => panic!("Multiplication overflow"), + } + } + + /// Returns the ratio (numerator / denominator) as a Decimal + pub fn checked_from_ratio( + numerator: impl Into, + denominator: impl Into, + ) -> Result { + let numerator: Int128 = numerator.into(); + let denominator: Int128 = denominator.into(); + match numerator.checked_multiply_ratio(Self::DECIMAL_FRACTIONAL, denominator) { + Ok(ratio) => { + // numerator * DECIMAL_FRACTIONAL / denominator + Ok(SignedDecimal(ratio)) + } + Err(CheckedMultiplyRatioError::Overflow) => Err(CheckedFromRatioError::Overflow), + Err(CheckedMultiplyRatioError::DivideByZero) => { + Err(CheckedFromRatioError::DivideByZero) + } + } + } + + #[must_use] + pub const fn is_zero(&self) -> bool { + self.0.is_zero() + } + + /// 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 cosmwasm_std::{Decimal, Int128}; + /// # use core::str::FromStr; + /// // Value with whole and fractional part + /// let a = Decimal::from_str("1.234").unwrap(); + /// assert_eq!(a.decimal_places(), 18); + /// assert_eq!(a.atomics(), Int128::new(1234000000000000000)); + /// + /// // Smallest possible value + /// let b = Decimal::from_str("0.000000000000000001").unwrap(); + /// assert_eq!(b.decimal_places(), 18); + /// assert_eq!(b.atomics(), Int128::new(1)); + /// ``` + #[must_use] + #[inline] + pub const fn atomics(&self) -> Int128 { + self.0 + } + + /// 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) -> u32 { + Self::DECIMAL_PLACES + } + + /// Rounds value down after decimal places. + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn floor(&self) -> Self { + Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) + } + + /// Rounds value up after decimal places. Panics on overflow. + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn ceil(&self) -> Self { + match self.checked_ceil() { + Ok(value) => value, + Err(_) => panic!("attempt to ceil with overflow"), + } + } + + /// Rounds value up after decimal places. Returns OverflowError on overflow. + pub fn checked_ceil(&self) -> Result { + let floor = self.floor(); + if floor == self { + Ok(floor) + } else { + floor + .checked_add(SignedDecimal::one()) + .map_err(|_| RoundUpOverflowError) + } + } + + pub fn checked_add(self, other: Self) -> Result { + self.0 + .checked_add(other.0) + .map(Self) + .map_err(|_| OverflowError::new(OverflowOperation::Add, self, other)) + } + + pub fn checked_sub(self, other: Self) -> Result { + self.0 + .checked_sub(other.0) + .map(Self) + .map_err(|_| OverflowError::new(OverflowOperation::Sub, self, other)) + } + + /// Multiplies one `Decimal` by another, returning an `OverflowError` if an overflow occurred. + pub fn checked_mul(self, other: Self) -> Result { + let result_as_int256 = + self.numerator().full_mul(other.numerator()) / Int256::from(Self::DECIMAL_FRACTIONAL); + result_as_int256 + .try_into() + .map(Self) + .map_err(|_| OverflowError { + operation: crate::OverflowOperation::Mul, + operand1: self.to_string(), + operand2: other.to_string(), + }) + } + + /// Raises a value to the power of `exp`, panics if an overflow occurred. + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn pow(self, exp: u32) -> Self { + match self.checked_pow(exp) { + Ok(value) => value, + Err(_) => panic!("Multiplication overflow"), + } + } + + /// Raises a value to the power of `exp`, returning an `OverflowError` if an overflow occurred. + pub fn checked_pow(self, exp: u32) -> Result { + // This uses the exponentiation by squaring algorithm: + // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method + + fn inner(mut x: SignedDecimal, mut n: u32) -> Result { + if n == 0 { + return Ok(SignedDecimal::one()); + } + + let mut y = SignedDecimal::one(); + + while n > 1 { + if n % 2 == 0 { + x = x.checked_mul(x)?; + n /= 2; + } else { + y = x.checked_mul(y)?; + x = x.checked_mul(x)?; + n = (n - 1) / 2; + } + } + + Ok(x * y) + } + + inner(self, exp).map_err(|_| OverflowError { + operation: crate::OverflowOperation::Pow, + operand1: self.to_string(), + operand2: exp.to_string(), + }) + } + + pub fn checked_div(self, other: Self) -> Result { + SignedDecimal::checked_from_ratio(self.numerator(), other.numerator()) + } + + pub fn checked_rem(self, other: Self) -> Result { + self.0 + .checked_rem(other.0) + .map(Self) + .map_err(|_| DivideByZeroError::new(self)) + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub const fn abs_diff(self, other: Self) -> Decimal { + Decimal::new(self.0.abs_diff(other.0)) + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_add(self, other: Self) -> Self { + match self.checked_add(other) { + Ok(value) => value, + Err(_) => Self::MAX, + } + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_sub(self, other: Self) -> Self { + match self.checked_sub(other) { + Ok(value) => value, + Err(_) => Self::zero(), + } + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_mul(self, other: Self) -> Self { + match self.checked_mul(other) { + Ok(value) => value, + Err(_) => Self::MAX, + } + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_pow(self, exp: u32) -> Self { + match self.checked_pow(exp) { + Ok(value) => value, + Err(_) => Self::MAX, + } + } + + /// Converts this decimal to an unsigned integer by truncating + /// the fractional part, e.g. 22.5 becomes 22. + /// + /// ## Examples + /// + /// ``` + /// use core::str::FromStr; + /// use cosmwasm_std::{Decimal, Int128}; + /// + /// let d = Decimal::from_str("12.345").unwrap(); + /// assert_eq!(d.to_int_floor(), Int128::new(12)); + /// + /// let d = Decimal::from_str("12.999").unwrap(); + /// assert_eq!(d.to_int_floor(), Int128::new(12)); + /// + /// let d = Decimal::from_str("75.0").unwrap(); + /// assert_eq!(d.to_int_floor(), Int128::new(75)); + /// ``` + #[must_use] + pub fn to_int_floor(self) -> Int128 { + self.0 / Self::DECIMAL_FRACTIONAL + } + + /// Converts this decimal to an unsigned integer by rounting up + /// to the next integer, e.g. 22.3 becomes 23. + /// + /// ## Examples + /// + /// ``` + /// use core::str::FromStr; + /// use cosmwasm_std::{Decimal, Int128}; + /// + /// let d = Decimal::from_str("12.345").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int128::new(13)); + /// + /// let d = Decimal::from_str("12.999").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int128::new(13)); + /// + /// let d = Decimal::from_str("75.0").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int128::new(75)); + /// ``` + #[must_use] + pub fn to_int_ceil(self) -> Int128 { + // Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q + // from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow. + let x = self.0; + let y = Self::DECIMAL_FRACTIONAL; + if x.is_zero() { + Int128::zero() + } else { + Int128::one() + ((x - Int128::one()) / y) + } + } +} + +impl Fraction for SignedDecimal { + #[inline] + fn numerator(&self) -> Int128 { + self.0 + } + + #[inline] + fn denominator(&self) -> Int128 { + Self::DECIMAL_FRACTIONAL + } + + /// Returns the multiplicative inverse `1/d` for decimal `d`. + /// + /// If `d` is zero, none is returned. + 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(SignedDecimal(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) + } + } +} + +impl FromStr for SignedDecimal { + type Err = StdError; + + /// Converts the decimal string to a Decimal + /// Possible inputs: "1.23", "1", "000012", "1.123000000" + /// Disallowed: "", ".23" + /// + /// This never performs any kind of rounding. + /// More than DECIMAL_PLACES fractional digits, even zeros, result in an error. + fn from_str(input: &str) -> Result { + let mut parts_iter = input.split('.'); + + let whole_part = parts_iter.next().unwrap(); // split always returns at least one element + let whole = whole_part + .parse::() + .map_err(|_| StdError::generic_err("Error parsing whole"))?; + let mut atomics = whole + .checked_mul(Self::DECIMAL_FRACTIONAL) + .map_err(|_| StdError::generic_err("Value too big"))?; + + if let Some(fractional_part) = parts_iter.next() { + let fractional = fractional_part + .parse::() + .map_err(|_| StdError::generic_err("Error parsing fractional"))?; + let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( + || { + StdError::generic_err(format!( + "Cannot parse more than {} fractional digits", + Self::DECIMAL_PLACES + )) + }, + )?; + debug_assert!(exp <= Self::DECIMAL_PLACES); + let fractional_factor = Int128::from(10i128.pow(exp)); + atomics = atomics + .checked_add( + // The inner multiplication can't overflow because + // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES + fractional.checked_mul(fractional_factor).unwrap(), + ) + .map_err(|_| StdError::generic_err("Value too big"))?; + } + + if parts_iter.next().is_some() { + return Err(StdError::generic_err("Unexpected number of dots")); + } + + Ok(SignedDecimal(atomics)) + } +} + +impl fmt::Display for SignedDecimal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let whole = (self.0) / Self::DECIMAL_FRACTIONAL; + let fractional = (self.0).checked_rem(Self::DECIMAL_FRACTIONAL).unwrap(); + + if fractional.is_zero() { + write!(f, "{whole}") + } else { + let fractional_string = format!( + "{:0>padding$}", + fractional, + padding = Self::DECIMAL_PLACES as usize + ); + f.write_str(&whole.to_string())?; + f.write_char('.')?; + f.write_str(fractional_string.trim_end_matches('0'))?; + Ok(()) + } + } +} + +impl fmt::Debug for SignedDecimal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Decimal({self})") + } +} + +impl Add for SignedDecimal { + type Output = Self; + + fn add(self, other: Self) -> Self { + SignedDecimal(self.0 + other.0) + } +} +forward_ref_binop!(impl Add, add for SignedDecimal, SignedDecimal); + +impl AddAssign for SignedDecimal { + fn add_assign(&mut self, rhs: SignedDecimal) { + *self = *self + rhs; + } +} +forward_ref_op_assign!(impl AddAssign, add_assign for SignedDecimal, SignedDecimal); + +impl Sub for SignedDecimal { + type Output = Self; + + fn sub(self, other: Self) -> Self { + SignedDecimal(self.0 - other.0) + } +} +forward_ref_binop!(impl Sub, sub for SignedDecimal, SignedDecimal); + +impl SubAssign for SignedDecimal { + fn sub_assign(&mut self, rhs: SignedDecimal) { + *self = *self - rhs; + } +} +forward_ref_op_assign!(impl SubAssign, sub_assign for SignedDecimal, SignedDecimal); + +impl Mul for SignedDecimal { + 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 result_as_int256 = + self.numerator().full_mul(other.numerator()) / Int256::from(Self::DECIMAL_FRACTIONAL); + match result_as_int256.try_into() { + Ok(result) => Self(result), + Err(_) => panic!("attempt to multiply with overflow"), + } + } +} +forward_ref_binop!(impl Mul, mul for SignedDecimal, SignedDecimal); + +impl MulAssign for SignedDecimal { + fn mul_assign(&mut self, rhs: SignedDecimal) { + *self = *self * rhs; + } +} +forward_ref_op_assign!(impl MulAssign, mul_assign for SignedDecimal, SignedDecimal); + +impl Div for SignedDecimal { + type Output = Self; + + fn div(self, other: Self) -> Self { + match SignedDecimal::checked_from_ratio(self.numerator(), other.numerator()) { + Ok(ratio) => ratio, + Err(CheckedFromRatioError::DivideByZero) => { + panic!("Division failed - denominator must not be zero") + } + Err(CheckedFromRatioError::Overflow) => { + panic!("Division failed - multiplication overflow") + } + } + } +} +forward_ref_binop!(impl Div, div for SignedDecimal, SignedDecimal); + +impl DivAssign for SignedDecimal { + fn div_assign(&mut self, rhs: SignedDecimal) { + *self = *self / rhs; + } +} +forward_ref_op_assign!(impl DivAssign, div_assign for SignedDecimal, SignedDecimal); + +impl Div for SignedDecimal { + type Output = Self; + + fn div(self, rhs: Int128) -> Self::Output { + SignedDecimal(self.0 / rhs) + } +} + +impl DivAssign for SignedDecimal { + fn div_assign(&mut self, rhs: Int128) { + self.0 /= rhs; + } +} + +impl Rem for SignedDecimal { + type Output = Self; + + /// # Panics + /// + /// This operation will panic if `rhs` is zero + #[inline] + fn rem(self, rhs: Self) -> Self { + Self(self.0.rem(rhs.0)) + } +} +forward_ref_binop!(impl Rem, rem for SignedDecimal, SignedDecimal); + +impl RemAssign for SignedDecimal { + fn rem_assign(&mut self, rhs: SignedDecimal) { + *self = *self % rhs; + } +} +forward_ref_op_assign!(impl RemAssign, rem_assign for SignedDecimal, SignedDecimal); + +impl core::iter::Sum for SignedDecimal +where + Self: Add, +{ + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +/// Serializes as a decimal string +impl Serialize for SignedDecimal { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +/// Deserializes as a base64 string +impl<'de> Deserialize<'de> for SignedDecimal { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(DecimalVisitor) + } +} + +struct DecimalVisitor; + +impl<'de> de::Visitor<'de> for DecimalVisitor { + type Value = SignedDecimal; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string-encoded decimal") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + match SignedDecimal::from_str(v) { + Ok(d) => Ok(d), + Err(e) => Err(E::custom(format!("Error parsing decimal '{v}': {e}"))), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{from_slice, to_vec}; + + fn dec(input: &str) -> SignedDecimal { + SignedDecimal::from_str(input).unwrap() + } + + #[test] + fn decimal_new() { + let expected = Int128::from(300i128); + assert_eq!(SignedDecimal::new(expected).0, expected); + } + + #[test] + fn decimal_raw() { + let value = 300i128; + assert_eq!(SignedDecimal::raw(value).0.i128(), value); + } + + #[test] + fn decimal_one() { + let value = SignedDecimal::one(); + assert_eq!(value.0, SignedDecimal::DECIMAL_FRACTIONAL); + } + + #[test] + fn decimal_zero() { + let value = SignedDecimal::zero(); + assert!(value.0.is_zero()); + } + + #[test] + fn decimal_percent() { + let value = SignedDecimal::percent(50); + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(2u8) + ); + } + + #[test] + fn decimal_permille() { + let value = SignedDecimal::permille(125); + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(8u8) + ); + } + + #[test] + fn decimal_bps() { + let value = SignedDecimal::bps(125); + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(80u8) + ); + } + + #[test] + fn decimal_from_atomics_works() { + let one = SignedDecimal::one(); + let two = one + one; + + assert_eq!(SignedDecimal::from_atomics(1i128, 0).unwrap(), one); + assert_eq!(SignedDecimal::from_atomics(10i128, 1).unwrap(), one); + assert_eq!(SignedDecimal::from_atomics(100i128, 2).unwrap(), one); + assert_eq!(SignedDecimal::from_atomics(1000i128, 3).unwrap(), one); + assert_eq!( + SignedDecimal::from_atomics(1000000000000000000i128, 18).unwrap(), + one + ); + assert_eq!( + SignedDecimal::from_atomics(10000000000000000000i128, 19).unwrap(), + one + ); + assert_eq!( + SignedDecimal::from_atomics(100000000000000000000i128, 20).unwrap(), + one + ); + + assert_eq!(SignedDecimal::from_atomics(2i128, 0).unwrap(), two); + assert_eq!(SignedDecimal::from_atomics(20i128, 1).unwrap(), two); + assert_eq!(SignedDecimal::from_atomics(200i128, 2).unwrap(), two); + assert_eq!(SignedDecimal::from_atomics(2000i128, 3).unwrap(), two); + assert_eq!( + SignedDecimal::from_atomics(2000000000000000000i128, 18).unwrap(), + two + ); + assert_eq!( + SignedDecimal::from_atomics(20000000000000000000i128, 19).unwrap(), + two + ); + assert_eq!( + SignedDecimal::from_atomics(200000000000000000000i128, 20).unwrap(), + two + ); + + // Cuts decimal digits (20 provided but only 18 can be stored) + assert_eq!( + SignedDecimal::from_atomics(4321i128, 20).unwrap(), + SignedDecimal::from_str("0.000000000000000043").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(6789i128, 20).unwrap(), + SignedDecimal::from_str("0.000000000000000067").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, 38).unwrap(), + SignedDecimal::from_str("3.402823669209384634").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, 39).unwrap(), + SignedDecimal::from_str("0.340282366920938463").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, 45).unwrap(), + SignedDecimal::from_str("0.000000340282366920").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, 51).unwrap(), + SignedDecimal::from_str("0.000000000000340282").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, 56).unwrap(), + SignedDecimal::from_str("0.000000000000000003").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, 57).unwrap(), + SignedDecimal::from_str("0.000000000000000000").unwrap() + ); + assert_eq!( + SignedDecimal::from_atomics(i128::MAX, u32::MAX).unwrap(), + SignedDecimal::from_str("0.000000000000000000").unwrap() + ); + + // Can be used with max value + let max = SignedDecimal::MAX; + assert_eq!( + SignedDecimal::from_atomics(max.atomics(), max.decimal_places()).unwrap(), + max + ); + + // Overflow is only possible with digits < 18 + let result = SignedDecimal::from_atomics(i128::MAX, 17); + assert_eq!(result.unwrap_err(), SignedDecimalRangeExceeded); + } + + #[test] + fn decimal_from_ratio_works() { + // 1.0 + assert_eq!( + SignedDecimal::from_ratio(1i128, 1i128), + SignedDecimal::one() + ); + assert_eq!( + SignedDecimal::from_ratio(53i128, 53i128), + SignedDecimal::one() + ); + assert_eq!( + SignedDecimal::from_ratio(125i128, 125i128), + SignedDecimal::one() + ); + + // 1.5 + assert_eq!( + SignedDecimal::from_ratio(3i128, 2i128), + SignedDecimal::percent(150) + ); + assert_eq!( + SignedDecimal::from_ratio(150i128, 100i128), + SignedDecimal::percent(150) + ); + assert_eq!( + SignedDecimal::from_ratio(333i128, 222i128), + SignedDecimal::percent(150) + ); + + // 0.125 + assert_eq!( + SignedDecimal::from_ratio(1i64, 8i64), + SignedDecimal::permille(125) + ); + assert_eq!( + SignedDecimal::from_ratio(125i64, 1000i64), + SignedDecimal::permille(125) + ); + + // 1/3 (result floored) + assert_eq!( + SignedDecimal::from_ratio(1i64, 3i64), + SignedDecimal(Int128::from(333_333_333_333_333_333i128)) + ); + + // 2/3 (result floored) + assert_eq!( + SignedDecimal::from_ratio(2i64, 3i64), + SignedDecimal(Int128::from(666_666_666_666_666_666i128)) + ); + + // large inputs + assert_eq!( + SignedDecimal::from_ratio(0i128, i128::MAX), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::from_ratio(i128::MAX, i128::MAX), + SignedDecimal::one() + ); + // 340282366920938463463 is the largest integer <= Decimal::MAX + assert_eq!( + SignedDecimal::from_ratio(340282366920938463463i128, 1i128), + SignedDecimal::from_str("340282366920938463463").unwrap() + ); + } + + #[test] + #[should_panic(expected = "Denominator must not be zero")] + fn decimal_from_ratio_panics_for_zero_denominator() { + SignedDecimal::from_ratio(1i128, 0i128); + } + + #[test] + #[should_panic(expected = "Multiplication overflow")] + fn decimal_from_ratio_panics_for_mul_overflow() { + SignedDecimal::from_ratio(i128::MAX, 1i128); + } + + #[test] + fn decimal_checked_from_ratio_does_not_panic() { + assert_eq!( + SignedDecimal::checked_from_ratio(1i128, 0i128), + Err(CheckedFromRatioError::DivideByZero) + ); + + assert_eq!( + SignedDecimal::checked_from_ratio(i128::MAX, 1i128), + Err(CheckedFromRatioError::Overflow) + ); + } + + #[test] + fn decimal_implements_fraction() { + let fraction = SignedDecimal::from_str("1234.567").unwrap(); + assert_eq!( + fraction.numerator(), + Int128::from(1_234_567_000_000_000_000_000i128) + ); + assert_eq!( + fraction.denominator(), + Int128::from(1_000_000_000_000_000_000i128) + ); + } + + #[test] + fn decimal_from_str_works() { + // Integers + assert_eq!( + SignedDecimal::from_str("0").unwrap(), + SignedDecimal::percent(0) + ); + assert_eq!( + SignedDecimal::from_str("1").unwrap(), + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::from_str("5").unwrap(), + SignedDecimal::percent(500) + ); + assert_eq!( + SignedDecimal::from_str("42").unwrap(), + SignedDecimal::percent(4200) + ); + assert_eq!( + SignedDecimal::from_str("000").unwrap(), + SignedDecimal::percent(0) + ); + assert_eq!( + SignedDecimal::from_str("001").unwrap(), + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::from_str("005").unwrap(), + SignedDecimal::percent(500) + ); + assert_eq!( + SignedDecimal::from_str("0042").unwrap(), + SignedDecimal::percent(4200) + ); + + // Decimals + assert_eq!( + SignedDecimal::from_str("1.0").unwrap(), + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::from_str("1.5").unwrap(), + SignedDecimal::percent(150) + ); + assert_eq!( + SignedDecimal::from_str("0.5").unwrap(), + SignedDecimal::percent(50) + ); + assert_eq!( + SignedDecimal::from_str("0.123").unwrap(), + SignedDecimal::permille(123) + ); + + assert_eq!( + SignedDecimal::from_str("40.00").unwrap(), + SignedDecimal::percent(4000) + ); + assert_eq!( + SignedDecimal::from_str("04.00").unwrap(), + SignedDecimal::percent(400) + ); + assert_eq!( + SignedDecimal::from_str("00.40").unwrap(), + SignedDecimal::percent(40) + ); + assert_eq!( + SignedDecimal::from_str("00.04").unwrap(), + SignedDecimal::percent(4) + ); + + // Can handle DECIMAL_PLACES fractional digits + assert_eq!( + SignedDecimal::from_str("7.123456789012345678").unwrap(), + SignedDecimal(Int128::from(7123456789012345678i128)) + ); + assert_eq!( + SignedDecimal::from_str("7.999999999999999999").unwrap(), + SignedDecimal(Int128::from(7999999999999999999i128)) + ); + + // Works for documented max value + assert_eq!( + SignedDecimal::from_str("340282366920938463463.374607431768211455").unwrap(), + SignedDecimal::MAX + ); + } + + #[test] + fn decimal_from_str_errors_for_broken_whole_part() { + match SignedDecimal::from_str("").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal::from_str(" ").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal::from_str("-1").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn decimal_from_str_errors_for_broken_fractinal_part() { + match SignedDecimal::from_str("1.").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal::from_str("1. ").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal::from_str("1.e").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal::from_str("1.2e3").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn decimal_from_str_errors_for_more_than_18_fractional_digits() { + match SignedDecimal::from_str("7.1234567890123456789").unwrap_err() { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "Cannot parse more than 18 fractional digits",) + } + e => panic!("Unexpected error: {e:?}"), + } + + // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. + match SignedDecimal::from_str("7.1230000000000000000").unwrap_err() { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "Cannot parse more than 18 fractional digits") + } + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn decimal_from_str_errors_for_invalid_number_of_dots() { + match SignedDecimal::from_str("1.2.3").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal::from_str("1.2.3.4").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn decimal_from_str_errors_for_more_than_max_value() { + // Integer + match SignedDecimal::from_str("340282366920938463464").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + + // Decimal + match SignedDecimal::from_str("340282366920938463464.0").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + match SignedDecimal::from_str("340282366920938463463.374607431768211456").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn decimal_atomics_works() { + let zero = SignedDecimal::zero(); + let one = SignedDecimal::one(); + let half = SignedDecimal::percent(50); + let two = SignedDecimal::percent(200); + let max = SignedDecimal::MAX; + + assert_eq!(zero.atomics(), Int128::new(0)); + assert_eq!(one.atomics(), Int128::new(1000000000000000000)); + assert_eq!(half.atomics(), Int128::new(500000000000000000)); + assert_eq!(two.atomics(), Int128::new(2000000000000000000)); + assert_eq!(max.atomics(), Int128::MAX); + } + + #[test] + fn decimal_decimal_places_works() { + let zero = SignedDecimal::zero(); + let one = SignedDecimal::one(); + let half = SignedDecimal::percent(50); + let two = SignedDecimal::percent(200); + let max = SignedDecimal::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_is_zero_works() { + assert!(SignedDecimal::zero().is_zero()); + assert!(SignedDecimal::percent(0).is_zero()); + assert!(SignedDecimal::permille(0).is_zero()); + + assert!(!SignedDecimal::one().is_zero()); + assert!(!SignedDecimal::percent(123).is_zero()); + assert!(!SignedDecimal::permille(1234).is_zero()); + } + + #[test] + fn decimal_inv_works() { + // d = 0 + assert_eq!(SignedDecimal::zero().inv(), None); + + // d == 1 + assert_eq!(SignedDecimal::one().inv(), Some(SignedDecimal::one())); + + // d > 1 exact + assert_eq!( + SignedDecimal::from_str("2").unwrap().inv(), + Some(SignedDecimal::from_str("0.5").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("20").unwrap().inv(), + Some(SignedDecimal::from_str("0.05").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("200").unwrap().inv(), + Some(SignedDecimal::from_str("0.005").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("2000").unwrap().inv(), + Some(SignedDecimal::from_str("0.0005").unwrap()) + ); + + // d > 1 rounded + assert_eq!( + SignedDecimal::from_str("3").unwrap().inv(), + Some(SignedDecimal::from_str("0.333333333333333333").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("6").unwrap().inv(), + Some(SignedDecimal::from_str("0.166666666666666666").unwrap()) + ); + + // d < 1 exact + assert_eq!( + SignedDecimal::from_str("0.5").unwrap().inv(), + Some(SignedDecimal::from_str("2").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("0.05").unwrap().inv(), + Some(SignedDecimal::from_str("20").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("0.005").unwrap().inv(), + Some(SignedDecimal::from_str("200").unwrap()) + ); + assert_eq!( + SignedDecimal::from_str("0.0005").unwrap().inv(), + Some(SignedDecimal::from_str("2000").unwrap()) + ); + } + + #[test] + #[allow(clippy::op_ref)] + fn decimal_add_works() { + let value = SignedDecimal::one() + SignedDecimal::percent(50); // 1.5 + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL * Int128::from(3u8) / Int128::from(2u8) + ); + + assert_eq!( + SignedDecimal::percent(5) + SignedDecimal::percent(4), + SignedDecimal::percent(9) + ); + assert_eq!( + SignedDecimal::percent(5) + SignedDecimal::zero(), + SignedDecimal::percent(5) + ); + assert_eq!( + SignedDecimal::zero() + SignedDecimal::zero(), + SignedDecimal::zero() + ); + + // works for refs + let a = SignedDecimal::percent(15); + let b = SignedDecimal::percent(25); + let expected = SignedDecimal::percent(40); + assert_eq!(a + b, expected); + assert_eq!(&a + b, expected); + assert_eq!(a + &b, expected); + assert_eq!(&a + &b, expected); + } + + #[test] + #[should_panic(expected = "attempt to add with overflow")] + fn decimal_add_overflow_panics() { + let _value = SignedDecimal::MAX + SignedDecimal::percent(50); + } + + #[test] + fn decimal_add_assign_works() { + let mut a = SignedDecimal::percent(30); + a += SignedDecimal::percent(20); + assert_eq!(a, SignedDecimal::percent(50)); + + // works for refs + let mut a = SignedDecimal::percent(15); + let b = SignedDecimal::percent(3); + let expected = SignedDecimal::percent(18); + a += &b; + assert_eq!(a, expected); + } + + #[test] + #[allow(clippy::op_ref)] + fn decimal_sub_works() { + let value = SignedDecimal::one() - SignedDecimal::percent(50); // 0.5 + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(2u8) + ); + + assert_eq!( + SignedDecimal::percent(9) - SignedDecimal::percent(4), + SignedDecimal::percent(5) + ); + assert_eq!( + SignedDecimal::percent(16) - SignedDecimal::zero(), + SignedDecimal::percent(16) + ); + assert_eq!( + SignedDecimal::percent(16) - SignedDecimal::percent(16), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::zero() - SignedDecimal::zero(), + SignedDecimal::zero() + ); + + // works for refs + let a = SignedDecimal::percent(13); + let b = SignedDecimal::percent(6); + let expected = SignedDecimal::percent(7); + assert_eq!(a - b, expected); + assert_eq!(&a - b, expected); + assert_eq!(a - &b, expected); + assert_eq!(&a - &b, expected); + } + + #[test] + #[should_panic(expected = "attempt to subtract with overflow")] + fn decimal_sub_overflow_panics() { + let _value = SignedDecimal::zero() - SignedDecimal::percent(50); + } + + #[test] + fn decimal_sub_assign_works() { + let mut a = SignedDecimal::percent(20); + a -= SignedDecimal::percent(2); + assert_eq!(a, SignedDecimal::percent(18)); + + // works for refs + let mut a = SignedDecimal::percent(33); + let b = SignedDecimal::percent(13); + let expected = SignedDecimal::percent(20); + a -= &b; + assert_eq!(a, expected); + } + + #[test] + #[allow(clippy::op_ref)] + fn decimal_implements_mul() { + let one = SignedDecimal::one(); + let two = one + one; + let half = SignedDecimal::percent(50); + + // 1*x and x*1 + assert_eq!(one * SignedDecimal::percent(0), SignedDecimal::percent(0)); + assert_eq!(one * SignedDecimal::percent(1), SignedDecimal::percent(1)); + assert_eq!(one * SignedDecimal::percent(10), SignedDecimal::percent(10)); + assert_eq!( + one * SignedDecimal::percent(100), + SignedDecimal::percent(100) + ); + assert_eq!( + one * SignedDecimal::percent(1000), + SignedDecimal::percent(1000) + ); + assert_eq!(one * SignedDecimal::MAX, SignedDecimal::MAX); + assert_eq!(SignedDecimal::percent(0) * one, SignedDecimal::percent(0)); + assert_eq!(SignedDecimal::percent(1) * one, SignedDecimal::percent(1)); + assert_eq!(SignedDecimal::percent(10) * one, SignedDecimal::percent(10)); + assert_eq!( + SignedDecimal::percent(100) * one, + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::percent(1000) * one, + SignedDecimal::percent(1000) + ); + assert_eq!(SignedDecimal::MAX * one, SignedDecimal::MAX); + + // double + assert_eq!(two * SignedDecimal::percent(0), SignedDecimal::percent(0)); + assert_eq!(two * SignedDecimal::percent(1), SignedDecimal::percent(2)); + assert_eq!(two * SignedDecimal::percent(10), SignedDecimal::percent(20)); + assert_eq!( + two * SignedDecimal::percent(100), + SignedDecimal::percent(200) + ); + assert_eq!( + two * SignedDecimal::percent(1000), + SignedDecimal::percent(2000) + ); + assert_eq!(SignedDecimal::percent(0) * two, SignedDecimal::percent(0)); + assert_eq!(SignedDecimal::percent(1) * two, SignedDecimal::percent(2)); + assert_eq!(SignedDecimal::percent(10) * two, SignedDecimal::percent(20)); + assert_eq!( + SignedDecimal::percent(100) * two, + SignedDecimal::percent(200) + ); + assert_eq!( + SignedDecimal::percent(1000) * two, + SignedDecimal::percent(2000) + ); + + // half + assert_eq!(half * SignedDecimal::percent(0), SignedDecimal::percent(0)); + assert_eq!(half * SignedDecimal::percent(1), SignedDecimal::permille(5)); + assert_eq!(half * SignedDecimal::percent(10), SignedDecimal::percent(5)); + assert_eq!( + half * SignedDecimal::percent(100), + SignedDecimal::percent(50) + ); + assert_eq!( + half * SignedDecimal::percent(1000), + SignedDecimal::percent(500) + ); + assert_eq!(SignedDecimal::percent(0) * half, SignedDecimal::percent(0)); + assert_eq!(SignedDecimal::percent(1) * half, SignedDecimal::permille(5)); + assert_eq!(SignedDecimal::percent(10) * half, SignedDecimal::percent(5)); + assert_eq!( + SignedDecimal::percent(100) * half, + SignedDecimal::percent(50) + ); + assert_eq!( + SignedDecimal::percent(1000) * half, + SignedDecimal::percent(500) + ); + + // Move left + let a = dec("123.127726548762582"); + assert_eq!(a * dec("1"), dec("123.127726548762582")); + assert_eq!(a * dec("10"), dec("1231.27726548762582")); + assert_eq!(a * dec("100"), dec("12312.7726548762582")); + assert_eq!(a * dec("1000"), dec("123127.726548762582")); + assert_eq!(a * dec("1000000"), dec("123127726.548762582")); + assert_eq!(a * dec("1000000000"), dec("123127726548.762582")); + assert_eq!(a * dec("1000000000000"), dec("123127726548762.582")); + assert_eq!(a * dec("1000000000000000"), dec("123127726548762582")); + assert_eq!(a * dec("1000000000000000000"), dec("123127726548762582000")); + assert_eq!(dec("1") * a, dec("123.127726548762582")); + assert_eq!(dec("10") * a, dec("1231.27726548762582")); + assert_eq!(dec("100") * a, dec("12312.7726548762582")); + assert_eq!(dec("1000") * a, dec("123127.726548762582")); + assert_eq!(dec("1000000") * a, dec("123127726.548762582")); + assert_eq!(dec("1000000000") * a, dec("123127726548.762582")); + assert_eq!(dec("1000000000000") * a, dec("123127726548762.582")); + assert_eq!(dec("1000000000000000") * a, dec("123127726548762582")); + assert_eq!(dec("1000000000000000000") * a, dec("123127726548762582000")); + + // Move right + let max = SignedDecimal::MAX; + assert_eq!( + max * dec("1.0"), + dec("340282366920938463463.374607431768211455") + ); + assert_eq!( + max * dec("0.1"), + dec("34028236692093846346.337460743176821145") + ); + assert_eq!( + max * dec("0.01"), + dec("3402823669209384634.633746074317682114") + ); + assert_eq!( + max * dec("0.001"), + dec("340282366920938463.463374607431768211") + ); + assert_eq!( + max * dec("0.000001"), + dec("340282366920938.463463374607431768") + ); + assert_eq!( + max * dec("0.000000001"), + dec("340282366920.938463463374607431") + ); + assert_eq!( + max * dec("0.000000000001"), + dec("340282366.920938463463374607") + ); + assert_eq!( + max * dec("0.000000000000001"), + dec("340282.366920938463463374") + ); + assert_eq!( + max * dec("0.000000000000000001"), + dec("340.282366920938463463") + ); + + // works for refs + let a = SignedDecimal::percent(20); + let b = SignedDecimal::percent(30); + let expected = SignedDecimal::percent(6); + assert_eq!(a * b, expected); + assert_eq!(&a * b, expected); + assert_eq!(a * &b, expected); + assert_eq!(&a * &b, expected); + } + + #[test] + fn decimal_mul_assign_works() { + let mut a = SignedDecimal::percent(15); + a *= SignedDecimal::percent(60); + assert_eq!(a, SignedDecimal::percent(9)); + + // works for refs + let mut a = SignedDecimal::percent(50); + let b = SignedDecimal::percent(20); + a *= &b; + assert_eq!(a, SignedDecimal::percent(10)); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn decimal_mul_overflow_panics() { + let _value = SignedDecimal::MAX * SignedDecimal::percent(101); + } + + #[test] + fn decimal_checked_mul() { + let test_data = [ + (SignedDecimal::zero(), SignedDecimal::zero()), + (SignedDecimal::zero(), SignedDecimal::one()), + (SignedDecimal::one(), SignedDecimal::zero()), + (SignedDecimal::percent(10), SignedDecimal::zero()), + (SignedDecimal::percent(10), SignedDecimal::percent(5)), + (SignedDecimal::MAX, SignedDecimal::one()), + ( + SignedDecimal::MAX / Int128::new(2), + SignedDecimal::percent(200), + ), + (SignedDecimal::permille(6), SignedDecimal::permille(13)), + ]; + + // The regular core::ops::Mul is our source of truth for these tests. + for (x, y) in test_data.into_iter() { + assert_eq!(x * y, x.checked_mul(y).unwrap()); + } + } + + #[test] + fn decimal_checked_mul_overflow() { + assert_eq!( + SignedDecimal::MAX.checked_mul(SignedDecimal::percent(200)), + Err(OverflowError { + operation: crate::OverflowOperation::Mul, + operand1: SignedDecimal::MAX.to_string(), + operand2: SignedDecimal::percent(200).to_string(), + }) + ); + } + + #[test] + #[allow(clippy::op_ref)] + fn decimal_implements_div() { + let one = SignedDecimal::one(); + let two = one + one; + let half = SignedDecimal::percent(50); + + // 1/x and x/1 + assert_eq!( + one / SignedDecimal::percent(1), + SignedDecimal::percent(10_000) + ); + assert_eq!( + one / SignedDecimal::percent(10), + SignedDecimal::percent(1_000) + ); + assert_eq!( + one / SignedDecimal::percent(100), + SignedDecimal::percent(100) + ); + assert_eq!( + one / SignedDecimal::percent(1000), + SignedDecimal::percent(10) + ); + assert_eq!(SignedDecimal::percent(0) / one, SignedDecimal::percent(0)); + assert_eq!(SignedDecimal::percent(1) / one, SignedDecimal::percent(1)); + assert_eq!(SignedDecimal::percent(10) / one, SignedDecimal::percent(10)); + assert_eq!( + SignedDecimal::percent(100) / one, + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::percent(1000) / one, + SignedDecimal::percent(1000) + ); + + // double + assert_eq!( + two / SignedDecimal::percent(1), + SignedDecimal::percent(20_000) + ); + assert_eq!( + two / SignedDecimal::percent(10), + SignedDecimal::percent(2_000) + ); + assert_eq!( + two / SignedDecimal::percent(100), + SignedDecimal::percent(200) + ); + assert_eq!( + two / SignedDecimal::percent(1000), + SignedDecimal::percent(20) + ); + assert_eq!(SignedDecimal::percent(0) / two, SignedDecimal::percent(0)); + assert_eq!(SignedDecimal::percent(1) / two, dec("0.005")); + assert_eq!(SignedDecimal::percent(10) / two, SignedDecimal::percent(5)); + assert_eq!( + SignedDecimal::percent(100) / two, + SignedDecimal::percent(50) + ); + assert_eq!( + SignedDecimal::percent(1000) / two, + SignedDecimal::percent(500) + ); + + // half + assert_eq!( + half / SignedDecimal::percent(1), + SignedDecimal::percent(5_000) + ); + assert_eq!( + half / SignedDecimal::percent(10), + SignedDecimal::percent(500) + ); + assert_eq!( + half / SignedDecimal::percent(100), + SignedDecimal::percent(50) + ); + assert_eq!( + half / SignedDecimal::percent(1000), + SignedDecimal::percent(5) + ); + assert_eq!(SignedDecimal::percent(0) / half, SignedDecimal::percent(0)); + assert_eq!(SignedDecimal::percent(1) / half, SignedDecimal::percent(2)); + assert_eq!( + SignedDecimal::percent(10) / half, + SignedDecimal::percent(20) + ); + assert_eq!( + SignedDecimal::percent(100) / half, + SignedDecimal::percent(200) + ); + assert_eq!( + SignedDecimal::percent(1000) / half, + SignedDecimal::percent(2000) + ); + + // Move right + let a = dec("123127726548762582"); + assert_eq!(a / dec("1"), dec("123127726548762582")); + assert_eq!(a / dec("10"), dec("12312772654876258.2")); + assert_eq!(a / dec("100"), dec("1231277265487625.82")); + assert_eq!(a / dec("1000"), dec("123127726548762.582")); + assert_eq!(a / dec("1000000"), dec("123127726548.762582")); + assert_eq!(a / dec("1000000000"), dec("123127726.548762582")); + assert_eq!(a / dec("1000000000000"), dec("123127.726548762582")); + assert_eq!(a / dec("1000000000000000"), dec("123.127726548762582")); + assert_eq!(a / dec("1000000000000000000"), dec("0.123127726548762582")); + assert_eq!(dec("1") / a, dec("0.000000000000000008")); + assert_eq!(dec("10") / a, dec("0.000000000000000081")); + assert_eq!(dec("100") / a, dec("0.000000000000000812")); + assert_eq!(dec("1000") / a, dec("0.000000000000008121")); + assert_eq!(dec("1000000") / a, dec("0.000000000008121647")); + assert_eq!(dec("1000000000") / a, dec("0.000000008121647560")); + assert_eq!(dec("1000000000000") / a, dec("0.000008121647560868")); + assert_eq!(dec("1000000000000000") / a, dec("0.008121647560868164")); + assert_eq!(dec("1000000000000000000") / a, dec("8.121647560868164773")); + + // Move left + let a = dec("0.123127726548762582"); + assert_eq!(a / dec("1.0"), dec("0.123127726548762582")); + assert_eq!(a / dec("0.1"), dec("1.23127726548762582")); + assert_eq!(a / dec("0.01"), dec("12.3127726548762582")); + assert_eq!(a / dec("0.001"), dec("123.127726548762582")); + assert_eq!(a / dec("0.000001"), dec("123127.726548762582")); + assert_eq!(a / dec("0.000000001"), dec("123127726.548762582")); + assert_eq!(a / dec("0.000000000001"), dec("123127726548.762582")); + assert_eq!(a / dec("0.000000000000001"), dec("123127726548762.582")); + assert_eq!(a / dec("0.000000000000000001"), dec("123127726548762582")); + + assert_eq!( + SignedDecimal::percent(15) / SignedDecimal::percent(60), + SignedDecimal::percent(25) + ); + + // works for refs + let a = SignedDecimal::percent(100); + let b = SignedDecimal::percent(20); + let expected = SignedDecimal::percent(500); + assert_eq!(a / b, expected); + assert_eq!(&a / b, expected); + assert_eq!(a / &b, expected); + assert_eq!(&a / &b, expected); + } + + #[test] + fn decimal_div_assign_works() { + let mut a = SignedDecimal::percent(15); + a /= SignedDecimal::percent(20); + assert_eq!(a, SignedDecimal::percent(75)); + + // works for refs + let mut a = SignedDecimal::percent(50); + let b = SignedDecimal::percent(20); + a /= &b; + assert_eq!(a, SignedDecimal::percent(250)); + } + + #[test] + #[should_panic(expected = "Division failed - multiplication overflow")] + fn decimal_div_overflow_panics() { + let _value = SignedDecimal::MAX / SignedDecimal::percent(10); + } + + #[test] + #[should_panic(expected = "Division failed - denominator must not be zero")] + fn decimal_div_by_zero_panics() { + let _value = SignedDecimal::one() / SignedDecimal::zero(); + } + + #[test] + fn decimal_int128_division() { + // a/b + let left = SignedDecimal::percent(150); // 1.5 + let right = Int128::new(3); + assert_eq!(left / right, SignedDecimal::percent(50)); + + // 0/a + let left = SignedDecimal::zero(); + let right = Int128::new(300); + assert_eq!(left / right, SignedDecimal::zero()); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn decimal_int128_divide_by_zero() { + let left = SignedDecimal::percent(150); // 1.5 + let right = Int128::new(0); + let _result = left / right; + } + + #[test] + fn decimal_int128_div_assign() { + // a/b + let mut dec = SignedDecimal::percent(150); // 1.5 + dec /= Int128::new(3); + assert_eq!(dec, SignedDecimal::percent(50)); + + // 0/a + let mut dec = SignedDecimal::zero(); + dec /= Int128::new(300); + assert_eq!(dec, SignedDecimal::zero()); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn decimal_int128_div_assign_by_zero() { + // a/0 + let mut dec = SignedDecimal::percent(50); + dec /= Int128::new(0); + } + + #[test] + fn decimal_checked_pow() { + for exp in 0..10 { + assert_eq!( + SignedDecimal::one().checked_pow(exp).unwrap(), + SignedDecimal::one() + ); + } + + // This case is mathematically undefined but we ensure consistency with Rust stdandard types + // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494 + assert_eq!( + SignedDecimal::zero().checked_pow(0).unwrap(), + SignedDecimal::one() + ); + + for exp in 1..10 { + assert_eq!( + SignedDecimal::zero().checked_pow(exp).unwrap(), + SignedDecimal::zero() + ); + } + + for num in &[ + SignedDecimal::percent(50), + SignedDecimal::percent(99), + SignedDecimal::percent(200), + ] { + assert_eq!(num.checked_pow(0).unwrap(), SignedDecimal::one()) + } + + assert_eq!( + SignedDecimal::percent(20).checked_pow(2).unwrap(), + SignedDecimal::percent(4) + ); + + assert_eq!( + SignedDecimal::percent(20).checked_pow(3).unwrap(), + SignedDecimal::permille(8) + ); + + assert_eq!( + SignedDecimal::percent(200).checked_pow(4).unwrap(), + SignedDecimal::percent(1600) + ); + + assert_eq!( + SignedDecimal::percent(200).checked_pow(4).unwrap(), + SignedDecimal::percent(1600) + ); + + assert_eq!( + SignedDecimal::percent(700).checked_pow(5).unwrap(), + SignedDecimal::percent(1680700) + ); + + assert_eq!( + SignedDecimal::percent(700).checked_pow(8).unwrap(), + SignedDecimal::percent(576480100) + ); + + assert_eq!( + SignedDecimal::percent(700).checked_pow(10).unwrap(), + SignedDecimal::percent(28247524900) + ); + + assert_eq!( + SignedDecimal::percent(120).checked_pow(123).unwrap(), + SignedDecimal(5486473221892422150877397607i128.into()) + ); + + assert_eq!( + SignedDecimal::percent(10).checked_pow(2).unwrap(), + SignedDecimal(10000000000000000i128.into()) + ); + + assert_eq!( + SignedDecimal::percent(10).checked_pow(18).unwrap(), + SignedDecimal(1i128.into()) + ); + } + + #[test] + fn decimal_checked_pow_overflow() { + assert_eq!( + SignedDecimal::MAX.checked_pow(2), + Err(OverflowError { + operation: crate::OverflowOperation::Pow, + operand1: SignedDecimal::MAX.to_string(), + operand2: "2".to_string(), + }) + ); + } + + #[test] + fn decimal_to_string() { + // Integers + assert_eq!(SignedDecimal::zero().to_string(), "0"); + assert_eq!(SignedDecimal::one().to_string(), "1"); + assert_eq!(SignedDecimal::percent(500).to_string(), "5"); + + // Decimals + assert_eq!(SignedDecimal::percent(125).to_string(), "1.25"); + assert_eq!(SignedDecimal::percent(42638).to_string(), "426.38"); + assert_eq!(SignedDecimal::percent(3).to_string(), "0.03"); + assert_eq!(SignedDecimal::permille(987).to_string(), "0.987"); + + assert_eq!( + SignedDecimal(Int128::from(1i128)).to_string(), + "0.000000000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(10i128)).to_string(), + "0.00000000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(100i128)).to_string(), + "0.0000000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(1000i128)).to_string(), + "0.000000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(10000i128)).to_string(), + "0.00000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(100000i128)).to_string(), + "0.0000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(1000000i128)).to_string(), + "0.000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(10000000i128)).to_string(), + "0.00000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(100000000i128)).to_string(), + "0.0000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(1000000000i128)).to_string(), + "0.000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(10000000000i128)).to_string(), + "0.00000001" + ); + assert_eq!( + SignedDecimal(Int128::from(100000000000i128)).to_string(), + "0.0000001" + ); + assert_eq!( + SignedDecimal(Int128::from(10000000000000i128)).to_string(), + "0.00001" + ); + assert_eq!( + SignedDecimal(Int128::from(100000000000000i128)).to_string(), + "0.0001" + ); + assert_eq!( + SignedDecimal(Int128::from(1000000000000000i128)).to_string(), + "0.001" + ); + assert_eq!( + SignedDecimal(Int128::from(10000000000000000i128)).to_string(), + "0.01" + ); + assert_eq!( + SignedDecimal(Int128::from(100000000000000000i128)).to_string(), + "0.1" + ); + } + + #[test] + fn decimal_iter_sum() { + let items = vec![ + SignedDecimal::zero(), + SignedDecimal(Int128::from(2i128)), + SignedDecimal(Int128::from(2i128)), + ]; + assert_eq!( + items.iter().sum::(), + SignedDecimal(Int128::from(4i128)) + ); + assert_eq!( + items.into_iter().sum::(), + SignedDecimal(Int128::from(4i128)) + ); + + let empty: Vec = vec![]; + assert_eq!(SignedDecimal::zero(), empty.iter().sum::()); + } + + #[test] + fn decimal_serialize() { + assert_eq!(to_vec(&SignedDecimal::zero()).unwrap(), br#""0""#); + assert_eq!(to_vec(&SignedDecimal::one()).unwrap(), br#""1""#); + assert_eq!(to_vec(&SignedDecimal::percent(8)).unwrap(), br#""0.08""#); + assert_eq!(to_vec(&SignedDecimal::percent(87)).unwrap(), br#""0.87""#); + assert_eq!(to_vec(&SignedDecimal::percent(876)).unwrap(), br#""8.76""#); + assert_eq!( + to_vec(&SignedDecimal::percent(8765)).unwrap(), + br#""87.65""# + ); + } + + #[test] + fn decimal_deserialize() { + assert_eq!( + from_slice::(br#""0""#).unwrap(), + SignedDecimal::zero() + ); + assert_eq!( + from_slice::(br#""1""#).unwrap(), + SignedDecimal::one() + ); + assert_eq!( + from_slice::(br#""000""#).unwrap(), + SignedDecimal::zero() + ); + assert_eq!( + from_slice::(br#""001""#).unwrap(), + SignedDecimal::one() + ); + + assert_eq!( + from_slice::(br#""0.08""#).unwrap(), + SignedDecimal::percent(8) + ); + assert_eq!( + from_slice::(br#""0.87""#).unwrap(), + SignedDecimal::percent(87) + ); + assert_eq!( + from_slice::(br#""8.76""#).unwrap(), + SignedDecimal::percent(876) + ); + assert_eq!( + from_slice::(br#""87.65""#).unwrap(), + SignedDecimal::percent(8765) + ); + } + + #[test] + fn decimal_abs_diff_works() { + let a = SignedDecimal::percent(285); + let b = SignedDecimal::percent(200); + let expected = Decimal::percent(85); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); + + let a = SignedDecimal::percent(-200); + let b = SignedDecimal::percent(200); + let expected = Decimal::percent(400); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); + } + + #[test] + #[allow(clippy::op_ref)] + fn decimal_rem_works() { + // 4.02 % 1.11 = 0.69 + assert_eq!( + SignedDecimal::percent(402) % SignedDecimal::percent(111), + SignedDecimal::percent(69) + ); + + // 15.25 % 4 = 3.25 + assert_eq!( + SignedDecimal::percent(1525) % SignedDecimal::percent(400), + SignedDecimal::percent(325) + ); + + let a = SignedDecimal::percent(318); + let b = SignedDecimal::percent(317); + let expected = SignedDecimal::percent(1); + assert_eq!(a % b, expected); + assert_eq!(a % &b, expected); + assert_eq!(&a % b, expected); + assert_eq!(&a % &b, expected); + } + + #[test] + fn decimal_rem_assign_works() { + let mut a = SignedDecimal::percent(17673); + a %= SignedDecimal::percent(2362); + assert_eq!(a, SignedDecimal::percent(1139)); // 176.73 % 23.62 = 11.39 + + let mut a = SignedDecimal::percent(4262); + let b = SignedDecimal::percent(1270); + a %= &b; + assert_eq!(a, SignedDecimal::percent(452)); // 42.62 % 12.7 = 4.52 + } + + #[test] + #[should_panic(expected = "divisor of zero")] + fn decimal_rem_panics_for_zero() { + let _ = SignedDecimal::percent(777) % SignedDecimal::zero(); + } + + #[test] + fn decimal_checked_methods() { + // checked add + assert_eq!( + SignedDecimal::percent(402) + .checked_add(SignedDecimal::percent(111)) + .unwrap(), + SignedDecimal::percent(513) + ); + assert!(matches!( + SignedDecimal::MAX.checked_add(SignedDecimal::percent(1)), + Err(OverflowError { .. }) + )); + + // checked sub + assert_eq!( + SignedDecimal::percent(1111) + .checked_sub(SignedDecimal::percent(111)) + .unwrap(), + SignedDecimal::percent(1000) + ); + assert!(matches!( + SignedDecimal::zero().checked_sub(SignedDecimal::percent(1)), + Err(OverflowError { .. }) + )); + + // checked div + assert_eq!( + SignedDecimal::percent(30) + .checked_div(SignedDecimal::percent(200)) + .unwrap(), + SignedDecimal::percent(15) + ); + assert_eq!( + SignedDecimal::percent(88) + .checked_div(SignedDecimal::percent(20)) + .unwrap(), + SignedDecimal::percent(440) + ); + assert!(matches!( + SignedDecimal::MAX.checked_div(SignedDecimal::zero()), + Err(CheckedFromRatioError::DivideByZero {}) + )); + assert!(matches!( + SignedDecimal::MAX.checked_div(SignedDecimal::percent(1)), + Err(CheckedFromRatioError::Overflow {}) + )); + + // checked rem + assert_eq!( + SignedDecimal::percent(402) + .checked_rem(SignedDecimal::percent(111)) + .unwrap(), + SignedDecimal::percent(69) + ); + assert_eq!( + SignedDecimal::percent(1525) + .checked_rem(SignedDecimal::percent(400)) + .unwrap(), + SignedDecimal::percent(325) + ); + assert!(matches!( + SignedDecimal::MAX.checked_rem(SignedDecimal::zero()), + Err(DivideByZeroError { .. }) + )); + } + + #[test] + fn decimal_pow_works() { + assert_eq!( + SignedDecimal::percent(200).pow(2), + SignedDecimal::percent(400) + ); + assert_eq!( + SignedDecimal::percent(200).pow(10), + SignedDecimal::percent(102400) + ); + } + + #[test] + #[should_panic] + fn decimal_pow_overflow_panics() { + _ = SignedDecimal::MAX.pow(2u32); + } + + #[test] + fn decimal_saturating_works() { + assert_eq!( + SignedDecimal::percent(200).saturating_add(SignedDecimal::percent(200)), + SignedDecimal::percent(400) + ); + assert_eq!( + SignedDecimal::MAX.saturating_add(SignedDecimal::percent(200)), + SignedDecimal::MAX + ); + assert_eq!( + SignedDecimal::percent(200).saturating_sub(SignedDecimal::percent(100)), + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::zero().saturating_sub(SignedDecimal::percent(200)), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::percent(200).saturating_mul(SignedDecimal::percent(50)), + SignedDecimal::percent(100) + ); + assert_eq!( + SignedDecimal::MAX.saturating_mul(SignedDecimal::percent(200)), + SignedDecimal::MAX + ); + assert_eq!( + SignedDecimal::percent(400).saturating_pow(2u32), + SignedDecimal::percent(1600) + ); + assert_eq!(SignedDecimal::MAX.saturating_pow(2u32), SignedDecimal::MAX); + } + + #[test] + fn decimal_rounding() { + assert_eq!(SignedDecimal::one().floor(), SignedDecimal::one()); + assert_eq!(SignedDecimal::percent(150).floor(), SignedDecimal::one()); + assert_eq!(SignedDecimal::percent(199).floor(), SignedDecimal::one()); + assert_eq!( + SignedDecimal::percent(200).floor(), + SignedDecimal::percent(200) + ); + assert_eq!(SignedDecimal::percent(99).floor(), SignedDecimal::zero()); + + assert_eq!(SignedDecimal::one().ceil(), SignedDecimal::one()); + assert_eq!( + SignedDecimal::percent(150).ceil(), + SignedDecimal::percent(200) + ); + assert_eq!( + SignedDecimal::percent(199).ceil(), + SignedDecimal::percent(200) + ); + assert_eq!(SignedDecimal::percent(99).ceil(), SignedDecimal::one()); + assert_eq!( + SignedDecimal(Int128::from(1i128)).ceil(), + SignedDecimal::one() + ); + } + + #[test] + #[should_panic(expected = "attempt to ceil with overflow")] + fn decimal_ceil_panics() { + let _ = SignedDecimal::MAX.ceil(); + } + + #[test] + fn decimal_checked_ceil() { + assert_eq!( + SignedDecimal::percent(199).checked_ceil(), + Ok(SignedDecimal::percent(200)) + ); + assert!(matches!( + SignedDecimal::MAX.checked_ceil(), + Err(RoundUpOverflowError { .. }) + )); + } + + #[test] + fn decimal_to_int_floor_works() { + let d = SignedDecimal::from_str("12.000000000000000001").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(12)); + let d = SignedDecimal::from_str("12.345").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(12)); + let d = SignedDecimal::from_str("12.999").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(12)); + let d = SignedDecimal::from_str("0.98451384").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(0)); + + let d = SignedDecimal::from_str("75.0").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(75)); + let d = SignedDecimal::from_str("0.0").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(0)); + + let d = SignedDecimal::MAX; + assert_eq!(d.to_int_floor(), Int128::new(340282366920938463463)); + } + + #[test] + fn decimal_to_int_ceil_works() { + let d = SignedDecimal::from_str("12.000000000000000001").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(13)); + let d = SignedDecimal::from_str("12.345").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(13)); + let d = SignedDecimal::from_str("12.999").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(13)); + + let d = SignedDecimal::from_str("75.0").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(75)); + let d = SignedDecimal::from_str("0.0").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(0)); + + let d = SignedDecimal::MAX; + assert_eq!(d.to_int_ceil(), Int128::new(340282366920938463464)); + } + + #[test] + fn decimal_partial_eq() { + let test_cases = [ + ("1", "1", true), + ("0.5", "0.5", true), + ("0.5", "0.51", false), + ("0", "0.00000", true), + ] + .into_iter() + .map(|(lhs, rhs, expected)| (dec(lhs), dec(rhs), expected)); + + #[allow(clippy::op_ref)] + for (lhs, rhs, expected) in test_cases { + assert_eq!(lhs == rhs, expected); + assert_eq!(&lhs == rhs, expected); + assert_eq!(lhs == &rhs, expected); + assert_eq!(&lhs == &rhs, expected); + } + } + + #[test] + fn decimal_implements_debug() { + let decimal = SignedDecimal::from_str("123.45").unwrap(); + assert_eq!(format!("{decimal:?}"), "Decimal(123.45)"); + + let test_cases = ["5", "5.01", "42", "0", "2"]; + for s in test_cases { + let decimal = SignedDecimal::from_str(s).unwrap(); + let expected = format!("Decimal({s})"); + assert_eq!(format!("{decimal:?}"), expected); + } + } +} From 4f4c88f2b97ca3a1f8dc77936bf0fa40be05b285 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 4 Aug 2023 18:14:16 +0200 Subject: [PATCH 02/26] Fix some tests --- packages/std/src/lib.rs | 3 +- packages/std/src/math/signed_decimal.rs | 266 +++++++++++++----------- 2 files changed, 148 insertions(+), 121 deletions(-) diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index ac4b9d696c..6c6454ffe2 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -61,7 +61,8 @@ pub use crate::ibc::{ pub use crate::iterator::{Order, Record}; pub use crate::math::{ Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Int128, Int256, - Int512, Int64, Isqrt, Uint128, Uint256, Uint512, Uint64, + Int512, Int64, Isqrt, SignedDecimal, SignedDecimalRangeExceeded, Uint128, Uint256, Uint512, + Uint64, }; pub use crate::metadata::{DenomMetadata, DenomUnit}; pub use crate::never::Never; diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index ecce917b70..b2eadbef6b 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -1,6 +1,8 @@ use core::cmp::Ordering; use core::fmt::{self, Write}; -use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign}; +use core::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; use core::str::FromStr; use forward_ref::{forward_ref_binop, forward_ref_op_assign}; use schemars::JsonSchema; @@ -16,9 +18,10 @@ use crate::{forward_ref_partial_eq, Decimal, Int256}; use super::Fraction; use super::Int128; -/// A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 +/// A signed fixed-point decimal value with 18 fractional digits, i.e. SignedDecimal(1_000_000_000_000_000_000) == 1.0 /// -/// The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18) +/// The greatest possible value that can be represented is 170141183460469231731.687303715884105727 (which is (2^127 - 1) / 10^18) +/// and the smallest is -170141183460469231731.687303715884105728 (which is -2^127 / 10^18). #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] pub struct SignedDecimal(#[schemars(with = "String")] Int128); @@ -36,53 +39,59 @@ impl SignedDecimal { /// The number of decimal places. Since decimal types are fixed-point rather than /// floating-point, this is a constant. pub const DECIMAL_PLACES: u32 = 18; // This needs to be an even number. - /// The largest value that can be represented by this decimal type. + /// The largest value that can be represented by this signed decimal type. pub const MAX: Self = Self(Int128::MAX); - /// The smallest value that can be represented by this decimal type. + /// The smallest value that can be represented by this signed decimal type. pub const MIN: Self = Self(Int128::MIN); - /// Creates a Decimal(value) + /// Creates a SignedDecimal(value) /// This is equivalent to `Decimal::from_atomics(value, 18)` but usable in a const context. pub const fn new(value: Int128) -> Self { Self(value) } - /// Creates a Decimal(Int128(value)) - /// This is equivalent to `Decimal::from_atomics(value, 18)` but usable in a const context. + /// Creates a SignedDecimal(Int128(value)) + /// This is equivalent to `SignedDecimal::from_atomics(value, 18)` but usable in a const context. pub const fn raw(value: i128) -> Self { Self(Int128::new(value)) } - /// Create a 1.0 Decimal + /// Create a 1.0 SignedDecimal #[inline] pub const fn one() -> Self { Self(Self::DECIMAL_FRACTIONAL) } - /// Create a 0.0 Decimal + /// Create a -1.0 SignedDecimal + #[inline] + pub const fn negative_one() -> Self { + Self(Int128::new(-Self::DECIMAL_FRACTIONAL.i128())) + } + + /// Create a 0.0 SignedDecimal #[inline] pub const fn zero() -> Self { Self(Int128::zero()) } - /// Convert x% into Decimal + /// Convert x% into SignedDecimal pub fn percent(x: i64) -> Self { Self(((x as i128) * 10_000_000_000_000_000).into()) } - /// Convert permille (x/1000) into Decimal + /// Convert permille (x/1000) into SignedDecimal pub fn permille(x: i64) -> Self { Self(((x as i128) * 1_000_000_000_000_000).into()) } - /// Convert basis points (x/10000) into Decimal + /// Convert basis points (x/10000) into SignedDecimal pub fn bps(x: i64) -> Self { Self(((x as i128) * 100_000_000_000_000).into()) } - /// Creates a decimal from a number of atomic units and the number + /// Creates a signed 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 123 and 2 will create + /// a signed decimal with 18 decimal places. So the input 123 and 2 will create /// the decimal 1.23. /// /// Using 18 decimal places is slightly more efficient than other values @@ -91,15 +100,18 @@ impl SignedDecimal { /// ## Examples /// /// ``` - /// # use cosmwasm_std::{Decimal, Int128}; - /// let a = Decimal::from_atomics(Int128::new(1234), 3).unwrap(); + /// # use cosmwasm_std::{SignedDecimal, Int128}; + /// let a = SignedDecimal::from_atomics(Int128::new(1234), 3).unwrap(); /// assert_eq!(a.to_string(), "1.234"); /// - /// let a = Decimal::from_atomics(1234i128, 0).unwrap(); + /// let a = SignedDecimal::from_atomics(1234i128, 0).unwrap(); /// assert_eq!(a.to_string(), "1234"); /// - /// let a = Decimal::from_atomics(1i64, 18).unwrap(); + /// let a = SignedDecimal::from_atomics(1i64, 18).unwrap(); /// assert_eq!(a.to_string(), "0.000000000000000001"); + /// + /// let a = SignedDecimal::from_atomics(-1i64, 18).unwrap(); + /// assert_eq!(a.to_string(), "-0.000000000000000001"); /// ``` pub fn from_atomics( atomics: impl Into, @@ -132,7 +144,7 @@ impl SignedDecimal { }) } - /// Returns the ratio (numerator / denominator) as a Decimal + /// Returns the ratio (numerator / denominator) as a SignedDecimal pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { match SignedDecimal::checked_from_ratio(numerator, denominator) { Ok(value) => value, @@ -143,7 +155,7 @@ impl SignedDecimal { } } - /// Returns the ratio (numerator / denominator) as a Decimal + /// Returns the ratio (numerator / denominator) as a SignedDecimal pub fn checked_from_ratio( numerator: impl Into, denominator: impl Into, @@ -173,15 +185,15 @@ impl SignedDecimal { /// ## Examples /// /// ``` - /// # use cosmwasm_std::{Decimal, Int128}; + /// # use cosmwasm_std::{SignedDecimal, Int128}; /// # use core::str::FromStr; /// // Value with whole and fractional part - /// let a = Decimal::from_str("1.234").unwrap(); + /// let a = SignedDecimal::from_str("1.234").unwrap(); /// assert_eq!(a.decimal_places(), 18); /// assert_eq!(a.atomics(), Int128::new(1234000000000000000)); /// /// // Smallest possible value - /// let b = Decimal::from_str("0.000000000000000001").unwrap(); + /// let b = SignedDecimal::from_str("0.000000000000000001").unwrap(); /// assert_eq!(b.decimal_places(), 18); /// assert_eq!(b.atomics(), Int128::new(1)); /// ``` @@ -194,7 +206,7 @@ impl SignedDecimal { /// 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()`]. + /// See also [`SignedDecimal::atomics()`]. #[must_use] #[inline] pub const fn decimal_places(&self) -> u32 { @@ -355,13 +367,13 @@ impl SignedDecimal { /// use core::str::FromStr; /// use cosmwasm_std::{Decimal, Int128}; /// - /// let d = Decimal::from_str("12.345").unwrap(); + /// let d = SignedDecimal::from_str("12.345").unwrap(); /// assert_eq!(d.to_int_floor(), Int128::new(12)); /// - /// let d = Decimal::from_str("12.999").unwrap(); + /// let d = SignedDecimal::from_str("12.999").unwrap(); /// assert_eq!(d.to_int_floor(), Int128::new(12)); /// - /// let d = Decimal::from_str("75.0").unwrap(); + /// let d = SignedDecimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_floor(), Int128::new(75)); /// ``` #[must_use] @@ -378,13 +390,13 @@ impl SignedDecimal { /// use core::str::FromStr; /// use cosmwasm_std::{Decimal, Int128}; /// - /// let d = Decimal::from_str("12.345").unwrap(); + /// let d = SignedDecimal::from_str("12.345").unwrap(); /// assert_eq!(d.to_int_ceil(), Int128::new(13)); /// - /// let d = Decimal::from_str("12.999").unwrap(); + /// let d = SignedDecimal::from_str("12.999").unwrap(); /// assert_eq!(d.to_int_ceil(), Int128::new(13)); /// - /// let d = Decimal::from_str("75.0").unwrap(); + /// let d = SignedDecimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_ceil(), Int128::new(75)); /// ``` #[must_use] @@ -427,10 +439,18 @@ impl Fraction for SignedDecimal { } } +impl Neg for SignedDecimal { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + impl FromStr for SignedDecimal { type Err = StdError; - /// Converts the decimal string to a Decimal + /// Converts the decimal string to a SignedDecimal /// Possible inputs: "1.23", "1", "000012", "1.123000000" /// Disallowed: "", ".23" /// @@ -542,7 +562,7 @@ impl Mul for SignedDecimal { #[allow(clippy::suspicious_arithmetic_impl)] fn mul(self, other: Self) -> Self { - // Decimals are fractions. We can multiply two decimals a and b + // SignedDecimals 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() @@ -647,13 +667,13 @@ impl<'de> Deserialize<'de> for SignedDecimal { where D: Deserializer<'de>, { - deserializer.deserialize_str(DecimalVisitor) + deserializer.deserialize_str(SignedDecimalVisitor) } } -struct DecimalVisitor; +struct SignedDecimalVisitor; -impl<'de> de::Visitor<'de> for DecimalVisitor { +impl<'de> de::Visitor<'de> for SignedDecimalVisitor { type Value = SignedDecimal; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -681,31 +701,31 @@ mod tests { } #[test] - fn decimal_new() { + fn signed_decimal_new() { let expected = Int128::from(300i128); assert_eq!(SignedDecimal::new(expected).0, expected); } #[test] - fn decimal_raw() { + fn signed_decimal_raw() { let value = 300i128; assert_eq!(SignedDecimal::raw(value).0.i128(), value); } #[test] - fn decimal_one() { + fn signed_decimal_one() { let value = SignedDecimal::one(); assert_eq!(value.0, SignedDecimal::DECIMAL_FRACTIONAL); } #[test] - fn decimal_zero() { + fn signed_decimal_zero() { let value = SignedDecimal::zero(); assert!(value.0.is_zero()); } #[test] - fn decimal_percent() { + fn signed_decimal_percent() { let value = SignedDecimal::percent(50); assert_eq!( value.0, @@ -714,7 +734,7 @@ mod tests { } #[test] - fn decimal_permille() { + fn signed_decimal_permille() { let value = SignedDecimal::permille(125); assert_eq!( value.0, @@ -723,7 +743,7 @@ mod tests { } #[test] - fn decimal_bps() { + fn signed_decimal_bps() { let value = SignedDecimal::bps(125); assert_eq!( value.0, @@ -732,7 +752,7 @@ mod tests { } #[test] - fn decimal_from_atomics_works() { + fn signed_decimal_from_atomics_works() { let one = SignedDecimal::one(); let two = one + one; @@ -821,7 +841,7 @@ mod tests { } #[test] - fn decimal_from_ratio_works() { + fn signed_decimal_from_ratio_works() { // 1.0 assert_eq!( SignedDecimal::from_ratio(1i128, 1i128), @@ -881,27 +901,27 @@ mod tests { SignedDecimal::from_ratio(i128::MAX, i128::MAX), SignedDecimal::one() ); - // 340282366920938463463 is the largest integer <= Decimal::MAX + // 170141183460469231731 is the largest integer <= SignedDecimal::MAX assert_eq!( - SignedDecimal::from_ratio(340282366920938463463i128, 1i128), - SignedDecimal::from_str("340282366920938463463").unwrap() + SignedDecimal::from_ratio(170141183460469231731i128, 1i128), + SignedDecimal::from_str("170141183460469231731").unwrap() ); } #[test] #[should_panic(expected = "Denominator must not be zero")] - fn decimal_from_ratio_panics_for_zero_denominator() { + fn signed_decimal_from_ratio_panics_for_zero_denominator() { SignedDecimal::from_ratio(1i128, 0i128); } #[test] #[should_panic(expected = "Multiplication overflow")] - fn decimal_from_ratio_panics_for_mul_overflow() { + fn signed_decimal_from_ratio_panics_for_mul_overflow() { SignedDecimal::from_ratio(i128::MAX, 1i128); } #[test] - fn decimal_checked_from_ratio_does_not_panic() { + fn signed_decimal_checked_from_ratio_does_not_panic() { assert_eq!( SignedDecimal::checked_from_ratio(1i128, 0i128), Err(CheckedFromRatioError::DivideByZero) @@ -914,7 +934,7 @@ mod tests { } #[test] - fn decimal_implements_fraction() { + fn signed_decimal_implements_fraction() { let fraction = SignedDecimal::from_str("1234.567").unwrap(); assert_eq!( fraction.numerator(), @@ -927,7 +947,7 @@ mod tests { } #[test] - fn decimal_from_str_works() { + fn signed_decimal_from_str_works() { // Integers assert_eq!( SignedDecimal::from_str("0").unwrap(), @@ -962,7 +982,7 @@ mod tests { SignedDecimal::percent(4200) ); - // Decimals + // SignedDecimals assert_eq!( SignedDecimal::from_str("1.0").unwrap(), SignedDecimal::percent(100) @@ -1009,13 +1029,13 @@ mod tests { // Works for documented max value assert_eq!( - SignedDecimal::from_str("340282366920938463463.374607431768211455").unwrap(), + SignedDecimal::from_str("170141183460469231731.687303715884105727").unwrap(), SignedDecimal::MAX ); } #[test] - fn decimal_from_str_errors_for_broken_whole_part() { + fn signed_decimal_from_str_errors_for_broken_whole_part() { match SignedDecimal::from_str("").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), e => panic!("Unexpected error: {e:?}"), @@ -1033,7 +1053,7 @@ mod tests { } #[test] - fn decimal_from_str_errors_for_broken_fractinal_part() { + fn signed_decimal_from_str_errors_for_broken_fractinal_part() { match SignedDecimal::from_str("1.").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), @@ -1056,7 +1076,7 @@ mod tests { } #[test] - fn decimal_from_str_errors_for_more_than_18_fractional_digits() { + fn signed_decimal_from_str_errors_for_more_than_18_fractional_digits() { match SignedDecimal::from_str("7.1234567890123456789").unwrap_err() { StdError::GenericErr { msg, .. } => { assert_eq!(msg, "Cannot parse more than 18 fractional digits",) @@ -1074,7 +1094,7 @@ mod tests { } #[test] - fn decimal_from_str_errors_for_invalid_number_of_dots() { + fn signed_decimal_from_str_errors_for_invalid_number_of_dots() { match SignedDecimal::from_str("1.2.3").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), e => panic!("Unexpected error: {e:?}"), @@ -1087,26 +1107,26 @@ mod tests { } #[test] - fn decimal_from_str_errors_for_more_than_max_value() { + fn signed_decimal_from_str_errors_for_more_than_max_value() { // Integer - match SignedDecimal::from_str("340282366920938463464").unwrap_err() { + match SignedDecimal::from_str("170141183460469231732").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } - // Decimal - match SignedDecimal::from_str("340282366920938463464.0").unwrap_err() { + // SignedDecimal + match SignedDecimal::from_str("170141183460469231732.0").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } - match SignedDecimal::from_str("340282366920938463463.374607431768211456").unwrap_err() { + match SignedDecimal::from_str("170141183460469231731.687303715884105728").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } } #[test] - fn decimal_atomics_works() { + fn signed_decimal_atomics_works() { let zero = SignedDecimal::zero(); let one = SignedDecimal::one(); let half = SignedDecimal::percent(50); @@ -1121,7 +1141,7 @@ mod tests { } #[test] - fn decimal_decimal_places_works() { + fn signed_decimal_decimal_places_works() { let zero = SignedDecimal::zero(); let one = SignedDecimal::one(); let half = SignedDecimal::percent(50); @@ -1136,7 +1156,7 @@ mod tests { } #[test] - fn decimal_is_zero_works() { + fn signed_decimal_is_zero_works() { assert!(SignedDecimal::zero().is_zero()); assert!(SignedDecimal::percent(0).is_zero()); assert!(SignedDecimal::permille(0).is_zero()); @@ -1147,7 +1167,7 @@ mod tests { } #[test] - fn decimal_inv_works() { + fn signed_decimal_inv_works() { // d = 0 assert_eq!(SignedDecimal::zero().inv(), None); @@ -1203,7 +1223,7 @@ mod tests { #[test] #[allow(clippy::op_ref)] - fn decimal_add_works() { + fn signed_decimal_add_works() { let value = SignedDecimal::one() + SignedDecimal::percent(50); // 1.5 assert_eq!( value.0, @@ -1235,12 +1255,12 @@ mod tests { #[test] #[should_panic(expected = "attempt to add with overflow")] - fn decimal_add_overflow_panics() { + fn signed_decimal_add_overflow_panics() { let _value = SignedDecimal::MAX + SignedDecimal::percent(50); } #[test] - fn decimal_add_assign_works() { + fn signed_decimal_add_assign_works() { let mut a = SignedDecimal::percent(30); a += SignedDecimal::percent(20); assert_eq!(a, SignedDecimal::percent(50)); @@ -1255,7 +1275,7 @@ mod tests { #[test] #[allow(clippy::op_ref)] - fn decimal_sub_works() { + fn signed_decimal_sub_works() { let value = SignedDecimal::one() - SignedDecimal::percent(50); // 0.5 assert_eq!( value.0, @@ -1291,12 +1311,12 @@ mod tests { #[test] #[should_panic(expected = "attempt to subtract with overflow")] - fn decimal_sub_overflow_panics() { + fn signed_decimal_sub_overflow_panics() { let _value = SignedDecimal::zero() - SignedDecimal::percent(50); } #[test] - fn decimal_sub_assign_works() { + fn signed_decimal_sub_assign_works() { let mut a = SignedDecimal::percent(20); a -= SignedDecimal::percent(2); assert_eq!(a, SignedDecimal::percent(18)); @@ -1311,7 +1331,7 @@ mod tests { #[test] #[allow(clippy::op_ref)] - fn decimal_implements_mul() { + fn signed_decimal_implements_mul() { let one = SignedDecimal::one(); let two = one + one; let half = SignedDecimal::percent(50); @@ -1415,39 +1435,39 @@ mod tests { let max = SignedDecimal::MAX; assert_eq!( max * dec("1.0"), - dec("340282366920938463463.374607431768211455") + dec("170141183460469231731.687303715884105727") ); assert_eq!( max * dec("0.1"), - dec("34028236692093846346.337460743176821145") + dec("17014118346046923173.168730371588410572") ); assert_eq!( max * dec("0.01"), - dec("3402823669209384634.633746074317682114") + dec("1701411834604692317.316873037158841057") ); assert_eq!( max * dec("0.001"), - dec("340282366920938463.463374607431768211") + dec("170141183460469231.731687303715884105") ); assert_eq!( max * dec("0.000001"), - dec("340282366920938.463463374607431768") + dec("170141183460469.231731687303715884") ); assert_eq!( max * dec("0.000000001"), - dec("340282366920.938463463374607431") + dec("170141183460.469231731687303715") ); assert_eq!( max * dec("0.000000000001"), - dec("340282366.920938463463374607") + dec("170141183.460469231731687303") ); assert_eq!( max * dec("0.000000000000001"), - dec("340282.366920938463463374") + dec("170141.183460469231731687") ); assert_eq!( max * dec("0.000000000000000001"), - dec("340.282366920938463463") + dec("170.141183460469231731") ); // works for refs @@ -1461,7 +1481,7 @@ mod tests { } #[test] - fn decimal_mul_assign_works() { + fn signed_decimal_mul_assign_works() { let mut a = SignedDecimal::percent(15); a *= SignedDecimal::percent(60); assert_eq!(a, SignedDecimal::percent(9)); @@ -1475,12 +1495,12 @@ mod tests { #[test] #[should_panic(expected = "attempt to multiply with overflow")] - fn decimal_mul_overflow_panics() { + fn signed_decimal_mul_overflow_panics() { let _value = SignedDecimal::MAX * SignedDecimal::percent(101); } #[test] - fn decimal_checked_mul() { + fn signed_decimal_checked_mul() { let test_data = [ (SignedDecimal::zero(), SignedDecimal::zero()), (SignedDecimal::zero(), SignedDecimal::one()), @@ -1502,7 +1522,7 @@ mod tests { } #[test] - fn decimal_checked_mul_overflow() { + fn signed_decimal_checked_mul_overflow() { assert_eq!( SignedDecimal::MAX.checked_mul(SignedDecimal::percent(200)), Err(OverflowError { @@ -1515,7 +1535,7 @@ mod tests { #[test] #[allow(clippy::op_ref)] - fn decimal_implements_div() { + fn signed_decimal_implements_div() { let one = SignedDecimal::one(); let two = one + one; let half = SignedDecimal::percent(50); @@ -1659,7 +1679,7 @@ mod tests { } #[test] - fn decimal_div_assign_works() { + fn signed_decimal_div_assign_works() { let mut a = SignedDecimal::percent(15); a /= SignedDecimal::percent(20); assert_eq!(a, SignedDecimal::percent(75)); @@ -1673,18 +1693,18 @@ mod tests { #[test] #[should_panic(expected = "Division failed - multiplication overflow")] - fn decimal_div_overflow_panics() { + fn signed_decimal_div_overflow_panics() { let _value = SignedDecimal::MAX / SignedDecimal::percent(10); } #[test] #[should_panic(expected = "Division failed - denominator must not be zero")] - fn decimal_div_by_zero_panics() { + fn signed_decimal_div_by_zero_panics() { let _value = SignedDecimal::one() / SignedDecimal::zero(); } #[test] - fn decimal_int128_division() { + fn signed_decimal_int128_division() { // a/b let left = SignedDecimal::percent(150); // 1.5 let right = Int128::new(3); @@ -1698,14 +1718,14 @@ mod tests { #[test] #[should_panic(expected = "attempt to divide by zero")] - fn decimal_int128_divide_by_zero() { + fn signed_decimal_int128_divide_by_zero() { let left = SignedDecimal::percent(150); // 1.5 let right = Int128::new(0); let _result = left / right; } #[test] - fn decimal_int128_div_assign() { + fn signed_decimal_int128_div_assign() { // a/b let mut dec = SignedDecimal::percent(150); // 1.5 dec /= Int128::new(3); @@ -1719,14 +1739,14 @@ mod tests { #[test] #[should_panic(expected = "attempt to divide by zero")] - fn decimal_int128_div_assign_by_zero() { + fn signed_decimal_int128_div_assign_by_zero() { // a/0 let mut dec = SignedDecimal::percent(50); dec /= Int128::new(0); } #[test] - fn decimal_checked_pow() { + fn signed_decimal_checked_pow() { for exp in 0..10 { assert_eq!( SignedDecimal::one().checked_pow(exp).unwrap(), @@ -1808,7 +1828,7 @@ mod tests { } #[test] - fn decimal_checked_pow_overflow() { + fn signed_decimal_checked_pow_overflow() { assert_eq!( SignedDecimal::MAX.checked_pow(2), Err(OverflowError { @@ -1820,13 +1840,13 @@ mod tests { } #[test] - fn decimal_to_string() { + fn signed_decimal_to_string() { // Integers assert_eq!(SignedDecimal::zero().to_string(), "0"); assert_eq!(SignedDecimal::one().to_string(), "1"); assert_eq!(SignedDecimal::percent(500).to_string(), "5"); - // Decimals + // SignedDecimals assert_eq!(SignedDecimal::percent(125).to_string(), "1.25"); assert_eq!(SignedDecimal::percent(42638).to_string(), "426.38"); assert_eq!(SignedDecimal::percent(3).to_string(), "0.03"); @@ -1903,7 +1923,7 @@ mod tests { } #[test] - fn decimal_iter_sum() { + fn signed_decimal_iter_sum() { let items = vec![ SignedDecimal::zero(), SignedDecimal(Int128::from(2i128)), @@ -1923,7 +1943,7 @@ mod tests { } #[test] - fn decimal_serialize() { + fn signed_decimal_serialize() { assert_eq!(to_vec(&SignedDecimal::zero()).unwrap(), br#""0""#); assert_eq!(to_vec(&SignedDecimal::one()).unwrap(), br#""1""#); assert_eq!(to_vec(&SignedDecimal::percent(8)).unwrap(), br#""0.08""#); @@ -1936,7 +1956,7 @@ mod tests { } #[test] - fn decimal_deserialize() { + fn signed_decimal_deserialize() { assert_eq!( from_slice::(br#""0""#).unwrap(), SignedDecimal::zero() @@ -1973,7 +1993,7 @@ mod tests { } #[test] - fn decimal_abs_diff_works() { + fn signed_decimal_abs_diff_works() { let a = SignedDecimal::percent(285); let b = SignedDecimal::percent(200); let expected = Decimal::percent(85); @@ -1989,7 +2009,7 @@ mod tests { #[test] #[allow(clippy::op_ref)] - fn decimal_rem_works() { + fn signed_decimal_rem_works() { // 4.02 % 1.11 = 0.69 assert_eq!( SignedDecimal::percent(402) % SignedDecimal::percent(111), @@ -2012,7 +2032,7 @@ mod tests { } #[test] - fn decimal_rem_assign_works() { + fn signed_decimal_rem_assign_works() { let mut a = SignedDecimal::percent(17673); a %= SignedDecimal::percent(2362); assert_eq!(a, SignedDecimal::percent(1139)); // 176.73 % 23.62 = 11.39 @@ -2025,12 +2045,12 @@ mod tests { #[test] #[should_panic(expected = "divisor of zero")] - fn decimal_rem_panics_for_zero() { + fn signed_decimal_rem_panics_for_zero() { let _ = SignedDecimal::percent(777) % SignedDecimal::zero(); } #[test] - fn decimal_checked_methods() { + fn signed_decimal_checked_methods() { // checked add assert_eq!( SignedDecimal::percent(402) @@ -2097,7 +2117,7 @@ mod tests { } #[test] - fn decimal_pow_works() { + fn signed_decimal_pow_works() { assert_eq!( SignedDecimal::percent(200).pow(2), SignedDecimal::percent(400) @@ -2110,12 +2130,12 @@ mod tests { #[test] #[should_panic] - fn decimal_pow_overflow_panics() { + fn signed_decimal_pow_overflow_panics() { _ = SignedDecimal::MAX.pow(2u32); } #[test] - fn decimal_saturating_works() { + fn signed_decimal_saturating_works() { assert_eq!( SignedDecimal::percent(200).saturating_add(SignedDecimal::percent(200)), SignedDecimal::percent(400) @@ -2148,7 +2168,7 @@ mod tests { } #[test] - fn decimal_rounding() { + fn signed_decimal_rounding() { assert_eq!(SignedDecimal::one().floor(), SignedDecimal::one()); assert_eq!(SignedDecimal::percent(150).floor(), SignedDecimal::one()); assert_eq!(SignedDecimal::percent(199).floor(), SignedDecimal::one()); @@ -2176,12 +2196,12 @@ mod tests { #[test] #[should_panic(expected = "attempt to ceil with overflow")] - fn decimal_ceil_panics() { + fn signed_decimal_ceil_panics() { let _ = SignedDecimal::MAX.ceil(); } #[test] - fn decimal_checked_ceil() { + fn signed_decimal_checked_ceil() { assert_eq!( SignedDecimal::percent(199).checked_ceil(), Ok(SignedDecimal::percent(200)) @@ -2193,7 +2213,7 @@ mod tests { } #[test] - fn decimal_to_int_floor_works() { + fn signed_decimal_to_int_floor_works() { let d = SignedDecimal::from_str("12.000000000000000001").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(12)); let d = SignedDecimal::from_str("12.345").unwrap(); @@ -2209,11 +2229,11 @@ mod tests { assert_eq!(d.to_int_floor(), Int128::new(0)); let d = SignedDecimal::MAX; - assert_eq!(d.to_int_floor(), Int128::new(340282366920938463463)); + assert_eq!(d.to_int_floor(), Int128::new(170141183460469231731)); } #[test] - fn decimal_to_int_ceil_works() { + fn signed_decimal_to_int_ceil_works() { let d = SignedDecimal::from_str("12.000000000000000001").unwrap(); assert_eq!(d.to_int_ceil(), Int128::new(13)); let d = SignedDecimal::from_str("12.345").unwrap(); @@ -2227,11 +2247,17 @@ mod tests { assert_eq!(d.to_int_ceil(), Int128::new(0)); let d = SignedDecimal::MAX; - assert_eq!(d.to_int_ceil(), Int128::new(340282366920938463464)); + assert_eq!(d.to_int_ceil(), Int128::new(170141183460469231732)); + } + + #[test] + fn signed_decimal_neg_works() { + assert_eq!(-SignedDecimal::percent(50), SignedDecimal::percent(-50)); + assert_eq!(-SignedDecimal::one(), SignedDecimal::negative_one()); } #[test] - fn decimal_partial_eq() { + fn signed_decimal_partial_eq() { let test_cases = [ ("1", "1", true), ("0.5", "0.5", true), @@ -2251,7 +2277,7 @@ mod tests { } #[test] - fn decimal_implements_debug() { + fn signed_decimal_implements_debug() { let decimal = SignedDecimal::from_str("123.45").unwrap(); assert_eq!(format!("{decimal:?}"), "Decimal(123.45)"); From 340ea0d81b03a2f0a5be71c04bb7e3105139a7db Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Tue, 22 Aug 2023 15:36:52 +0200 Subject: [PATCH 03/26] Fix SignedDecimal::saturating_* math --- packages/std/src/math/signed_decimal.rs | 59 ++++++++++++++++++------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index b2eadbef6b..2655f87391 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -179,6 +179,12 @@ impl SignedDecimal { self.0.is_zero() } + /// Returns `true` if the number is negative (< 0) + #[must_use] + pub const fn is_negative(&self) -> bool { + self.0.i128() < 0 + } + /// 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. /// @@ -328,25 +334,26 @@ impl SignedDecimal { #[must_use = "this returns the result of the operation, without modifying the original"] pub fn saturating_add(self, other: Self) -> Self { - match self.checked_add(other) { - Ok(value) => value, - Err(_) => Self::MAX, - } + Self(self.0.saturating_add(other.0)) } #[must_use = "this returns the result of the operation, without modifying the original"] pub fn saturating_sub(self, other: Self) -> Self { - match self.checked_sub(other) { - Ok(value) => value, - Err(_) => Self::zero(), - } + Self(self.0.saturating_sub(other.0)) } #[must_use = "this returns the result of the operation, without modifying the original"] pub fn saturating_mul(self, other: Self) -> Self { match self.checked_mul(other) { Ok(value) => value, - Err(_) => Self::MAX, + Err(_) => { + // both negative or both positive results in positive number, otherwise negative + if self.is_negative() == other.is_negative() { + Self::MAX + } else { + Self::MIN + } + } } } @@ -354,7 +361,15 @@ impl SignedDecimal { pub fn saturating_pow(self, exp: u32) -> Self { match self.checked_pow(exp) { Ok(value) => value, - Err(_) => Self::MAX, + Err(_) => { + // odd exponent of negative number results in negative number + // everything else results in positive number + if self.is_negative() && exp % 2 == 1 { + Self::MIN + } else { + Self::MAX + } + } } } @@ -801,23 +816,23 @@ mod tests { ); assert_eq!( SignedDecimal::from_atomics(i128::MAX, 38).unwrap(), - SignedDecimal::from_str("3.402823669209384634").unwrap() + SignedDecimal::from_str("1.701411834604692317").unwrap() ); assert_eq!( SignedDecimal::from_atomics(i128::MAX, 39).unwrap(), - SignedDecimal::from_str("0.340282366920938463").unwrap() + SignedDecimal::from_str("0.170141183460469231").unwrap() ); assert_eq!( SignedDecimal::from_atomics(i128::MAX, 45).unwrap(), - SignedDecimal::from_str("0.000000340282366920").unwrap() + SignedDecimal::from_str("0.000000170141183460").unwrap() ); assert_eq!( SignedDecimal::from_atomics(i128::MAX, 51).unwrap(), - SignedDecimal::from_str("0.000000000000340282").unwrap() + SignedDecimal::from_str("0.000000000000170141").unwrap() ); assert_eq!( SignedDecimal::from_atomics(i128::MAX, 56).unwrap(), - SignedDecimal::from_str("0.000000000000000003").unwrap() + SignedDecimal::from_str("0.000000000000000001").unwrap() ); assert_eq!( SignedDecimal::from_atomics(i128::MAX, 57).unwrap(), @@ -2150,7 +2165,11 @@ mod tests { ); assert_eq!( SignedDecimal::zero().saturating_sub(SignedDecimal::percent(200)), - SignedDecimal::zero() + SignedDecimal::from_str("-2").unwrap() + ); + assert_eq!( + SignedDecimal::MIN.saturating_sub(SignedDecimal::percent(200)), + SignedDecimal::MIN ); assert_eq!( SignedDecimal::percent(200).saturating_mul(SignedDecimal::percent(50)), @@ -2160,6 +2179,14 @@ mod tests { SignedDecimal::MAX.saturating_mul(SignedDecimal::percent(200)), SignedDecimal::MAX ); + assert_eq!( + SignedDecimal::MIN.saturating_mul(SignedDecimal::percent(200)), + SignedDecimal::MIN + ); + assert_eq!( + SignedDecimal::MIN.saturating_mul(SignedDecimal::percent(-200)), + SignedDecimal::MAX + ); assert_eq!( SignedDecimal::percent(400).saturating_pow(2u32), SignedDecimal::percent(1600) From 5fa9425401b5845040782c9c511fb4b5fc450fe6 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 30 Aug 2023 18:21:36 +0200 Subject: [PATCH 04/26] Fix SignedDecimal::from_str --- packages/std/src/math/signed_decimal.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 2655f87391..3e2ac7a33c 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -475,6 +475,8 @@ impl FromStr for SignedDecimal { let mut parts_iter = input.split('.'); let whole_part = parts_iter.next().unwrap(); // split always returns at least one element + let is_neg = whole_part.starts_with('-'); + let whole = whole_part .parse::() .map_err(|_| StdError::generic_err("Error parsing whole"))?; @@ -484,7 +486,7 @@ impl FromStr for SignedDecimal { if let Some(fractional_part) = parts_iter.next() { let fractional = fractional_part - .parse::() + .parse::() // u64 is enough for 18 decimal places .map_err(|_| StdError::generic_err("Error parsing fractional"))?; let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( || { @@ -496,13 +498,20 @@ impl FromStr for SignedDecimal { )?; debug_assert!(exp <= Self::DECIMAL_PLACES); let fractional_factor = Int128::from(10i128.pow(exp)); - atomics = atomics - .checked_add( - // The inner multiplication can't overflow because - // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES - fractional.checked_mul(fractional_factor).unwrap(), - ) - .map_err(|_| StdError::generic_err("Value too big"))?; + + // This multiplication can't overflow because + // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES + let fractional_part = Int128::from(fractional) + .checked_mul(fractional_factor) + .unwrap(); + + // for negative numbers, we need to subtract the fractional part + atomics = if is_neg { + atomics.checked_sub(fractional_part) + } else { + atomics.checked_add(fractional_part) + } + .map_err(|_| StdError::generic_err("Value too big"))?; } if parts_iter.next().is_some() { From e71a34f9118b5047aff134af2c069a6cf5f8520a Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Tue, 29 Aug 2023 15:59:07 +0200 Subject: [PATCH 05/26] Fix tests --- packages/std/src/math/signed_decimal.rs | 83 +++++++++++++++++++++---- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 3e2ac7a33c..2f45934658 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -728,12 +728,18 @@ mod tests { fn signed_decimal_new() { let expected = Int128::from(300i128); assert_eq!(SignedDecimal::new(expected).0, expected); + + let expected = Int128::from(-300i128); + assert_eq!(SignedDecimal::new(expected).0, expected); } #[test] fn signed_decimal_raw() { let value = 300i128; assert_eq!(SignedDecimal::raw(value).0.i128(), value); + + let value = -300i128; + assert_eq!(SignedDecimal::raw(value).0.i128(), value); } #[test] @@ -755,6 +761,12 @@ mod tests { value.0, SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(2u8) ); + + let value = SignedDecimal::percent(-50); + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(-2i8) + ); } #[test] @@ -764,6 +776,12 @@ mod tests { value.0, SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(8u8) ); + + let value = SignedDecimal::permille(-125); + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(-8i8) + ); } #[test] @@ -773,12 +791,19 @@ mod tests { value.0, SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(80u8) ); + + let value = SignedDecimal::bps(-125); + assert_eq!( + value.0, + SignedDecimal::DECIMAL_FRACTIONAL / Int128::from(-80i8) + ); } #[test] fn signed_decimal_from_atomics_works() { let one = SignedDecimal::one(); let two = one + one; + let neg_one = SignedDecimal::negative_one(); assert_eq!(SignedDecimal::from_atomics(1i128, 0).unwrap(), one); assert_eq!(SignedDecimal::from_atomics(10i128, 1).unwrap(), one); @@ -814,6 +839,13 @@ mod tests { two ); + assert_eq!(SignedDecimal::from_atomics(-1i128, 0).unwrap(), neg_one); + assert_eq!(SignedDecimal::from_atomics(-10i128, 1).unwrap(), neg_one); + assert_eq!( + SignedDecimal::from_atomics(-100000000000000000000i128, 20).unwrap(), + neg_one + ); + // Cuts decimal digits (20 provided but only 18 can be stored) assert_eq!( SignedDecimal::from_atomics(4321i128, 20).unwrap(), @@ -880,6 +912,20 @@ mod tests { SignedDecimal::one() ); + // -1.0 + assert_eq!( + SignedDecimal::from_ratio(-1i128, 1i128), + SignedDecimal::negative_one() + ); + assert_eq!( + SignedDecimal::from_ratio(-53i128, 53i128), + SignedDecimal::negative_one() + ); + assert_eq!( + SignedDecimal::from_ratio(125i128, -125i128), + SignedDecimal::negative_one() + ); + // 1.5 assert_eq!( SignedDecimal::from_ratio(3i128, 2i128), @@ -968,6 +1014,16 @@ mod tests { fraction.denominator(), Int128::from(1_000_000_000_000_000_000i128) ); + + let fraction = SignedDecimal::from_str("-1234.567").unwrap(); + assert_eq!( + fraction.numerator(), + Int128::from(-1_234_567_000_000_000_000_000i128) + ); + assert_eq!( + fraction.denominator(), + Int128::from(1_000_000_000_000_000_000i128) + ); } #[test] @@ -1056,6 +1112,10 @@ mod tests { SignedDecimal::from_str("170141183460469231731.687303715884105727").unwrap(), SignedDecimal::MAX ); + assert_eq!( + SignedDecimal::from_str("-1").unwrap(), + SignedDecimal::negative_one() + ); } #[test] @@ -1069,11 +1129,6 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), e => panic!("Unexpected error: {e:?}"), } - - match SignedDecimal::from_str("-1").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } } #[test] @@ -1278,7 +1333,7 @@ mod tests { } #[test] - #[should_panic(expected = "attempt to add with overflow")] + #[should_panic] fn signed_decimal_add_overflow_panics() { let _value = SignedDecimal::MAX + SignedDecimal::percent(50); } @@ -1334,9 +1389,9 @@ mod tests { } #[test] - #[should_panic(expected = "attempt to subtract with overflow")] + #[should_panic] fn signed_decimal_sub_overflow_panics() { - let _value = SignedDecimal::zero() - SignedDecimal::percent(50); + let _value = SignedDecimal::MIN - SignedDecimal::percent(50); } #[test] @@ -1741,7 +1796,7 @@ mod tests { } #[test] - #[should_panic(expected = "attempt to divide by zero")] + #[should_panic] fn signed_decimal_int128_divide_by_zero() { let left = SignedDecimal::percent(150); // 1.5 let right = Int128::new(0); @@ -1762,7 +1817,7 @@ mod tests { } #[test] - #[should_panic(expected = "attempt to divide by zero")] + #[should_panic] fn signed_decimal_int128_div_assign_by_zero() { // a/0 let mut dec = SignedDecimal::percent(50); @@ -2094,8 +2149,14 @@ mod tests { .unwrap(), SignedDecimal::percent(1000) ); + assert_eq!( + SignedDecimal::zero() + .checked_sub(SignedDecimal::percent(1)) + .unwrap(), + SignedDecimal::percent(-1) + ); assert!(matches!( - SignedDecimal::zero().checked_sub(SignedDecimal::percent(1)), + SignedDecimal::MIN.checked_sub(SignedDecimal::percent(1)), Err(OverflowError { .. }) )); From 37ae96da3ca0f0e8d27d88ce6fa0a5cb81da4382 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 31 Aug 2023 10:46:19 +0200 Subject: [PATCH 06/26] Fix SignedDecimal Display impl --- packages/std/src/math/signed_decimal.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 2f45934658..ceb344cb53 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -532,13 +532,18 @@ impl fmt::Display for SignedDecimal { } else { let fractional_string = format!( "{:0>padding$}", - fractional, + fractional.abs(), // fractional should always be printed as positive padding = Self::DECIMAL_PLACES as usize ); - f.write_str(&whole.to_string())?; - f.write_char('.')?; - f.write_str(fractional_string.trim_end_matches('0'))?; - Ok(()) + if self.is_negative() { + f.write_char('-')?; + } + write!( + f, + "{whole}.{fractional}", + whole = whole.abs(), + fractional = fractional_string.trim_end_matches('0') + ) } } } From b9cfb6dcd8829165c14976e46a14bc74478df0e0 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 31 Aug 2023 10:53:13 +0200 Subject: [PATCH 07/26] Fix SignedDecimal::to_int_{floor, ceil} --- packages/std/src/math/signed_decimal.rs | 76 +++++++++++++++++++------ 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index ceb344cb53..ebab99b928 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -373,6 +373,42 @@ impl SignedDecimal { } } + /// Converts this decimal to an unsigned integer by rounding down + /// to the next integer, e.g. 22.5 becomes 22 and -1.2 becomes -2. + /// + /// ## Examples + /// + /// ``` + /// use core::str::FromStr; + /// use cosmwasm_std::{SignedDecimal, Int128}; + /// + /// let d = SignedDecimal::from_str("12.345").unwrap(); + /// assert_eq!(d.to_int_floor(), Int128::new(12)); + /// + /// let d = SignedDecimal::from_str("-12.999").unwrap(); + /// assert_eq!(d.to_int_floor(), Int128::new(-13)); + /// + /// let d = SignedDecimal::from_str("-0.05").unwrap(); + /// assert_eq!(d.to_int_floor(), Int128::new(-1)); + /// ``` + #[must_use] + pub fn to_int_floor(self) -> Int128 { + if self.is_negative() { + // Using `x.to_int_floor() = -(-x).to_int_ceil()` for a negative `x`, + // but avoiding overflow by implementing the formula from `to_int_ceil` directly. + let x = self.0; + let y = Self::DECIMAL_FRACTIONAL; + if x.is_zero() { + Int128::zero() + } else { + // making sure not to negate `x`, as this would overflow + -Int128::one() - ((-Int128::one() - x) / y) + } + } else { + self.to_int_trunc() + } + } + /// Converts this decimal to an unsigned integer by truncating /// the fractional part, e.g. 22.5 becomes 22. /// @@ -380,50 +416,54 @@ impl SignedDecimal { /// /// ``` /// use core::str::FromStr; - /// use cosmwasm_std::{Decimal, Int128}; + /// use cosmwasm_std::{SignedDecimal, Int128}; /// /// let d = SignedDecimal::from_str("12.345").unwrap(); - /// assert_eq!(d.to_int_floor(), Int128::new(12)); + /// assert_eq!(d.to_int_trunc(), Int128::new(12)); /// - /// let d = SignedDecimal::from_str("12.999").unwrap(); - /// assert_eq!(d.to_int_floor(), Int128::new(12)); + /// let d = SignedDecimal::from_str("-12.999").unwrap(); + /// assert_eq!(d.to_int_trunc(), Int128::new(-12)); /// /// let d = SignedDecimal::from_str("75.0").unwrap(); - /// assert_eq!(d.to_int_floor(), Int128::new(75)); + /// assert_eq!(d.to_int_trunc(), Int128::new(75)); /// ``` #[must_use] - pub fn to_int_floor(self) -> Int128 { + pub fn to_int_trunc(self) -> Int128 { self.0 / Self::DECIMAL_FRACTIONAL } - /// Converts this decimal to an unsigned integer by rounting up - /// to the next integer, e.g. 22.3 becomes 23. + /// Converts this decimal to an unsigned integer by rounding up + /// to the next integer, e.g. 22.3 becomes 23 and -1.2 becomes -1. /// /// ## Examples /// /// ``` /// use core::str::FromStr; - /// use cosmwasm_std::{Decimal, Int128}; + /// use cosmwasm_std::{SignedDecimal, Int128}; /// /// let d = SignedDecimal::from_str("12.345").unwrap(); /// assert_eq!(d.to_int_ceil(), Int128::new(13)); /// - /// let d = SignedDecimal::from_str("12.999").unwrap(); - /// assert_eq!(d.to_int_ceil(), Int128::new(13)); + /// let d = SignedDecimal::from_str("-12.999").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int128::new(-12)); /// /// let d = SignedDecimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_ceil(), Int128::new(75)); /// ``` #[must_use] pub fn to_int_ceil(self) -> Int128 { - // Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q - // from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow. - let x = self.0; - let y = Self::DECIMAL_FRACTIONAL; - if x.is_zero() { - Int128::zero() + if self.is_negative() { + self.to_int_trunc() } else { - Int128::one() + ((x - Int128::one()) / y) + // Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q + // from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow. + let x = self.0; + let y = Self::DECIMAL_FRACTIONAL; + if x.is_zero() { + Int128::zero() + } else { + Int128::one() + ((x - Int128::one()) / y) + } } } } From 17b906b7590a22ada25d02318913f3f5b6ecdb18 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 31 Aug 2023 16:28:02 +0200 Subject: [PATCH 08/26] Fix SignedDecimal::floor --- packages/std/src/errors/mod.rs | 3 ++- packages/std/src/errors/std_error.rs | 4 ++++ packages/std/src/math/signed_decimal.rs | 32 ++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index 54422007d3..47555b5849 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -7,7 +7,8 @@ pub use recover_pubkey_error::RecoverPubkeyError; pub use std_error::{ CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, CoinFromStrError, CoinsError, ConversionOverflowError, DivideByZeroError, DivisionError, - OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult, + OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, + StdResult, }; pub use system_error::SystemError; pub use verification_error::VerificationError; diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs index f8b0c8275e..2900e65141 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -599,6 +599,10 @@ pub enum CheckedFromRatioError { #[error("Round up operation failed because of overflow")] pub struct RoundUpOverflowError; +#[derive(Error, Debug, PartialEq, Eq)] +#[error("Round down operation failed because of overflow")] +pub struct RoundDownOverflowError; + #[derive(Error, Debug, PartialEq, Eq)] pub enum CoinsError { #[error("Duplicate denom")] diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index ebab99b928..e8c9c67caf 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -11,7 +11,7 @@ use thiserror::Error; use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, - OverflowOperation, RoundUpOverflowError, StdError, + OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; use crate::{forward_ref_partial_eq, Decimal, Int256}; @@ -219,12 +219,38 @@ impl SignedDecimal { Self::DECIMAL_PLACES } - /// Rounds value down after decimal places. + /// Rounds value by truncating the decimal places. #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn floor(&self) -> Self { + pub fn trunc(&self) -> Self { Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) } + /// Rounds value down after decimal places. Panics on overflow. + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn floor(&self) -> Self { + match self.checked_floor() { + Ok(value) => value, + Err(_) => panic!("attempt to floor with overflow"), + } + } + + /// Rounds value down after decimal places. + pub fn checked_floor(&self) -> Result { + if self.is_negative() { + let truncated = self.trunc(); + + if truncated != self { + truncated + .checked_sub(SignedDecimal::one()) + .map_err(|_| RoundDownOverflowError) + } else { + Ok(truncated) + } + } else { + Ok(self.trunc()) + } + } + /// Rounds value up after decimal places. Panics on overflow. #[must_use = "this returns the result of the operation, without modifying the original"] pub fn ceil(&self) -> Self { From 40256aece752ad938c00aed736d99b9b90f4e264 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 31 Aug 2023 17:34:13 +0200 Subject: [PATCH 09/26] Improve SignedDecimal tests --- packages/std/src/math/signed_decimal.rs | 460 +++++++++++++++++++++++- 1 file changed, 447 insertions(+), 13 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index e8c9c67caf..6f3f1a249f 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -532,7 +532,7 @@ impl FromStr for SignedDecimal { type Err = StdError; /// Converts the decimal string to a SignedDecimal - /// Possible inputs: "1.23", "1", "000012", "1.123000000" + /// Possible inputs: "1.23", "1", "000012", "1.123000000", "-1.12300" /// Disallowed: "", ".23" /// /// This never performs any kind of rounding. @@ -616,7 +616,7 @@ impl fmt::Display for SignedDecimal { impl fmt::Debug for SignedDecimal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Decimal({self})") + write!(f, "SignedDecimal({self})") } } @@ -922,6 +922,10 @@ mod tests { SignedDecimal::from_atomics(4321i128, 20).unwrap(), SignedDecimal::from_str("0.000000000000000043").unwrap() ); + assert_eq!( + SignedDecimal::from_atomics(-4321i128, 20).unwrap(), + SignedDecimal::from_str("-0.000000000000000043").unwrap() + ); assert_eq!( SignedDecimal::from_atomics(6789i128, 20).unwrap(), SignedDecimal::from_str("0.000000000000000067").unwrap() @@ -962,6 +966,13 @@ mod tests { max ); + // Can be used with min value + let min = SignedDecimal::MIN; + assert_eq!( + SignedDecimal::from_atomics(min.atomics(), min.decimal_places()).unwrap(), + min + ); + // Overflow is only possible with digits < 18 let result = SignedDecimal::from_atomics(i128::MAX, 17); assert_eq!(result.unwrap_err(), SignedDecimalRangeExceeded); @@ -1021,6 +1032,16 @@ mod tests { SignedDecimal::permille(125) ); + // -0.125 + assert_eq!( + SignedDecimal::from_ratio(-1i64, 8i64), + SignedDecimal::permille(-125) + ); + assert_eq!( + SignedDecimal::from_ratio(125i64, -1000i64), + SignedDecimal::permille(-125) + ); + // 1/3 (result floored) assert_eq!( SignedDecimal::from_ratio(1i64, 3i64), @@ -1133,7 +1154,7 @@ mod tests { SignedDecimal::percent(4200) ); - // SignedDecimals + // Positive decimals assert_eq!( SignedDecimal::from_str("1.0").unwrap(), SignedDecimal::percent(100) @@ -1167,6 +1188,19 @@ mod tests { SignedDecimal::from_str("00.04").unwrap(), SignedDecimal::percent(4) ); + // Negative decimals + assert_eq!( + SignedDecimal::from_str("-00.04").unwrap(), + SignedDecimal::percent(-4) + ); + assert_eq!( + SignedDecimal::from_str("-00.40").unwrap(), + SignedDecimal::percent(-40) + ); + assert_eq!( + SignedDecimal::from_str("-04.00").unwrap(), + SignedDecimal::percent(-400) + ); // Can handle DECIMAL_PLACES fractional digits assert_eq!( @@ -1183,6 +1217,11 @@ mod tests { SignedDecimal::from_str("170141183460469231731.687303715884105727").unwrap(), SignedDecimal::MAX ); + // Works for documented min value + assert_eq!( + SignedDecimal::from_str("-170141183460469231731.687303715884105728").unwrap(), + SignedDecimal::MIN + ); assert_eq!( SignedDecimal::from_str("-1").unwrap(), SignedDecimal::negative_one() @@ -1200,6 +1239,11 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), e => panic!("Unexpected error: {e:?}"), } + + match SignedDecimal::from_str("-").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } } #[test] @@ -1223,6 +1267,11 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), } + + match SignedDecimal::from_str("1.-2").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } } #[test] @@ -1263,6 +1312,10 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } + match SignedDecimal::from_str("-170141183460469231732").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } // SignedDecimal match SignedDecimal::from_str("170141183460469231732.0").unwrap_err() { @@ -1273,6 +1326,10 @@ mod tests { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), e => panic!("Unexpected error: {e:?}"), } + match SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } } #[test] @@ -1282,12 +1339,18 @@ mod tests { let half = SignedDecimal::percent(50); let two = SignedDecimal::percent(200); let max = SignedDecimal::MAX; + let neg_half = SignedDecimal::percent(-50); + let neg_two = SignedDecimal::percent(-200); + let min = SignedDecimal::MIN; assert_eq!(zero.atomics(), Int128::new(0)); assert_eq!(one.atomics(), Int128::new(1000000000000000000)); assert_eq!(half.atomics(), Int128::new(500000000000000000)); assert_eq!(two.atomics(), Int128::new(2000000000000000000)); assert_eq!(max.atomics(), Int128::MAX); + assert_eq!(neg_half.atomics(), Int128::new(-500000000000000000)); + assert_eq!(neg_two.atomics(), Int128::new(-2000000000000000000)); + assert_eq!(min.atomics(), Int128::MIN); } #[test] @@ -1297,12 +1360,14 @@ mod tests { let half = SignedDecimal::percent(50); let two = SignedDecimal::percent(200); let max = SignedDecimal::MAX; + let neg_one = SignedDecimal::negative_one(); 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); + assert_eq!(neg_one.decimal_places(), 18); } #[test] @@ -1313,7 +1378,7 @@ mod tests { assert!(!SignedDecimal::one().is_zero()); assert!(!SignedDecimal::percent(123).is_zero()); - assert!(!SignedDecimal::permille(1234).is_zero()); + assert!(!SignedDecimal::permille(-1234).is_zero()); } #[test] @@ -1324,6 +1389,12 @@ mod tests { // d == 1 assert_eq!(SignedDecimal::one().inv(), Some(SignedDecimal::one())); + // d == -1 + assert_eq!( + SignedDecimal::negative_one().inv(), + Some(SignedDecimal::negative_one()) + ); + // d > 1 exact assert_eq!( SignedDecimal::from_str("2").unwrap().inv(), @@ -1369,6 +1440,17 @@ mod tests { SignedDecimal::from_str("0.0005").unwrap().inv(), Some(SignedDecimal::from_str("2000").unwrap()) ); + + // d < 0 + assert_eq!( + SignedDecimal::from_str("-0.5").unwrap().inv(), + Some(SignedDecimal::from_str("-2").unwrap()) + ); + // d < 0 rounded + assert_eq!( + SignedDecimal::from_str("-3").unwrap().inv(), + Some(SignedDecimal::from_str("-0.333333333333333333").unwrap()) + ); } #[test] @@ -1392,6 +1474,19 @@ mod tests { SignedDecimal::zero() + SignedDecimal::zero(), SignedDecimal::zero() ); + // negative numbers + assert_eq!( + SignedDecimal::percent(-5) + SignedDecimal::percent(-4), + SignedDecimal::percent(-9) + ); + assert_eq!( + SignedDecimal::percent(-5) + SignedDecimal::percent(4), + SignedDecimal::percent(-1) + ); + assert_eq!( + SignedDecimal::percent(5) + SignedDecimal::percent(-4), + SignedDecimal::percent(1) + ); // works for refs let a = SignedDecimal::percent(15); @@ -1449,6 +1544,20 @@ mod tests { SignedDecimal::zero() ); + // negative numbers + assert_eq!( + SignedDecimal::percent(-5) - SignedDecimal::percent(-4), + SignedDecimal::percent(-1) + ); + assert_eq!( + SignedDecimal::percent(-5) - SignedDecimal::percent(4), + SignedDecimal::percent(-9) + ); + assert_eq!( + SignedDecimal::percent(500) - SignedDecimal::percent(-4), + SignedDecimal::percent(504) + ); + // works for refs let a = SignedDecimal::percent(13); let b = SignedDecimal::percent(6); @@ -1511,6 +1620,11 @@ mod tests { SignedDecimal::percent(1000) ); assert_eq!(SignedDecimal::MAX * one, SignedDecimal::MAX); + assert_eq!(SignedDecimal::percent(-1) * one, SignedDecimal::percent(-1)); + assert_eq!( + one * SignedDecimal::percent(-10), + SignedDecimal::percent(-10) + ); // double assert_eq!(two * SignedDecimal::percent(0), SignedDecimal::percent(0)); @@ -1535,6 +1649,11 @@ mod tests { SignedDecimal::percent(1000) * two, SignedDecimal::percent(2000) ); + assert_eq!(SignedDecimal::percent(-1) * two, SignedDecimal::percent(-2)); + assert_eq!( + two * SignedDecimal::new(Int128::MIN / Int128::new(2)), + SignedDecimal::MIN + ); // half assert_eq!(half * SignedDecimal::percent(0), SignedDecimal::percent(0)); @@ -1580,6 +1699,10 @@ mod tests { assert_eq!(dec("1000000000000") * a, dec("123127726548762.582")); assert_eq!(dec("1000000000000000") * a, dec("123127726548762582")); assert_eq!(dec("1000000000000000000") * a, dec("123127726548762582000")); + assert_eq!( + dec("-1000000000000000000") * a, + dec("-123127726548762582000") + ); // Move right let max = SignedDecimal::MAX; @@ -1663,6 +1786,8 @@ mod tests { SignedDecimal::percent(200), ), (SignedDecimal::permille(6), SignedDecimal::permille(13)), + (SignedDecimal::permille(-6), SignedDecimal::permille(0)), + (SignedDecimal::MAX, SignedDecimal::negative_one()), ]; // The regular core::ops::Mul is our source of truth for these tests. @@ -1718,6 +1843,14 @@ mod tests { SignedDecimal::percent(1000) / one, SignedDecimal::percent(1000) ); + assert_eq!( + one / SignedDecimal::percent(-1), + SignedDecimal::percent(-10_000) + ); + assert_eq!( + one / SignedDecimal::percent(-10), + SignedDecimal::percent(-1_000) + ); // double assert_eq!( @@ -1747,6 +1880,14 @@ mod tests { SignedDecimal::percent(1000) / two, SignedDecimal::percent(500) ); + assert_eq!( + two / SignedDecimal::percent(-1), + SignedDecimal::percent(-20_000) + ); + assert_eq!( + SignedDecimal::percent(-10000) / two, + SignedDecimal::percent(-5000) + ); // half assert_eq!( @@ -1800,6 +1941,18 @@ mod tests { assert_eq!(dec("1000000000000") / a, dec("0.000008121647560868")); assert_eq!(dec("1000000000000000") / a, dec("0.008121647560868164")); assert_eq!(dec("1000000000000000000") / a, dec("8.121647560868164773")); + // negative + let a = dec("-123127726548762582"); + assert_eq!(a / dec("1"), dec("-123127726548762582")); + assert_eq!(a / dec("10"), dec("-12312772654876258.2")); + assert_eq!(a / dec("100"), dec("-1231277265487625.82")); + assert_eq!(a / dec("1000"), dec("-123127726548762.582")); + assert_eq!(a / dec("1000000"), dec("-123127726548.762582")); + assert_eq!(a / dec("1000000000"), dec("-123127726.548762582")); + assert_eq!(a / dec("1000000000000"), dec("-123127.726548762582")); + assert_eq!(a / dec("1000000000000000"), dec("-123.127726548762582")); + assert_eq!(a / dec("1000000000000000000"), dec("-0.123127726548762582")); + assert_eq!(dec("1") / a, dec("-0.000000000000000008")); // Move left let a = dec("0.123127726548762582"); @@ -1812,6 +1965,14 @@ mod tests { assert_eq!(a / dec("0.000000000001"), dec("123127726548.762582")); assert_eq!(a / dec("0.000000000000001"), dec("123127726548762.582")); assert_eq!(a / dec("0.000000000000000001"), dec("123127726548762582")); + // negative + let a = dec("-0.123127726548762582"); + assert_eq!(a / dec("1.0"), dec("-0.123127726548762582")); + assert_eq!(a / dec("0.1"), dec("-1.23127726548762582")); + assert_eq!(a / dec("0.01"), dec("-12.3127726548762582")); + assert_eq!(a / dec("0.001"), dec("-123.127726548762582")); + assert_eq!(a / dec("0.000001"), dec("-123127.726548762582")); + assert_eq!(a / dec("0.000000001"), dec("-123127726.548762582")); assert_eq!( SignedDecimal::percent(15) / SignedDecimal::percent(60), @@ -1860,6 +2021,11 @@ mod tests { let right = Int128::new(3); assert_eq!(left / right, SignedDecimal::percent(50)); + // negative + let left = SignedDecimal::percent(-150); // -1.5 + let right = Int128::new(3); + assert_eq!(left / right, SignedDecimal::percent(-50)); + // 0/a let left = SignedDecimal::zero(); let right = Int128::new(300); @@ -1918,6 +2084,18 @@ mod tests { ); } + for exp in 1..10 { + assert_eq!( + SignedDecimal::negative_one().checked_pow(exp).unwrap(), + // alternates between 1 and -1 + if exp % 2 == 0 { + SignedDecimal::one() + } else { + SignedDecimal::negative_one() + } + ) + } + for num in &[ SignedDecimal::percent(50), SignedDecimal::percent(99), @@ -1975,6 +2153,24 @@ mod tests { SignedDecimal::percent(10).checked_pow(18).unwrap(), SignedDecimal(1i128.into()) ); + + let decimals = [ + SignedDecimal::percent(-50), + SignedDecimal::percent(-99), + SignedDecimal::percent(-200), + ]; + let exponents = [1, 2, 3, 4, 5, 8, 10]; + + for d in decimals { + for e in exponents { + // use multiplication as source of truth + let mut mul = Ok(d); + for _ in 1..e { + mul = mul.and_then(|mul| mul.checked_mul(d)); + } + assert_eq!(mul, d.checked_pow(e)); + } + } } #[test] @@ -1995,12 +2191,17 @@ mod tests { assert_eq!(SignedDecimal::zero().to_string(), "0"); assert_eq!(SignedDecimal::one().to_string(), "1"); assert_eq!(SignedDecimal::percent(500).to_string(), "5"); + assert_eq!(SignedDecimal::percent(-500).to_string(), "-5"); // SignedDecimals assert_eq!(SignedDecimal::percent(125).to_string(), "1.25"); assert_eq!(SignedDecimal::percent(42638).to_string(), "426.38"); assert_eq!(SignedDecimal::percent(3).to_string(), "0.03"); assert_eq!(SignedDecimal::permille(987).to_string(), "0.987"); + assert_eq!(SignedDecimal::percent(-125).to_string(), "-1.25"); + assert_eq!(SignedDecimal::percent(-42638).to_string(), "-426.38"); + assert_eq!(SignedDecimal::percent(-3).to_string(), "-0.03"); + assert_eq!(SignedDecimal::permille(-987).to_string(), "-0.987"); assert_eq!( SignedDecimal(Int128::from(1i128)).to_string(), @@ -2070,6 +2271,18 @@ mod tests { SignedDecimal(Int128::from(100000000000000000i128)).to_string(), "0.1" ); + assert_eq!( + SignedDecimal(Int128::from(-1i128)).to_string(), + "-0.000000000000000001" + ); + assert_eq!( + SignedDecimal(Int128::from(-100000000000000i128)).to_string(), + "-0.0001" + ); + assert_eq!( + SignedDecimal(Int128::from(-100000000000000000i128)).to_string(), + "-0.1" + ); } #[test] @@ -2078,14 +2291,15 @@ mod tests { SignedDecimal::zero(), SignedDecimal(Int128::from(2i128)), SignedDecimal(Int128::from(2i128)), + SignedDecimal(Int128::from(-2i128)), ]; assert_eq!( items.iter().sum::(), - SignedDecimal(Int128::from(4i128)) + SignedDecimal(Int128::from(2i128)) ); assert_eq!( items.into_iter().sum::(), - SignedDecimal(Int128::from(4i128)) + SignedDecimal(Int128::from(2i128)) ); let empty: Vec = vec![]; @@ -2103,6 +2317,12 @@ mod tests { to_vec(&SignedDecimal::percent(8765)).unwrap(), br#""87.65""# ); + assert_eq!( + to_vec(&SignedDecimal::percent(-87654)).unwrap(), + br#""-876.54""# + ); + assert_eq!(to_vec(&SignedDecimal::negative_one()).unwrap(), br#""-1""#); + assert_eq!(to_vec(&-SignedDecimal::percent(8)).unwrap(), br#""-0.08""#); } #[test] @@ -2140,6 +2360,24 @@ mod tests { from_slice::(br#""87.65""#).unwrap(), SignedDecimal::percent(8765) ); + + // negative numbers + assert_eq!( + from_slice::(br#""-0""#).unwrap(), + SignedDecimal::zero() + ); + assert_eq!( + from_slice::(br#""-1""#).unwrap(), + SignedDecimal::negative_one() + ); + assert_eq!( + from_slice::(br#""-001""#).unwrap(), + SignedDecimal::negative_one() + ); + assert_eq!( + from_slice::(br#""-0.08""#).unwrap(), + SignedDecimal::percent(-8) + ); } #[test] @@ -2155,6 +2393,12 @@ mod tests { let expected = Decimal::percent(400); assert_eq!(a.abs_diff(b), expected); assert_eq!(b.abs_diff(a), expected); + + let a = SignedDecimal::percent(-200); + let b = SignedDecimal::percent(-240); + let expected = Decimal::percent(40); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); } #[test] @@ -2172,6 +2416,12 @@ mod tests { SignedDecimal::percent(325) ); + // -20.25 % 5 = -25 + assert_eq!( + SignedDecimal::percent(-2025) % SignedDecimal::percent(500), + SignedDecimal::percent(-25) + ); + let a = SignedDecimal::percent(318); let b = SignedDecimal::percent(317); let expected = SignedDecimal::percent(1); @@ -2191,6 +2441,11 @@ mod tests { let b = SignedDecimal::percent(1270); a %= &b; assert_eq!(a, SignedDecimal::percent(452)); // 42.62 % 12.7 = 4.52 + + let mut a = SignedDecimal::percent(-4262); + let b = SignedDecimal::percent(1270); + a %= &b; + assert_eq!(a, SignedDecimal::percent(-452)); // -42.62 % 12.7 = -4.52 } #[test] @@ -2212,6 +2467,10 @@ mod tests { SignedDecimal::MAX.checked_add(SignedDecimal::percent(1)), Err(OverflowError { .. }) )); + assert!(matches!( + SignedDecimal::MIN.checked_add(SignedDecimal::percent(-1)), + Err(OverflowError { .. }) + )); // checked sub assert_eq!( @@ -2230,6 +2489,10 @@ mod tests { SignedDecimal::MIN.checked_sub(SignedDecimal::percent(1)), Err(OverflowError { .. }) )); + assert!(matches!( + SignedDecimal::MAX.checked_sub(SignedDecimal::percent(-1)), + Err(OverflowError { .. }) + )); // checked div assert_eq!( @@ -2252,6 +2515,18 @@ mod tests { SignedDecimal::MAX.checked_div(SignedDecimal::percent(1)), Err(CheckedFromRatioError::Overflow {}) )); + assert_eq!( + SignedDecimal::percent(-88) + .checked_div(SignedDecimal::percent(20)) + .unwrap(), + SignedDecimal::percent(-440) + ); + assert_eq!( + SignedDecimal::percent(-88) + .checked_div(SignedDecimal::percent(-20)) + .unwrap(), + SignedDecimal::percent(440) + ); // checked rem assert_eq!( @@ -2266,6 +2541,18 @@ mod tests { .unwrap(), SignedDecimal::percent(325) ); + assert_eq!( + SignedDecimal::percent(-1525) + .checked_rem(SignedDecimal::percent(400)) + .unwrap(), + SignedDecimal::percent(-325) + ); + assert_eq!( + SignedDecimal::percent(-1525) + .checked_rem(SignedDecimal::percent(-400)) + .unwrap(), + SignedDecimal::percent(-325) + ); assert!(matches!( SignedDecimal::MAX.checked_rem(SignedDecimal::zero()), Err(DivideByZeroError { .. }) @@ -2278,6 +2565,14 @@ mod tests { SignedDecimal::percent(200).pow(2), SignedDecimal::percent(400) ); + assert_eq!( + SignedDecimal::percent(-200).pow(2), + SignedDecimal::percent(400) + ); + assert_eq!( + SignedDecimal::percent(-200).pow(3), + SignedDecimal::percent(-800) + ); assert_eq!( SignedDecimal::percent(200).pow(10), SignedDecimal::percent(102400) @@ -2296,14 +2591,34 @@ mod tests { SignedDecimal::percent(200).saturating_add(SignedDecimal::percent(200)), SignedDecimal::percent(400) ); + assert_eq!( + SignedDecimal::percent(-200).saturating_add(SignedDecimal::percent(200)), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::percent(-200).saturating_add(SignedDecimal::percent(-200)), + SignedDecimal::percent(-400) + ); assert_eq!( SignedDecimal::MAX.saturating_add(SignedDecimal::percent(200)), SignedDecimal::MAX ); + assert_eq!( + SignedDecimal::MIN.saturating_add(SignedDecimal::percent(-1)), + SignedDecimal::MIN + ); assert_eq!( SignedDecimal::percent(200).saturating_sub(SignedDecimal::percent(100)), SignedDecimal::percent(100) ); + assert_eq!( + SignedDecimal::percent(-200).saturating_sub(SignedDecimal::percent(100)), + SignedDecimal::percent(-300) + ); + assert_eq!( + SignedDecimal::percent(-200).saturating_sub(SignedDecimal::percent(-100)), + SignedDecimal::percent(-100) + ); assert_eq!( SignedDecimal::zero().saturating_sub(SignedDecimal::percent(200)), SignedDecimal::from_str("-2").unwrap() @@ -2312,10 +2627,22 @@ mod tests { SignedDecimal::MIN.saturating_sub(SignedDecimal::percent(200)), SignedDecimal::MIN ); + assert_eq!( + SignedDecimal::MAX.saturating_sub(SignedDecimal::percent(-200)), + SignedDecimal::MAX + ); assert_eq!( SignedDecimal::percent(200).saturating_mul(SignedDecimal::percent(50)), SignedDecimal::percent(100) ); + assert_eq!( + SignedDecimal::percent(-200).saturating_mul(SignedDecimal::percent(50)), + SignedDecimal::percent(-100) + ); + assert_eq!( + SignedDecimal::percent(-200).saturating_mul(SignedDecimal::percent(-50)), + SignedDecimal::percent(100) + ); assert_eq!( SignedDecimal::MAX.saturating_mul(SignedDecimal::percent(200)), SignedDecimal::MAX @@ -2345,6 +2672,18 @@ mod tests { SignedDecimal::percent(200) ); assert_eq!(SignedDecimal::percent(99).floor(), SignedDecimal::zero()); + assert_eq!( + SignedDecimal(Int128::from(1i128)).floor(), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal(Int128::from(-1i128)).floor(), + SignedDecimal::negative_one() + ); + assert_eq!( + SignedDecimal::permille(-1234).floor(), + SignedDecimal::percent(-200) + ); assert_eq!(SignedDecimal::one().ceil(), SignedDecimal::one()); assert_eq!( @@ -2360,6 +2699,35 @@ mod tests { SignedDecimal(Int128::from(1i128)).ceil(), SignedDecimal::one() ); + assert_eq!( + SignedDecimal(Int128::from(-1i128)).ceil(), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::permille(-1234).ceil(), + SignedDecimal::negative_one() + ); + + assert_eq!(SignedDecimal::one().trunc(), SignedDecimal::one()); + assert_eq!(SignedDecimal::percent(150).trunc(), SignedDecimal::one()); + assert_eq!(SignedDecimal::percent(199).trunc(), SignedDecimal::one()); + assert_eq!( + SignedDecimal::percent(200).trunc(), + SignedDecimal::percent(200) + ); + assert_eq!(SignedDecimal::percent(99).trunc(), SignedDecimal::zero()); + assert_eq!( + SignedDecimal(Int128::from(1i128)).trunc(), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal(Int128::from(-1i128)).trunc(), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::permille(-1234).trunc(), + SignedDecimal::negative_one() + ); } #[test] @@ -2368,16 +2736,35 @@ mod tests { let _ = SignedDecimal::MAX.ceil(); } + #[test] + #[should_panic(expected = "attempt to floor with overflow")] + fn signed_decimal_floor_panics() { + let _ = SignedDecimal::MIN.floor(); + } + #[test] fn signed_decimal_checked_ceil() { assert_eq!( SignedDecimal::percent(199).checked_ceil(), Ok(SignedDecimal::percent(200)) ); - assert!(matches!( - SignedDecimal::MAX.checked_ceil(), - Err(RoundUpOverflowError { .. }) - )); + assert_eq!(SignedDecimal::MAX.checked_ceil(), Err(RoundUpOverflowError)); + } + + #[test] + fn signed_decimal_checked_floor() { + assert_eq!( + SignedDecimal::percent(199).checked_floor(), + Ok(SignedDecimal::one()) + ); + assert_eq!( + SignedDecimal::percent(-199).checked_floor(), + Ok(SignedDecimal::percent(-200)) + ); + assert_eq!( + SignedDecimal::MIN.checked_floor(), + Err(RoundDownOverflowError) + ); } #[test] @@ -2390,14 +2777,22 @@ mod tests { assert_eq!(d.to_int_floor(), Int128::new(12)); let d = SignedDecimal::from_str("0.98451384").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(0)); + let d = SignedDecimal::from_str("-12.000000000000000001").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(-13)); + let d = SignedDecimal::from_str("-12.345").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(-13)); let d = SignedDecimal::from_str("75.0").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(75)); let d = SignedDecimal::from_str("0.0").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(0)); + let d = SignedDecimal::from_str("-75.0").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(-75)); let d = SignedDecimal::MAX; assert_eq!(d.to_int_floor(), Int128::new(170141183460469231731)); + let d = SignedDecimal::MIN; + assert_eq!(d.to_int_floor(), Int128::new(-170141183460469231732)); } #[test] @@ -2408,14 +2803,48 @@ mod tests { assert_eq!(d.to_int_ceil(), Int128::new(13)); let d = SignedDecimal::from_str("12.999").unwrap(); assert_eq!(d.to_int_ceil(), Int128::new(13)); + let d = SignedDecimal::from_str("-12.000000000000000001").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(-12)); + let d = SignedDecimal::from_str("-12.345").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(-12)); let d = SignedDecimal::from_str("75.0").unwrap(); assert_eq!(d.to_int_ceil(), Int128::new(75)); let d = SignedDecimal::from_str("0.0").unwrap(); assert_eq!(d.to_int_ceil(), Int128::new(0)); + let d = SignedDecimal::from_str("-75.0").unwrap(); + assert_eq!(d.to_int_ceil(), Int128::new(-75)); let d = SignedDecimal::MAX; assert_eq!(d.to_int_ceil(), Int128::new(170141183460469231732)); + let d = SignedDecimal::MIN; + assert_eq!(d.to_int_ceil(), Int128::new(-170141183460469231731)); + } + + #[test] + fn signed_decimal_to_int_trunc_works() { + let d = SignedDecimal::from_str("12.000000000000000001").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(12)); + let d = SignedDecimal::from_str("12.345").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(12)); + let d = SignedDecimal::from_str("12.999").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(12)); + let d = SignedDecimal::from_str("-12.000000000000000001").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(-12)); + let d = SignedDecimal::from_str("-12.345").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(-12)); + + let d = SignedDecimal::from_str("75.0").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(75)); + let d = SignedDecimal::from_str("0.0").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(0)); + let d = SignedDecimal::from_str("-75.0").unwrap(); + assert_eq!(d.to_int_trunc(), Int128::new(-75)); + + let d = SignedDecimal::MAX; + assert_eq!(d.to_int_trunc(), Int128::new(170141183460469231731)); + let d = SignedDecimal::MIN; + assert_eq!(d.to_int_trunc(), Int128::new(-170141183460469231731)); } #[test] @@ -2431,6 +2860,11 @@ mod tests { ("0.5", "0.5", true), ("0.5", "0.51", false), ("0", "0.00000", true), + ("-1", "-1", true), + ("-0.5", "-0.5", true), + ("-0.5", "0.5", false), + ("-0.5", "-0.51", false), + ("-0", "-0.00000", true), ] .into_iter() .map(|(lhs, rhs, expected)| (dec(lhs), dec(rhs), expected)); @@ -2447,12 +2881,12 @@ mod tests { #[test] fn signed_decimal_implements_debug() { let decimal = SignedDecimal::from_str("123.45").unwrap(); - assert_eq!(format!("{decimal:?}"), "Decimal(123.45)"); + assert_eq!(format!("{decimal:?}"), "SignedDecimal(123.45)"); - let test_cases = ["5", "5.01", "42", "0", "2"]; + let test_cases = ["5", "5.01", "42", "0", "2", "-0.000001"]; for s in test_cases { let decimal = SignedDecimal::from_str(s).unwrap(); - let expected = format!("Decimal({s})"); + let expected = format!("SignedDecimal({s})"); assert_eq!(format!("{decimal:?}"), expected); } } From 38733394809809ee89b4710eb1d26e4d9731aa06 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Tue, 5 Sep 2023 10:23:00 +0200 Subject: [PATCH 10/26] Improve SignedDecimal docs --- packages/std/src/math/signed_decimal.rs | 85 +++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 6f3f1a249f..e390c77f12 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -39,19 +39,49 @@ impl SignedDecimal { /// The number of decimal places. Since decimal types are fixed-point rather than /// floating-point, this is a constant. pub const DECIMAL_PLACES: u32 = 18; // This needs to be an even number. + /// The largest value that can be represented by this signed decimal type. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// assert_eq!(SignedDecimal::MAX.to_string(), "170141183460469231731.687303715884105727"); + /// ``` pub const MAX: Self = Self(Int128::MAX); + /// The smallest value that can be represented by this signed decimal type. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// assert_eq!(SignedDecimal::MIN.to_string(), "-170141183460469231731.687303715884105728"); + /// ``` pub const MIN: Self = Self(Int128::MIN); /// Creates a SignedDecimal(value) /// This is equivalent to `Decimal::from_atomics(value, 18)` but usable in a const context. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::{SignedDecimal, Int128}; + /// assert_eq!(SignedDecimal::new(Int128::one()).to_string(), "0.000000000000000001"); + /// ``` pub const fn new(value: Int128) -> Self { Self(value) } /// Creates a SignedDecimal(Int128(value)) /// This is equivalent to `SignedDecimal::from_atomics(value, 18)` but usable in a const context. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// assert_eq!(SignedDecimal::raw(1234i128).to_string(), "0.000000000000001234"); + /// ``` pub const fn raw(value: i128) -> Self { Self(Int128::new(value)) } @@ -145,6 +175,16 @@ impl SignedDecimal { } /// Returns the ratio (numerator / denominator) as a SignedDecimal + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// assert_eq!( + /// SignedDecimal::from_ratio(1, 3).to_string(), + /// "0.333333333333333333" + /// ); + /// ``` pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { match SignedDecimal::checked_from_ratio(numerator, denominator) { Ok(value) => value, @@ -156,6 +196,20 @@ impl SignedDecimal { } /// Returns the ratio (numerator / denominator) as a SignedDecimal + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::{SignedDecimal, CheckedFromRatioError}; + /// assert_eq!( + /// SignedDecimal::checked_from_ratio(1, 3).unwrap().to_string(), + /// "0.333333333333333333" + /// ); + /// assert_eq!( + /// SignedDecimal::checked_from_ratio(1, 0), + /// Err(CheckedFromRatioError::DivideByZero) + /// ); + /// ``` pub fn checked_from_ratio( numerator: impl Into, denominator: impl Into, @@ -174,6 +228,7 @@ impl SignedDecimal { } } + /// Returns `true` if the number is 0 #[must_use] pub const fn is_zero(&self) -> bool { self.0.is_zero() @@ -220,12 +275,30 @@ impl SignedDecimal { } /// Rounds value by truncating the decimal places. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// # use core::str::FromStr; + /// assert!(SignedDecimal::from_str("0.6").unwrap().trunc().is_zero()); + /// assert_eq!(SignedDecimal::from_str("-5.8").unwrap().trunc().to_string(), "-5"); + /// ``` #[must_use = "this returns the result of the operation, without modifying the original"] pub fn trunc(&self) -> Self { Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) } /// Rounds value down after decimal places. Panics on overflow. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// # use core::str::FromStr; + /// assert!(SignedDecimal::from_str("0.6").unwrap().floor().is_zero()); + /// assert_eq!(SignedDecimal::from_str("-5.2").unwrap().floor().to_string(), "-6"); + /// ``` #[must_use = "this returns the result of the operation, without modifying the original"] pub fn floor(&self) -> Self { match self.checked_floor() { @@ -252,6 +325,15 @@ impl SignedDecimal { } /// Rounds value up after decimal places. Panics on overflow. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal; + /// # use core::str::FromStr; + /// assert_eq!(SignedDecimal::from_str("0.2").unwrap().ceil(), SignedDecimal::one()); + /// assert_eq!(SignedDecimal::from_str("-5.8").unwrap().ceil().to_string(), "-5"); + /// ``` #[must_use = "this returns the result of the operation, without modifying the original"] pub fn ceil(&self) -> Self { match self.checked_ceil() { @@ -272,6 +354,7 @@ impl SignedDecimal { } } + /// Computes `self + other`, returning an `OverflowError` if an overflow occurred. pub fn checked_add(self, other: Self) -> Result { self.0 .checked_add(other.0) @@ -279,6 +362,7 @@ impl SignedDecimal { .map_err(|_| OverflowError::new(OverflowOperation::Add, self, other)) } + /// Computes `self - other`, returning an `OverflowError` if an overflow occurred. pub fn checked_sub(self, other: Self) -> Result { self.0 .checked_sub(other.0) @@ -346,6 +430,7 @@ impl SignedDecimal { SignedDecimal::checked_from_ratio(self.numerator(), other.numerator()) } + /// Computes `self % other`, returning an `DivideByZeroError` if `other == 0`. pub fn checked_rem(self, other: Self) -> Result { self.0 .checked_rem(other.0) From f86d102e8e054ce8d72bd4bfb29f1189621577cc Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 11 Sep 2023 11:33:11 +0200 Subject: [PATCH 11/26] Fix SignedDecimal docs --- packages/std/src/math/signed_decimal.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index e390c77f12..6d377f4925 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -61,7 +61,7 @@ impl SignedDecimal { pub const MIN: Self = Self(Int128::MIN); /// Creates a SignedDecimal(value) - /// This is equivalent to `Decimal::from_atomics(value, 18)` but usable in a const context. + /// This is equivalent to `SignedDecimal::from_atomics(value, 18)` but usable in a const context. /// /// # Examples /// @@ -370,7 +370,7 @@ impl SignedDecimal { .map_err(|_| OverflowError::new(OverflowOperation::Sub, self, other)) } - /// Multiplies one `Decimal` by another, returning an `OverflowError` if an overflow occurred. + /// Multiplies one `SignedDecimal` by another, returning an `OverflowError` if an overflow occurred. pub fn checked_mul(self, other: Self) -> Result { let result_as_int256 = self.numerator().full_mul(other.numerator()) / Int256::from(Self::DECIMAL_FRACTIONAL); @@ -484,7 +484,7 @@ impl SignedDecimal { } } - /// Converts this decimal to an unsigned integer by rounding down + /// Converts this decimal to a signed integer by rounding down /// to the next integer, e.g. 22.5 becomes 22 and -1.2 becomes -2. /// /// ## Examples @@ -520,7 +520,7 @@ impl SignedDecimal { } } - /// Converts this decimal to an unsigned integer by truncating + /// Converts this decimal to a signed integer by truncating /// the fractional part, e.g. 22.5 becomes 22. /// /// ## Examples @@ -543,7 +543,7 @@ impl SignedDecimal { self.0 / Self::DECIMAL_FRACTIONAL } - /// Converts this decimal to an unsigned integer by rounding up + /// Converts this decimal to a signed integer by rounding up /// to the next integer, e.g. 22.3 becomes 23 and -1.2 becomes -1. /// /// ## Examples From eb99c42c829f1a82cdca4927374945870350fff3 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 11 Sep 2023 13:12:17 +0200 Subject: [PATCH 12/26] Add SignedDecimal256 --- packages/std/src/lib.rs | 4 +- packages/std/src/math/mod.rs | 2 + packages/std/src/math/signed_decimal_256.rs | 3178 +++++++++++++++++++ 3 files changed, 3182 insertions(+), 2 deletions(-) create mode 100644 packages/std/src/math/signed_decimal_256.rs diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 6c6454ffe2..dcf106b96e 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -61,8 +61,8 @@ pub use crate::ibc::{ pub use crate::iterator::{Order, Record}; pub use crate::math::{ Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Int128, Int256, - Int512, Int64, Isqrt, SignedDecimal, SignedDecimalRangeExceeded, Uint128, Uint256, Uint512, - Uint64, + Int512, Int64, Isqrt, SignedDecimal, SignedDecimal256, SignedDecimal256RangeExceeded, + SignedDecimalRangeExceeded, Uint128, Uint256, Uint512, Uint64, }; pub use crate::metadata::{DenomMetadata, DenomUnit}; pub use crate::never::Never; diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs index 5defdb142b..1e4527f214 100644 --- a/packages/std/src/math/mod.rs +++ b/packages/std/src/math/mod.rs @@ -9,6 +9,7 @@ mod int64; mod isqrt; mod num_consts; mod signed_decimal; +mod signed_decimal_256; mod uint128; mod uint256; mod uint512; @@ -23,6 +24,7 @@ pub use int512::Int512; pub use int64::Int64; pub use isqrt::Isqrt; pub use signed_decimal::{SignedDecimal, SignedDecimalRangeExceeded}; +pub use signed_decimal_256::{SignedDecimal256, SignedDecimal256RangeExceeded}; pub use uint128::Uint128; pub use uint256::Uint256; pub use uint512::Uint512; diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs new file mode 100644 index 0000000000..eafc3f53d3 --- /dev/null +++ b/packages/std/src/math/signed_decimal_256.rs @@ -0,0 +1,3178 @@ +use core::cmp::Ordering; +use core::fmt::{self, Write}; +use core::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; +use core::str::FromStr; +use forward_ref::{forward_ref_binop, forward_ref_op_assign}; +use schemars::JsonSchema; +use serde::{de, ser, Deserialize, Deserializer, Serialize}; +use thiserror::Error; + +use crate::errors::{ + CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, +}; +use crate::{forward_ref_partial_eq, Decimal256, Int512}; + +use super::Fraction; +use super::Int256; + +/// A signed fixed-point decimal value with 18 fractional digits, +/// i.e. SignedDecimal256(1_000_000_000_000_000_000) == 1.0 +/// +/// The greatest possible value that can be represented is +/// 57896044618658097711785492504343953926634992332820282019728.792003956564819967 +/// (which is (2^255 - 1) / 10^18) +/// and the smallest is +/// -57896044618658097711785492504343953926634992332820282019728.792003956564819968 +/// (which is -2^255 / 10^18). +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] +pub struct SignedDecimal256(#[schemars(with = "String")] Int256); + +forward_ref_partial_eq!(SignedDecimal256, SignedDecimal256); + +#[derive(Error, Debug, PartialEq, Eq)] +#[error("SignedDecimal256 range exceeded")] +pub struct SignedDecimal256RangeExceeded; + +impl SignedDecimal256 { + const DECIMAL_FRACTIONAL: Int256 = // 1*10**18 + Int256::from_be_bytes([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, + 179, 167, 100, 0, 0, + ]); + const DECIMAL_FRACTIONAL_SQUARED: Int256 = // 1*10**36 + Int256::from_be_bytes([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 151, 206, 123, 201, 7, 21, 179, + 75, 159, 16, 0, 0, 0, 0, + ]); + + /// The number of decimal places. Since decimal types are fixed-point rather than + /// floating-point, this is a constant. + pub const DECIMAL_PLACES: u32 = 18; // This needs to be an even number. + + /// The largest value that can be represented by this signed decimal type. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// assert_eq!( + /// SignedDecimal256::MAX.to_string(), + /// "57896044618658097711785492504343953926634992332820282019728.792003956564819967" + /// ); + /// ``` + pub const MAX: Self = Self(Int256::MAX); + + /// The smallest value that can be represented by this signed decimal type. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// assert_eq!( + /// SignedDecimal256::MIN.to_string(), + /// "-57896044618658097711785492504343953926634992332820282019728.792003956564819968" + /// ); + /// ``` + pub const MIN: Self = Self(Int256::MIN); + + /// Creates a SignedDecimal256(value) + /// This is equivalent to `SignedDecimal256::from_atomics(value, 18)` but usable in a const context. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::{SignedDecimal256, Int256}; + /// assert_eq!(SignedDecimal256::new(Int256::one()).to_string(), "0.000000000000000001"); + /// ``` + pub const fn new(value: Int256) -> Self { + Self(value) + } + + /// Creates a SignedDecimal256(Int256(value)) + /// This is equivalent to `SignedDecimal256::from_atomics(value, 18)` but usable in a const context. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// assert_eq!(SignedDecimal256::raw(1234i128).to_string(), "0.000000000000001234"); + /// ``` + pub const fn raw(value: i128) -> Self { + Self(Int256::from_i128(value)) + } + + /// Create a 1.0 SignedDecimal256 + #[inline] + pub const fn one() -> Self { + Self(Self::DECIMAL_FRACTIONAL) + } + + /// Create a -1.0 SignedDecimal256 + #[inline] + pub fn negative_one() -> Self { + Self(-Self::DECIMAL_FRACTIONAL) // TODO: constify + } + + /// Create a 0.0 SignedDecimal256 + #[inline] + pub const fn zero() -> Self { + Self(Int256::zero()) + } + + /// Convert x% into SignedDecimal256 + pub fn percent(x: i64) -> Self { + Self(((x as i128) * 10_000_000_000_000_000).into()) + } + + /// Convert permille (x/1000) into SignedDecimal256 + pub fn permille(x: i64) -> Self { + Self(((x as i128) * 1_000_000_000_000_000).into()) + } + + /// Convert basis points (x/10000) into SignedDecimal256 + pub fn bps(x: i64) -> Self { + Self(((x as i128) * 100_000_000_000_000).into()) + } + + /// Creates a signed decimal from a number of atomic units and the number + /// of decimal places. The inputs will be converted internally to form + /// a signed decimal with 18 decimal places. So the input 123 and 2 will create + /// the decimal 1.23. + /// + /// Using 18 decimal places is slightly more efficient than other values + /// as no internal conversion is necessary. + /// + /// ## Examples + /// + /// ``` + /// # use cosmwasm_std::{SignedDecimal256, Int256}; + /// let a = SignedDecimal256::from_atomics(Int256::from(1234), 3).unwrap(); + /// assert_eq!(a.to_string(), "1.234"); + /// + /// let a = SignedDecimal256::from_atomics(1234i128, 0).unwrap(); + /// assert_eq!(a.to_string(), "1234"); + /// + /// let a = SignedDecimal256::from_atomics(1i64, 18).unwrap(); + /// assert_eq!(a.to_string(), "0.000000000000000001"); + /// + /// let a = SignedDecimal256::from_atomics(-1i64, 18).unwrap(); + /// assert_eq!(a.to_string(), "-0.000000000000000001"); + /// ``` + pub fn from_atomics( + atomics: impl Into, + decimal_places: u32, + ) -> Result { + let atomics = atomics.into(); + let ten = Int256::from(10u64); + Ok(match decimal_places.cmp(&(Self::DECIMAL_PLACES)) { + Ordering::Less => { + let digits = (Self::DECIMAL_PLACES) - decimal_places; // No overflow because decimal_places < DECIMAL_PLACES + let factor = ten.checked_pow(digits).unwrap(); // Safe because digits <= 17 + Self( + atomics + .checked_mul(factor) + .map_err(|_| SignedDecimal256RangeExceeded)?, + ) + } + Ordering::Equal => Self(atomics), + Ordering::Greater => { + let digits = decimal_places - (Self::DECIMAL_PLACES); // No overflow because decimal_places > DECIMAL_PLACES + if let Ok(factor) = ten.checked_pow(digits) { + Self(atomics.checked_div(factor).unwrap()) // Safe because factor cannot be zero + } else { + // In this case `factor` exceeds the Int256 range. + // Any Int256 `x` divided by `factor` with `factor > Int256::MAX` is 0. + // Try e.g. Python3: `(2**128-1) // 2**128` + Self(Int256::zero()) + } + } + }) + } + + /// Returns the ratio (numerator / denominator) as a SignedDecimal256 + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// assert_eq!( + /// SignedDecimal256::from_ratio(1, 3).to_string(), + /// "0.333333333333333333" + /// ); + /// ``` + pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + match SignedDecimal256::checked_from_ratio(numerator, denominator) { + Ok(value) => value, + Err(CheckedFromRatioError::DivideByZero) => { + panic!("Denominator must not be zero") + } + Err(CheckedFromRatioError::Overflow) => panic!("Multiplication overflow"), + } + } + + /// Returns the ratio (numerator / denominator) as a SignedDecimal256 + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::{SignedDecimal256, CheckedFromRatioError}; + /// assert_eq!( + /// SignedDecimal256::checked_from_ratio(1, 3).unwrap().to_string(), + /// "0.333333333333333333" + /// ); + /// assert_eq!( + /// SignedDecimal256::checked_from_ratio(1, 0), + /// Err(CheckedFromRatioError::DivideByZero) + /// ); + /// ``` + pub fn checked_from_ratio( + numerator: impl Into, + denominator: impl Into, + ) -> Result { + let numerator: Int256 = numerator.into(); + let denominator: Int256 = denominator.into(); + match numerator.checked_multiply_ratio(Self::DECIMAL_FRACTIONAL, denominator) { + Ok(ratio) => { + // numerator * DECIMAL_FRACTIONAL / denominator + Ok(SignedDecimal256(ratio)) + } + Err(CheckedMultiplyRatioError::Overflow) => Err(CheckedFromRatioError::Overflow), + Err(CheckedMultiplyRatioError::DivideByZero) => { + Err(CheckedFromRatioError::DivideByZero) + } + } + } + + /// Returns `true` if the number is 0 + #[must_use] + pub const fn is_zero(&self) -> bool { + self.0.is_zero() + } + + /// Returns `true` if the number is negative (< 0) + #[must_use] + pub const fn is_negative(&self) -> bool { + self.0.is_negative() + } + + /// 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 cosmwasm_std::{SignedDecimal256, Int256}; + /// # use core::str::FromStr; + /// // Value with whole and fractional part + /// let a = SignedDecimal256::from_str("1.234").unwrap(); + /// assert_eq!(a.decimal_places(), 18); + /// assert_eq!(a.atomics(), Int256::from(1234000000000000000i128)); + /// + /// // Smallest possible value + /// let b = SignedDecimal256::from_str("0.000000000000000001").unwrap(); + /// assert_eq!(b.decimal_places(), 18); + /// assert_eq!(b.atomics(), Int256::from(1)); + /// ``` + #[must_use] + #[inline] + pub const fn atomics(&self) -> Int256 { + self.0 + } + + /// The number of decimal places. This is a constant value for now + /// but this could potentially change as the type evolves. + /// + /// See also [`SignedDecimal256::atomics()`]. + #[must_use] + #[inline] + pub const fn decimal_places(&self) -> u32 { + Self::DECIMAL_PLACES + } + + /// Rounds value by truncating the decimal places. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// # use core::str::FromStr; + /// assert!(SignedDecimal256::from_str("0.6").unwrap().trunc().is_zero()); + /// assert_eq!(SignedDecimal256::from_str("-5.8").unwrap().trunc().to_string(), "-5"); + /// ``` + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn trunc(&self) -> Self { + Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) + } + + /// Rounds value down after decimal places. Panics on overflow. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// # use core::str::FromStr; + /// assert!(SignedDecimal256::from_str("0.6").unwrap().floor().is_zero()); + /// assert_eq!(SignedDecimal256::from_str("-5.2").unwrap().floor().to_string(), "-6"); + /// ``` + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn floor(&self) -> Self { + match self.checked_floor() { + Ok(value) => value, + Err(_) => panic!("attempt to floor with overflow"), + } + } + + /// Rounds value down after decimal places. + pub fn checked_floor(&self) -> Result { + if self.is_negative() { + let truncated = self.trunc(); + + if truncated != self { + truncated + .checked_sub(SignedDecimal256::one()) + .map_err(|_| RoundDownOverflowError) + } else { + Ok(truncated) + } + } else { + Ok(self.trunc()) + } + } + + /// Rounds value up after decimal places. Panics on overflow. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::SignedDecimal256; + /// # use core::str::FromStr; + /// assert_eq!(SignedDecimal256::from_str("0.2").unwrap().ceil(), SignedDecimal256::one()); + /// assert_eq!(SignedDecimal256::from_str("-5.8").unwrap().ceil().to_string(), "-5"); + /// ``` + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn ceil(&self) -> Self { + match self.checked_ceil() { + Ok(value) => value, + Err(_) => panic!("attempt to ceil with overflow"), + } + } + + /// Rounds value up after decimal places. Returns OverflowError on overflow. + pub fn checked_ceil(&self) -> Result { + let floor = self.floor(); + if floor == self { + Ok(floor) + } else { + floor + .checked_add(SignedDecimal256::one()) + .map_err(|_| RoundUpOverflowError) + } + } + + /// Computes `self + other`, returning an `OverflowError` if an overflow occurred. + pub fn checked_add(self, other: Self) -> Result { + self.0 + .checked_add(other.0) + .map(Self) + .map_err(|_| OverflowError::new(OverflowOperation::Add, self, other)) + } + + /// Computes `self - other`, returning an `OverflowError` if an overflow occurred. + pub fn checked_sub(self, other: Self) -> Result { + self.0 + .checked_sub(other.0) + .map(Self) + .map_err(|_| OverflowError::new(OverflowOperation::Sub, self, other)) + } + + /// Multiplies one `SignedDecimal256` by another, returning an `OverflowError` if an overflow occurred. + pub fn checked_mul(self, other: Self) -> Result { + let result_as_int512 = + self.numerator().full_mul(other.numerator()) / Int512::from(Self::DECIMAL_FRACTIONAL); + result_as_int512 + .try_into() + .map(Self) + .map_err(|_| OverflowError { + operation: crate::OverflowOperation::Mul, + operand1: self.to_string(), + operand2: other.to_string(), + }) + } + + /// Raises a value to the power of `exp`, panics if an overflow occurred. + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn pow(self, exp: u32) -> Self { + match self.checked_pow(exp) { + Ok(value) => value, + Err(_) => panic!("Multiplication overflow"), + } + } + + /// Raises a value to the power of `exp`, returning an `OverflowError` if an overflow occurred. + pub fn checked_pow(self, exp: u32) -> Result { + // This uses the exponentiation by squaring algorithm: + // https://en.wikipedia.org/wiki/Exponentiation_by_squaring#Basic_method + + fn inner(mut x: SignedDecimal256, mut n: u32) -> Result { + if n == 0 { + return Ok(SignedDecimal256::one()); + } + + let mut y = SignedDecimal256::one(); + + while n > 1 { + if n % 2 == 0 { + x = x.checked_mul(x)?; + n /= 2; + } else { + y = x.checked_mul(y)?; + x = x.checked_mul(x)?; + n = (n - 1) / 2; + } + } + + Ok(x * y) + } + + inner(self, exp).map_err(|_| OverflowError { + operation: crate::OverflowOperation::Pow, + operand1: self.to_string(), + operand2: exp.to_string(), + }) + } + + pub fn checked_div(self, other: Self) -> Result { + SignedDecimal256::checked_from_ratio(self.numerator(), other.numerator()) + } + + /// Computes `self % other`, returning an `DivideByZeroError` if `other == 0`. + pub fn checked_rem(self, other: Self) -> Result { + self.0 + .checked_rem(other.0) + .map(Self) + .map_err(|_| DivideByZeroError::new(self)) + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub const fn abs_diff(self, other: Self) -> Decimal256 { + Decimal256::new(self.0.abs_diff(other.0)) + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_add(self, other: Self) -> Self { + Self(self.0.saturating_add(other.0)) + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_sub(self, other: Self) -> Self { + Self(self.0.saturating_sub(other.0)) + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_mul(self, other: Self) -> Self { + match self.checked_mul(other) { + Ok(value) => value, + Err(_) => { + // both negative or both positive results in positive number, otherwise negative + if self.is_negative() == other.is_negative() { + Self::MAX + } else { + Self::MIN + } + } + } + } + + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn saturating_pow(self, exp: u32) -> Self { + match self.checked_pow(exp) { + Ok(value) => value, + Err(_) => { + // odd exponent of negative number results in negative number + // everything else results in positive number + if self.is_negative() && exp % 2 == 1 { + Self::MIN + } else { + Self::MAX + } + } + } + } + + /// Converts this decimal to a signed integer by rounding down + /// to the next integer, e.g. 22.5 becomes 22 and -1.2 becomes -2. + /// + /// ## Examples + /// + /// ``` + /// use core::str::FromStr; + /// use cosmwasm_std::{SignedDecimal256, Int256}; + /// + /// let d = SignedDecimal256::from_str("12.345").unwrap(); + /// assert_eq!(d.to_int_floor(), Int256::from(12)); + /// + /// let d = SignedDecimal256::from_str("-12.999").unwrap(); + /// assert_eq!(d.to_int_floor(), Int256::from(-13)); + /// + /// let d = SignedDecimal256::from_str("-0.05").unwrap(); + /// assert_eq!(d.to_int_floor(), Int256::from(-1)); + /// ``` + #[must_use] + pub fn to_int_floor(self) -> Int256 { + if self.is_negative() { + // Using `x.to_int_floor() = -(-x).to_int_ceil()` for a negative `x`, + // but avoiding overflow by implementing the formula from `to_int_ceil` directly. + let x = self.0; + let y = Self::DECIMAL_FRACTIONAL; + if x.is_zero() { + Int256::zero() + } else { + // making sure not to negate `x`, as this would overflow + -Int256::one() - ((-Int256::one() - x) / y) + } + } else { + self.to_int_trunc() + } + } + + /// Converts this decimal to a signed integer by truncating + /// the fractional part, e.g. 22.5 becomes 22. + /// + /// ## Examples + /// + /// ``` + /// use core::str::FromStr; + /// use cosmwasm_std::{SignedDecimal256, Int256}; + /// + /// let d = SignedDecimal256::from_str("12.345").unwrap(); + /// assert_eq!(d.to_int_trunc(), Int256::from(12)); + /// + /// let d = SignedDecimal256::from_str("-12.999").unwrap(); + /// assert_eq!(d.to_int_trunc(), Int256::from(-12)); + /// + /// let d = SignedDecimal256::from_str("75.0").unwrap(); + /// assert_eq!(d.to_int_trunc(), Int256::from(75)); + /// ``` + #[must_use] + pub fn to_int_trunc(self) -> Int256 { + self.0 / Self::DECIMAL_FRACTIONAL + } + + /// Converts this decimal to a signed integer by rounding up + /// to the next integer, e.g. 22.3 becomes 23 and -1.2 becomes -1. + /// + /// ## Examples + /// + /// ``` + /// use core::str::FromStr; + /// use cosmwasm_std::{SignedDecimal256, Int256}; + /// + /// let d = SignedDecimal256::from_str("12.345").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int256::from(13)); + /// + /// let d = SignedDecimal256::from_str("-12.999").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int256::from(-12)); + /// + /// let d = SignedDecimal256::from_str("75.0").unwrap(); + /// assert_eq!(d.to_int_ceil(), Int256::from(75)); + /// ``` + #[must_use] + pub fn to_int_ceil(self) -> Int256 { + if self.is_negative() { + self.to_int_trunc() + } else { + // Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q + // from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow. + let x = self.0; + let y = Self::DECIMAL_FRACTIONAL; + if x.is_zero() { + Int256::zero() + } else { + Int256::one() + ((x - Int256::one()) / y) + } + } + } +} + +impl Fraction for SignedDecimal256 { + #[inline] + fn numerator(&self) -> Int256 { + self.0 + } + + #[inline] + fn denominator(&self) -> Int256 { + Self::DECIMAL_FRACTIONAL + } + + /// Returns the multiplicative inverse `1/d` for decimal `d`. + /// + /// If `d` is zero, none is returned. + 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(SignedDecimal256(Self::DECIMAL_FRACTIONAL_SQUARED / self.0)) + } + } +} + +impl Neg for SignedDecimal256 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +impl FromStr for SignedDecimal256 { + type Err = StdError; + + /// Converts the decimal string to a SignedDecimal256 + /// Possible inputs: "1.23", "1", "000012", "1.123000000", "-1.12300" + /// Disallowed: "", ".23" + /// + /// This never performs any kind of rounding. + /// More than DECIMAL_PLACES fractional digits, even zeros, result in an error. + fn from_str(input: &str) -> Result { + let mut parts_iter = input.split('.'); + + let whole_part = parts_iter.next().unwrap(); // split always returns at least one element + let is_neg = whole_part.starts_with('-'); + + let whole = whole_part + .parse::() + .map_err(|_| StdError::generic_err("Error parsing whole"))?; + let mut atomics = whole + .checked_mul(Self::DECIMAL_FRACTIONAL) + .map_err(|_| StdError::generic_err("Value too big"))?; + + if let Some(fractional_part) = parts_iter.next() { + let fractional = fractional_part + .parse::() // u64 is enough for 18 decimal places + .map_err(|_| StdError::generic_err("Error parsing fractional"))?; + let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( + || { + StdError::generic_err(format!( + "Cannot parse more than {} fractional digits", + Self::DECIMAL_PLACES + )) + }, + )?; + debug_assert!(exp <= Self::DECIMAL_PLACES); + let fractional_factor = Int256::from(10i128.pow(exp)); + + // This multiplication can't overflow because + // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES + let fractional_part = Int256::from(fractional) + .checked_mul(fractional_factor) + .unwrap(); + + // for negative numbers, we need to subtract the fractional part + atomics = if is_neg { + atomics.checked_sub(fractional_part) + } else { + atomics.checked_add(fractional_part) + } + .map_err(|_| StdError::generic_err("Value too big"))?; + } + + if parts_iter.next().is_some() { + return Err(StdError::generic_err("Unexpected number of dots")); + } + + Ok(SignedDecimal256(atomics)) + } +} + +impl fmt::Display for SignedDecimal256 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let whole = (self.0) / Self::DECIMAL_FRACTIONAL; + let fractional = (self.0).checked_rem(Self::DECIMAL_FRACTIONAL).unwrap(); + + if fractional.is_zero() { + write!(f, "{whole}") + } else { + let fractional_string = format!( + "{:0>padding$}", + fractional.abs(), // fractional should always be printed as positive + padding = Self::DECIMAL_PLACES as usize + ); + if self.is_negative() { + f.write_char('-')?; + } + write!( + f, + "{whole}.{fractional}", + whole = whole.abs(), + fractional = fractional_string.trim_end_matches('0') + ) + } + } +} + +impl fmt::Debug for SignedDecimal256 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "SignedDecimal256({self})") + } +} + +impl Add for SignedDecimal256 { + type Output = Self; + + fn add(self, other: Self) -> Self { + SignedDecimal256(self.0 + other.0) + } +} +forward_ref_binop!(impl Add, add for SignedDecimal256, SignedDecimal256); + +impl AddAssign for SignedDecimal256 { + fn add_assign(&mut self, rhs: SignedDecimal256) { + *self = *self + rhs; + } +} +forward_ref_op_assign!(impl AddAssign, add_assign for SignedDecimal256, SignedDecimal256); + +impl Sub for SignedDecimal256 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + SignedDecimal256(self.0 - other.0) + } +} +forward_ref_binop!(impl Sub, sub for SignedDecimal256, SignedDecimal256); + +impl SubAssign for SignedDecimal256 { + fn sub_assign(&mut self, rhs: SignedDecimal256) { + *self = *self - rhs; + } +} +forward_ref_op_assign!(impl SubAssign, sub_assign for SignedDecimal256, SignedDecimal256); + +impl Mul for SignedDecimal256 { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn mul(self, other: Self) -> Self { + // SignedDecimal256s 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 result_as_int512 = + self.numerator().full_mul(other.numerator()) / Int512::from(Self::DECIMAL_FRACTIONAL); + match result_as_int512.try_into() { + Ok(result) => Self(result), + Err(_) => panic!("attempt to multiply with overflow"), + } + } +} +forward_ref_binop!(impl Mul, mul for SignedDecimal256, SignedDecimal256); + +impl MulAssign for SignedDecimal256 { + fn mul_assign(&mut self, rhs: SignedDecimal256) { + *self = *self * rhs; + } +} +forward_ref_op_assign!(impl MulAssign, mul_assign for SignedDecimal256, SignedDecimal256); + +impl Div for SignedDecimal256 { + type Output = Self; + + fn div(self, other: Self) -> Self { + match SignedDecimal256::checked_from_ratio(self.numerator(), other.numerator()) { + Ok(ratio) => ratio, + Err(CheckedFromRatioError::DivideByZero) => { + panic!("Division failed - denominator must not be zero") + } + Err(CheckedFromRatioError::Overflow) => { + panic!("Division failed - multiplication overflow") + } + } + } +} +forward_ref_binop!(impl Div, div for SignedDecimal256, SignedDecimal256); + +impl DivAssign for SignedDecimal256 { + fn div_assign(&mut self, rhs: SignedDecimal256) { + *self = *self / rhs; + } +} +forward_ref_op_assign!(impl DivAssign, div_assign for SignedDecimal256, SignedDecimal256); + +impl Div for SignedDecimal256 { + type Output = Self; + + fn div(self, rhs: Int256) -> Self::Output { + SignedDecimal256(self.0 / rhs) + } +} + +impl DivAssign for SignedDecimal256 { + fn div_assign(&mut self, rhs: Int256) { + self.0 /= rhs; + } +} + +impl Rem for SignedDecimal256 { + type Output = Self; + + /// # Panics + /// + /// This operation will panic if `rhs` is zero + #[inline] + fn rem(self, rhs: Self) -> Self { + Self(self.0.rem(rhs.0)) + } +} +forward_ref_binop!(impl Rem, rem for SignedDecimal256, SignedDecimal256); + +impl RemAssign for SignedDecimal256 { + fn rem_assign(&mut self, rhs: SignedDecimal256) { + *self = *self % rhs; + } +} +forward_ref_op_assign!(impl RemAssign, rem_assign for SignedDecimal256, SignedDecimal256); + +impl core::iter::Sum for SignedDecimal256 +where + Self: Add, +{ + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +/// Serializes as a decimal string +impl Serialize for SignedDecimal256 { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +/// Deserializes as a base64 string +impl<'de> Deserialize<'de> for SignedDecimal256 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(SignedDecimal256Visitor) + } +} + +struct SignedDecimal256Visitor; + +impl<'de> de::Visitor<'de> for SignedDecimal256Visitor { + type Value = SignedDecimal256; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string-encoded decimal") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + match SignedDecimal256::from_str(v) { + Ok(d) => Ok(d), + Err(e) => Err(E::custom(format!("Error parsing decimal '{v}': {e}"))), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{from_slice, to_vec}; + + fn dec(input: &str) -> SignedDecimal256 { + SignedDecimal256::from_str(input).unwrap() + } + + #[test] + fn signed_decimal_256_new() { + let expected = Int256::from(300i128); + assert_eq!(SignedDecimal256::new(expected).0, expected); + + let expected = Int256::from(-300i128); + assert_eq!(SignedDecimal256::new(expected).0, expected); + } + + #[test] + fn signed_decimal_256_raw() { + let value = 300i128; + assert_eq!(SignedDecimal256::raw(value).0, Int256::from(value)); + + let value = -300i128; + assert_eq!(SignedDecimal256::raw(value).0, Int256::from(value)); + } + + #[test] + fn signed_decimal_256_one() { + let value = SignedDecimal256::one(); + assert_eq!(value.0, SignedDecimal256::DECIMAL_FRACTIONAL); + } + + #[test] + fn signed_decimal_256_zero() { + let value = SignedDecimal256::zero(); + assert!(value.0.is_zero()); + } + + #[test] + fn signed_decimal_256_percent() { + let value = SignedDecimal256::percent(50); + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(2u8) + ); + + let value = SignedDecimal256::percent(-50); + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(-2i8) + ); + } + + #[test] + fn signed_decimal_256_permille() { + let value = SignedDecimal256::permille(125); + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(8u8) + ); + + let value = SignedDecimal256::permille(-125); + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(-8i8) + ); + } + + #[test] + fn signed_decimal_256_bps() { + let value = SignedDecimal256::bps(125); + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(80u8) + ); + + let value = SignedDecimal256::bps(-125); + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(-80i8) + ); + } + + #[test] + fn signed_decimal_256_from_atomics_works() { + let one = SignedDecimal256::one(); + let two = one + one; + let neg_one = SignedDecimal256::negative_one(); + + assert_eq!(SignedDecimal256::from_atomics(1i128, 0).unwrap(), one); + assert_eq!(SignedDecimal256::from_atomics(10i128, 1).unwrap(), one); + assert_eq!(SignedDecimal256::from_atomics(100i128, 2).unwrap(), one); + assert_eq!(SignedDecimal256::from_atomics(1000i128, 3).unwrap(), one); + assert_eq!( + SignedDecimal256::from_atomics(1000000000000000000i128, 18).unwrap(), + one + ); + assert_eq!( + SignedDecimal256::from_atomics(10000000000000000000i128, 19).unwrap(), + one + ); + assert_eq!( + SignedDecimal256::from_atomics(100000000000000000000i128, 20).unwrap(), + one + ); + + assert_eq!(SignedDecimal256::from_atomics(2i128, 0).unwrap(), two); + assert_eq!(SignedDecimal256::from_atomics(20i128, 1).unwrap(), two); + assert_eq!(SignedDecimal256::from_atomics(200i128, 2).unwrap(), two); + assert_eq!(SignedDecimal256::from_atomics(2000i128, 3).unwrap(), two); + assert_eq!( + SignedDecimal256::from_atomics(2000000000000000000i128, 18).unwrap(), + two + ); + assert_eq!( + SignedDecimal256::from_atomics(20000000000000000000i128, 19).unwrap(), + two + ); + assert_eq!( + SignedDecimal256::from_atomics(200000000000000000000i128, 20).unwrap(), + two + ); + + assert_eq!(SignedDecimal256::from_atomics(-1i128, 0).unwrap(), neg_one); + assert_eq!(SignedDecimal256::from_atomics(-10i128, 1).unwrap(), neg_one); + assert_eq!( + SignedDecimal256::from_atomics(-100000000000000000000i128, 20).unwrap(), + neg_one + ); + + // Cuts decimal digits (20 provided but only 18 can be stored) + assert_eq!( + SignedDecimal256::from_atomics(4321i128, 20).unwrap(), + SignedDecimal256::from_str("0.000000000000000043").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(-4321i128, 20).unwrap(), + SignedDecimal256::from_str("-0.000000000000000043").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(6789i128, 20).unwrap(), + SignedDecimal256::from_str("0.000000000000000067").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, 38).unwrap(), + SignedDecimal256::from_str("1.701411834604692317").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, 39).unwrap(), + SignedDecimal256::from_str("0.170141183460469231").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, 45).unwrap(), + SignedDecimal256::from_str("0.000000170141183460").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, 51).unwrap(), + SignedDecimal256::from_str("0.000000000000170141").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, 56).unwrap(), + SignedDecimal256::from_str("0.000000000000000001").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, 57).unwrap(), + SignedDecimal256::from_str("0.000000000000000000").unwrap() + ); + assert_eq!( + SignedDecimal256::from_atomics(i128::MAX, u32::MAX).unwrap(), + SignedDecimal256::from_str("0.000000000000000000").unwrap() + ); + + // Can be used with max value + let max = SignedDecimal256::MAX; + assert_eq!( + SignedDecimal256::from_atomics(max.atomics(), max.decimal_places()).unwrap(), + max + ); + + // Can be used with min value + let min = SignedDecimal256::MIN; + assert_eq!( + SignedDecimal256::from_atomics(min.atomics(), min.decimal_places()).unwrap(), + min + ); + + // Overflow is only possible with digits < 18 + let result = SignedDecimal256::from_atomics(Int256::MAX, 17); + assert_eq!(result.unwrap_err(), SignedDecimal256RangeExceeded); + } + + #[test] + fn signed_decimal_256_from_ratio_works() { + // 1.0 + assert_eq!( + SignedDecimal256::from_ratio(1i128, 1i128), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::from_ratio(53i128, 53i128), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::from_ratio(125i128, 125i128), + SignedDecimal256::one() + ); + + // -1.0 + assert_eq!( + SignedDecimal256::from_ratio(-1i128, 1i128), + SignedDecimal256::negative_one() + ); + assert_eq!( + SignedDecimal256::from_ratio(-53i128, 53i128), + SignedDecimal256::negative_one() + ); + assert_eq!( + SignedDecimal256::from_ratio(125i128, -125i128), + SignedDecimal256::negative_one() + ); + + // 1.5 + assert_eq!( + SignedDecimal256::from_ratio(3i128, 2i128), + SignedDecimal256::percent(150) + ); + assert_eq!( + SignedDecimal256::from_ratio(150i128, 100i128), + SignedDecimal256::percent(150) + ); + assert_eq!( + SignedDecimal256::from_ratio(333i128, 222i128), + SignedDecimal256::percent(150) + ); + + // 0.125 + assert_eq!( + SignedDecimal256::from_ratio(1i64, 8i64), + SignedDecimal256::permille(125) + ); + assert_eq!( + SignedDecimal256::from_ratio(125i64, 1000i64), + SignedDecimal256::permille(125) + ); + + // -0.125 + assert_eq!( + SignedDecimal256::from_ratio(-1i64, 8i64), + SignedDecimal256::permille(-125) + ); + assert_eq!( + SignedDecimal256::from_ratio(125i64, -1000i64), + SignedDecimal256::permille(-125) + ); + + // 1/3 (result floored) + assert_eq!( + SignedDecimal256::from_ratio(1i64, 3i64), + SignedDecimal256(Int256::from(333_333_333_333_333_333i128)) + ); + + // 2/3 (result floored) + assert_eq!( + SignedDecimal256::from_ratio(2i64, 3i64), + SignedDecimal256(Int256::from(666_666_666_666_666_666i128)) + ); + + // large inputs + assert_eq!( + SignedDecimal256::from_ratio(0i128, i128::MAX), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256::from_ratio(i128::MAX, i128::MAX), + SignedDecimal256::one() + ); + // 170141183460469231731 is the largest integer <= SignedDecimal256::MAX + assert_eq!( + SignedDecimal256::from_ratio(170141183460469231731i128, 1i128), + SignedDecimal256::from_str("170141183460469231731").unwrap() + ); + } + + #[test] + #[should_panic(expected = "Denominator must not be zero")] + fn signed_decimal_256_from_ratio_panics_for_zero_denominator() { + SignedDecimal256::from_ratio(1i128, 0i128); + } + + #[test] + #[should_panic(expected = "Multiplication overflow")] + fn signed_decimal_256_from_ratio_panics_for_mul_overflow() { + SignedDecimal256::from_ratio(Int256::MAX, 1i128); + } + + #[test] + fn signed_decimal_256_checked_from_ratio_does_not_panic() { + assert_eq!( + SignedDecimal256::checked_from_ratio(1i128, 0i128), + Err(CheckedFromRatioError::DivideByZero) + ); + + assert_eq!( + SignedDecimal256::checked_from_ratio(Int256::MAX, 1i128), + Err(CheckedFromRatioError::Overflow) + ); + } + + #[test] + fn signed_decimal_256_implements_fraction() { + let fraction = SignedDecimal256::from_str("1234.567").unwrap(); + assert_eq!( + fraction.numerator(), + Int256::from(1_234_567_000_000_000_000_000i128) + ); + assert_eq!( + fraction.denominator(), + Int256::from(1_000_000_000_000_000_000i128) + ); + + let fraction = SignedDecimal256::from_str("-1234.567").unwrap(); + assert_eq!( + fraction.numerator(), + Int256::from(-1_234_567_000_000_000_000_000i128) + ); + assert_eq!( + fraction.denominator(), + Int256::from(1_000_000_000_000_000_000i128) + ); + } + + #[test] + fn signed_decimal_256_from_str_works() { + // Integers + assert_eq!( + SignedDecimal256::from_str("0").unwrap(), + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::from_str("1").unwrap(), + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::from_str("5").unwrap(), + SignedDecimal256::percent(500) + ); + assert_eq!( + SignedDecimal256::from_str("42").unwrap(), + SignedDecimal256::percent(4200) + ); + assert_eq!( + SignedDecimal256::from_str("000").unwrap(), + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::from_str("001").unwrap(), + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::from_str("005").unwrap(), + SignedDecimal256::percent(500) + ); + assert_eq!( + SignedDecimal256::from_str("0042").unwrap(), + SignedDecimal256::percent(4200) + ); + + // Positive decimals + assert_eq!( + SignedDecimal256::from_str("1.0").unwrap(), + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::from_str("1.5").unwrap(), + SignedDecimal256::percent(150) + ); + assert_eq!( + SignedDecimal256::from_str("0.5").unwrap(), + SignedDecimal256::percent(50) + ); + assert_eq!( + SignedDecimal256::from_str("0.123").unwrap(), + SignedDecimal256::permille(123) + ); + + assert_eq!( + SignedDecimal256::from_str("40.00").unwrap(), + SignedDecimal256::percent(4000) + ); + assert_eq!( + SignedDecimal256::from_str("04.00").unwrap(), + SignedDecimal256::percent(400) + ); + assert_eq!( + SignedDecimal256::from_str("00.40").unwrap(), + SignedDecimal256::percent(40) + ); + assert_eq!( + SignedDecimal256::from_str("00.04").unwrap(), + SignedDecimal256::percent(4) + ); + // Negative decimals + assert_eq!( + SignedDecimal256::from_str("-00.04").unwrap(), + SignedDecimal256::percent(-4) + ); + assert_eq!( + SignedDecimal256::from_str("-00.40").unwrap(), + SignedDecimal256::percent(-40) + ); + assert_eq!( + SignedDecimal256::from_str("-04.00").unwrap(), + SignedDecimal256::percent(-400) + ); + + // Can handle DECIMAL_PLACES fractional digits + assert_eq!( + SignedDecimal256::from_str("7.123456789012345678").unwrap(), + SignedDecimal256(Int256::from(7123456789012345678i128)) + ); + assert_eq!( + SignedDecimal256::from_str("7.999999999999999999").unwrap(), + SignedDecimal256(Int256::from(7999999999999999999i128)) + ); + + // Works for documented max value + assert_eq!( + SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019728.792003956564819967" + ) + .unwrap(), + SignedDecimal256::MAX + ); + // Works for documented min value + assert_eq!( + SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019728.792003956564819968" + ) + .unwrap(), + SignedDecimal256::MIN + ); + assert_eq!( + SignedDecimal256::from_str("-1").unwrap(), + SignedDecimal256::negative_one() + ); + } + + #[test] + fn signed_decimal_256_from_str_errors_for_broken_whole_part() { + match SignedDecimal256::from_str("").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str(" ").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str("-").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn signed_decimal_256_from_str_errors_for_broken_fractinal_part() { + match SignedDecimal256::from_str("1.").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str("1. ").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str("1.e").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str("1.2e3").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str("1.-2").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn signed_decimal_256_from_str_errors_for_more_than_18_fractional_digits() { + match SignedDecimal256::from_str("7.1234567890123456789").unwrap_err() { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "Cannot parse more than 18 fractional digits",) + } + e => panic!("Unexpected error: {e:?}"), + } + + // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. + match SignedDecimal256::from_str("7.1230000000000000000").unwrap_err() { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "Cannot parse more than 18 fractional digits") + } + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn signed_decimal_256_from_str_errors_for_invalid_number_of_dots() { + match SignedDecimal256::from_str("1.2.3").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), + e => panic!("Unexpected error: {e:?}"), + } + + match SignedDecimal256::from_str("1.2.3.4").unwrap_err() { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn signed_decimal_256_from_str_errors_for_more_than_max_value() { + // Integer + match SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019729", + ) + .unwrap_err() + { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + match SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019729", + ) + .unwrap_err() + { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + + // SignedDecimal256 + match SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019729.0", + ) + .unwrap_err() + { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + match SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019728.792003956564819968", + ) + .unwrap_err() + { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + match SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019728.792003956564819969", + ) + .unwrap_err() + { + StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn signed_decimal_256_atomics_works() { + let zero = SignedDecimal256::zero(); + let one = SignedDecimal256::one(); + let half = SignedDecimal256::percent(50); + let two = SignedDecimal256::percent(200); + let max = SignedDecimal256::MAX; + let neg_half = SignedDecimal256::percent(-50); + let neg_two = SignedDecimal256::percent(-200); + let min = SignedDecimal256::MIN; + + assert_eq!(zero.atomics(), Int256::from(0)); + assert_eq!(one.atomics(), Int256::from(1000000000000000000i128)); + assert_eq!(half.atomics(), Int256::from(500000000000000000i128)); + assert_eq!(two.atomics(), Int256::from(2000000000000000000i128)); + assert_eq!(max.atomics(), Int256::MAX); + assert_eq!(neg_half.atomics(), Int256::from(-500000000000000000i128)); + assert_eq!(neg_two.atomics(), Int256::from(-2000000000000000000i128)); + assert_eq!(min.atomics(), Int256::MIN); + } + + #[test] + fn signed_decimal_256_decimal_places_works() { + let zero = SignedDecimal256::zero(); + let one = SignedDecimal256::one(); + let half = SignedDecimal256::percent(50); + let two = SignedDecimal256::percent(200); + let max = SignedDecimal256::MAX; + let neg_one = SignedDecimal256::negative_one(); + + 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); + assert_eq!(neg_one.decimal_places(), 18); + } + + #[test] + fn signed_decimal_256_is_zero_works() { + assert!(SignedDecimal256::zero().is_zero()); + assert!(SignedDecimal256::percent(0).is_zero()); + assert!(SignedDecimal256::permille(0).is_zero()); + + assert!(!SignedDecimal256::one().is_zero()); + assert!(!SignedDecimal256::percent(123).is_zero()); + assert!(!SignedDecimal256::permille(-1234).is_zero()); + } + + #[test] + fn signed_decimal_256_inv_works() { + // d = 0 + assert_eq!(SignedDecimal256::zero().inv(), None); + + // d == 1 + assert_eq!(SignedDecimal256::one().inv(), Some(SignedDecimal256::one())); + + // d == -1 + assert_eq!( + SignedDecimal256::negative_one().inv(), + Some(SignedDecimal256::negative_one()) + ); + + // d > 1 exact + assert_eq!( + SignedDecimal256::from_str("2").unwrap().inv(), + Some(SignedDecimal256::from_str("0.5").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("20").unwrap().inv(), + Some(SignedDecimal256::from_str("0.05").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("200").unwrap().inv(), + Some(SignedDecimal256::from_str("0.005").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("2000").unwrap().inv(), + Some(SignedDecimal256::from_str("0.0005").unwrap()) + ); + + // d > 1 rounded + assert_eq!( + SignedDecimal256::from_str("3").unwrap().inv(), + Some(SignedDecimal256::from_str("0.333333333333333333").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("6").unwrap().inv(), + Some(SignedDecimal256::from_str("0.166666666666666666").unwrap()) + ); + + // d < 1 exact + assert_eq!( + SignedDecimal256::from_str("0.5").unwrap().inv(), + Some(SignedDecimal256::from_str("2").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("0.05").unwrap().inv(), + Some(SignedDecimal256::from_str("20").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("0.005").unwrap().inv(), + Some(SignedDecimal256::from_str("200").unwrap()) + ); + assert_eq!( + SignedDecimal256::from_str("0.0005").unwrap().inv(), + Some(SignedDecimal256::from_str("2000").unwrap()) + ); + + // d < 0 + assert_eq!( + SignedDecimal256::from_str("-0.5").unwrap().inv(), + Some(SignedDecimal256::from_str("-2").unwrap()) + ); + // d < 0 rounded + assert_eq!( + SignedDecimal256::from_str("-3").unwrap().inv(), + Some(SignedDecimal256::from_str("-0.333333333333333333").unwrap()) + ); + } + + #[test] + #[allow(clippy::op_ref)] + fn signed_decimal_256_add_works() { + let value = SignedDecimal256::one() + SignedDecimal256::percent(50); // 1.5 + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL * Int256::from(3u8) / Int256::from(2u8) + ); + + assert_eq!( + SignedDecimal256::percent(5) + SignedDecimal256::percent(4), + SignedDecimal256::percent(9) + ); + assert_eq!( + SignedDecimal256::percent(5) + SignedDecimal256::zero(), + SignedDecimal256::percent(5) + ); + assert_eq!( + SignedDecimal256::zero() + SignedDecimal256::zero(), + SignedDecimal256::zero() + ); + // negative numbers + assert_eq!( + SignedDecimal256::percent(-5) + SignedDecimal256::percent(-4), + SignedDecimal256::percent(-9) + ); + assert_eq!( + SignedDecimal256::percent(-5) + SignedDecimal256::percent(4), + SignedDecimal256::percent(-1) + ); + assert_eq!( + SignedDecimal256::percent(5) + SignedDecimal256::percent(-4), + SignedDecimal256::percent(1) + ); + + // works for refs + let a = SignedDecimal256::percent(15); + let b = SignedDecimal256::percent(25); + let expected = SignedDecimal256::percent(40); + assert_eq!(a + b, expected); + assert_eq!(&a + b, expected); + assert_eq!(a + &b, expected); + assert_eq!(&a + &b, expected); + } + + #[test] + #[should_panic] + fn signed_decimal_256_add_overflow_panics() { + let _value = SignedDecimal256::MAX + SignedDecimal256::percent(50); + } + + #[test] + fn signed_decimal_256_add_assign_works() { + let mut a = SignedDecimal256::percent(30); + a += SignedDecimal256::percent(20); + assert_eq!(a, SignedDecimal256::percent(50)); + + // works for refs + let mut a = SignedDecimal256::percent(15); + let b = SignedDecimal256::percent(3); + let expected = SignedDecimal256::percent(18); + a += &b; + assert_eq!(a, expected); + } + + #[test] + #[allow(clippy::op_ref)] + fn signed_decimal_256_sub_works() { + let value = SignedDecimal256::one() - SignedDecimal256::percent(50); // 0.5 + assert_eq!( + value.0, + SignedDecimal256::DECIMAL_FRACTIONAL / Int256::from(2u8) + ); + + assert_eq!( + SignedDecimal256::percent(9) - SignedDecimal256::percent(4), + SignedDecimal256::percent(5) + ); + assert_eq!( + SignedDecimal256::percent(16) - SignedDecimal256::zero(), + SignedDecimal256::percent(16) + ); + assert_eq!( + SignedDecimal256::percent(16) - SignedDecimal256::percent(16), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256::zero() - SignedDecimal256::zero(), + SignedDecimal256::zero() + ); + + // negative numbers + assert_eq!( + SignedDecimal256::percent(-5) - SignedDecimal256::percent(-4), + SignedDecimal256::percent(-1) + ); + assert_eq!( + SignedDecimal256::percent(-5) - SignedDecimal256::percent(4), + SignedDecimal256::percent(-9) + ); + assert_eq!( + SignedDecimal256::percent(500) - SignedDecimal256::percent(-4), + SignedDecimal256::percent(504) + ); + + // works for refs + let a = SignedDecimal256::percent(13); + let b = SignedDecimal256::percent(6); + let expected = SignedDecimal256::percent(7); + assert_eq!(a - b, expected); + assert_eq!(&a - b, expected); + assert_eq!(a - &b, expected); + assert_eq!(&a - &b, expected); + } + + #[test] + #[should_panic] + fn signed_decimal_256_sub_overflow_panics() { + let _value = SignedDecimal256::MIN - SignedDecimal256::percent(50); + } + + #[test] + fn signed_decimal_256_sub_assign_works() { + let mut a = SignedDecimal256::percent(20); + a -= SignedDecimal256::percent(2); + assert_eq!(a, SignedDecimal256::percent(18)); + + // works for refs + let mut a = SignedDecimal256::percent(33); + let b = SignedDecimal256::percent(13); + let expected = SignedDecimal256::percent(20); + a -= &b; + assert_eq!(a, expected); + } + + #[test] + #[allow(clippy::op_ref)] + fn signed_decimal_256_implements_mul() { + let one = SignedDecimal256::one(); + let two = one + one; + let half = SignedDecimal256::percent(50); + + // 1*x and x*1 + assert_eq!( + one * SignedDecimal256::percent(0), + SignedDecimal256::percent(0) + ); + assert_eq!( + one * SignedDecimal256::percent(1), + SignedDecimal256::percent(1) + ); + assert_eq!( + one * SignedDecimal256::percent(10), + SignedDecimal256::percent(10) + ); + assert_eq!( + one * SignedDecimal256::percent(100), + SignedDecimal256::percent(100) + ); + assert_eq!( + one * SignedDecimal256::percent(1000), + SignedDecimal256::percent(1000) + ); + assert_eq!(one * SignedDecimal256::MAX, SignedDecimal256::MAX); + assert_eq!( + SignedDecimal256::percent(0) * one, + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::percent(1) * one, + SignedDecimal256::percent(1) + ); + assert_eq!( + SignedDecimal256::percent(10) * one, + SignedDecimal256::percent(10) + ); + assert_eq!( + SignedDecimal256::percent(100) * one, + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::percent(1000) * one, + SignedDecimal256::percent(1000) + ); + assert_eq!(SignedDecimal256::MAX * one, SignedDecimal256::MAX); + assert_eq!( + SignedDecimal256::percent(-1) * one, + SignedDecimal256::percent(-1) + ); + assert_eq!( + one * SignedDecimal256::percent(-10), + SignedDecimal256::percent(-10) + ); + + // double + assert_eq!( + two * SignedDecimal256::percent(0), + SignedDecimal256::percent(0) + ); + assert_eq!( + two * SignedDecimal256::percent(1), + SignedDecimal256::percent(2) + ); + assert_eq!( + two * SignedDecimal256::percent(10), + SignedDecimal256::percent(20) + ); + assert_eq!( + two * SignedDecimal256::percent(100), + SignedDecimal256::percent(200) + ); + assert_eq!( + two * SignedDecimal256::percent(1000), + SignedDecimal256::percent(2000) + ); + assert_eq!( + SignedDecimal256::percent(0) * two, + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::percent(1) * two, + SignedDecimal256::percent(2) + ); + assert_eq!( + SignedDecimal256::percent(10) * two, + SignedDecimal256::percent(20) + ); + assert_eq!( + SignedDecimal256::percent(100) * two, + SignedDecimal256::percent(200) + ); + assert_eq!( + SignedDecimal256::percent(1000) * two, + SignedDecimal256::percent(2000) + ); + assert_eq!( + SignedDecimal256::percent(-1) * two, + SignedDecimal256::percent(-2) + ); + assert_eq!( + two * SignedDecimal256::new(Int256::MIN / Int256::from(2)), + SignedDecimal256::MIN + ); + + // half + assert_eq!( + half * SignedDecimal256::percent(0), + SignedDecimal256::percent(0) + ); + assert_eq!( + half * SignedDecimal256::percent(1), + SignedDecimal256::permille(5) + ); + assert_eq!( + half * SignedDecimal256::percent(10), + SignedDecimal256::percent(5) + ); + assert_eq!( + half * SignedDecimal256::percent(100), + SignedDecimal256::percent(50) + ); + assert_eq!( + half * SignedDecimal256::percent(1000), + SignedDecimal256::percent(500) + ); + assert_eq!( + SignedDecimal256::percent(0) * half, + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::percent(1) * half, + SignedDecimal256::permille(5) + ); + assert_eq!( + SignedDecimal256::percent(10) * half, + SignedDecimal256::percent(5) + ); + assert_eq!( + SignedDecimal256::percent(100) * half, + SignedDecimal256::percent(50) + ); + assert_eq!( + SignedDecimal256::percent(1000) * half, + SignedDecimal256::percent(500) + ); + + // Move left + let a = dec("123.127726548762582"); + assert_eq!(a * dec("1"), dec("123.127726548762582")); + assert_eq!(a * dec("10"), dec("1231.27726548762582")); + assert_eq!(a * dec("100"), dec("12312.7726548762582")); + assert_eq!(a * dec("1000"), dec("123127.726548762582")); + assert_eq!(a * dec("1000000"), dec("123127726.548762582")); + assert_eq!(a * dec("1000000000"), dec("123127726548.762582")); + assert_eq!(a * dec("1000000000000"), dec("123127726548762.582")); + assert_eq!(a * dec("1000000000000000"), dec("123127726548762582")); + assert_eq!(a * dec("1000000000000000000"), dec("123127726548762582000")); + assert_eq!(dec("1") * a, dec("123.127726548762582")); + assert_eq!(dec("10") * a, dec("1231.27726548762582")); + assert_eq!(dec("100") * a, dec("12312.7726548762582")); + assert_eq!(dec("1000") * a, dec("123127.726548762582")); + assert_eq!(dec("1000000") * a, dec("123127726.548762582")); + assert_eq!(dec("1000000000") * a, dec("123127726548.762582")); + assert_eq!(dec("1000000000000") * a, dec("123127726548762.582")); + assert_eq!(dec("1000000000000000") * a, dec("123127726548762582")); + assert_eq!(dec("1000000000000000000") * a, dec("123127726548762582000")); + assert_eq!( + dec("-1000000000000000000") * a, + dec("-123127726548762582000") + ); + + // Move right + let max = SignedDecimal256::MAX; + assert_eq!( + max * dec("1.0"), + dec("57896044618658097711785492504343953926634992332820282019728.792003956564819967") + ); + assert_eq!( + max * dec("0.1"), + dec("5789604461865809771178549250434395392663499233282028201972.879200395656481996") + ); + assert_eq!( + max * dec("0.01"), + dec("578960446186580977117854925043439539266349923328202820197.287920039565648199") + ); + assert_eq!( + max * dec("0.001"), + dec("57896044618658097711785492504343953926634992332820282019.728792003956564819") + ); + assert_eq!( + max * dec("0.000001"), + dec("57896044618658097711785492504343953926634992332820282.019728792003956564") + ); + assert_eq!( + max * dec("0.000000001"), + dec("57896044618658097711785492504343953926634992332820.282019728792003956") + ); + assert_eq!( + max * dec("0.000000000001"), + dec("57896044618658097711785492504343953926634992332.820282019728792003") + ); + assert_eq!( + max * dec("0.000000000000001"), + dec("57896044618658097711785492504343953926634992.332820282019728792") + ); + assert_eq!( + max * dec("0.000000000000000001"), + dec("57896044618658097711785492504343953926634.992332820282019728") + ); + + // works for refs + let a = SignedDecimal256::percent(20); + let b = SignedDecimal256::percent(30); + let expected = SignedDecimal256::percent(6); + assert_eq!(a * b, expected); + assert_eq!(&a * b, expected); + assert_eq!(a * &b, expected); + assert_eq!(&a * &b, expected); + } + + #[test] + fn signed_decimal_256_mul_assign_works() { + let mut a = SignedDecimal256::percent(15); + a *= SignedDecimal256::percent(60); + assert_eq!(a, SignedDecimal256::percent(9)); + + // works for refs + let mut a = SignedDecimal256::percent(50); + let b = SignedDecimal256::percent(20); + a *= &b; + assert_eq!(a, SignedDecimal256::percent(10)); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn signed_decimal_256_mul_overflow_panics() { + let _value = SignedDecimal256::MAX * SignedDecimal256::percent(101); + } + + #[test] + fn signed_decimal_256_checked_mul() { + let test_data = [ + (SignedDecimal256::zero(), SignedDecimal256::zero()), + (SignedDecimal256::zero(), SignedDecimal256::one()), + (SignedDecimal256::one(), SignedDecimal256::zero()), + (SignedDecimal256::percent(10), SignedDecimal256::zero()), + (SignedDecimal256::percent(10), SignedDecimal256::percent(5)), + (SignedDecimal256::MAX, SignedDecimal256::one()), + ( + SignedDecimal256::MAX / Int256::from(2), + SignedDecimal256::percent(200), + ), + ( + SignedDecimal256::permille(6), + SignedDecimal256::permille(13), + ), + ( + SignedDecimal256::permille(-6), + SignedDecimal256::permille(0), + ), + (SignedDecimal256::MAX, SignedDecimal256::negative_one()), + ]; + + // The regular core::ops::Mul is our source of truth for these tests. + for (x, y) in test_data.into_iter() { + assert_eq!(x * y, x.checked_mul(y).unwrap()); + } + } + + #[test] + fn signed_decimal_256_checked_mul_overflow() { + assert_eq!( + SignedDecimal256::MAX.checked_mul(SignedDecimal256::percent(200)), + Err(OverflowError { + operation: crate::OverflowOperation::Mul, + operand1: SignedDecimal256::MAX.to_string(), + operand2: SignedDecimal256::percent(200).to_string(), + }) + ); + } + + #[test] + #[allow(clippy::op_ref)] + fn signed_decimal_256_implements_div() { + let one = SignedDecimal256::one(); + let two = one + one; + let half = SignedDecimal256::percent(50); + + // 1/x and x/1 + assert_eq!( + one / SignedDecimal256::percent(1), + SignedDecimal256::percent(10_000) + ); + assert_eq!( + one / SignedDecimal256::percent(10), + SignedDecimal256::percent(1_000) + ); + assert_eq!( + one / SignedDecimal256::percent(100), + SignedDecimal256::percent(100) + ); + assert_eq!( + one / SignedDecimal256::percent(1000), + SignedDecimal256::percent(10) + ); + assert_eq!( + SignedDecimal256::percent(0) / one, + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::percent(1) / one, + SignedDecimal256::percent(1) + ); + assert_eq!( + SignedDecimal256::percent(10) / one, + SignedDecimal256::percent(10) + ); + assert_eq!( + SignedDecimal256::percent(100) / one, + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::percent(1000) / one, + SignedDecimal256::percent(1000) + ); + assert_eq!( + one / SignedDecimal256::percent(-1), + SignedDecimal256::percent(-10_000) + ); + assert_eq!( + one / SignedDecimal256::percent(-10), + SignedDecimal256::percent(-1_000) + ); + + // double + assert_eq!( + two / SignedDecimal256::percent(1), + SignedDecimal256::percent(20_000) + ); + assert_eq!( + two / SignedDecimal256::percent(10), + SignedDecimal256::percent(2_000) + ); + assert_eq!( + two / SignedDecimal256::percent(100), + SignedDecimal256::percent(200) + ); + assert_eq!( + two / SignedDecimal256::percent(1000), + SignedDecimal256::percent(20) + ); + assert_eq!( + SignedDecimal256::percent(0) / two, + SignedDecimal256::percent(0) + ); + assert_eq!(SignedDecimal256::percent(1) / two, dec("0.005")); + assert_eq!( + SignedDecimal256::percent(10) / two, + SignedDecimal256::percent(5) + ); + assert_eq!( + SignedDecimal256::percent(100) / two, + SignedDecimal256::percent(50) + ); + assert_eq!( + SignedDecimal256::percent(1000) / two, + SignedDecimal256::percent(500) + ); + assert_eq!( + two / SignedDecimal256::percent(-1), + SignedDecimal256::percent(-20_000) + ); + assert_eq!( + SignedDecimal256::percent(-10000) / two, + SignedDecimal256::percent(-5000) + ); + + // half + assert_eq!( + half / SignedDecimal256::percent(1), + SignedDecimal256::percent(5_000) + ); + assert_eq!( + half / SignedDecimal256::percent(10), + SignedDecimal256::percent(500) + ); + assert_eq!( + half / SignedDecimal256::percent(100), + SignedDecimal256::percent(50) + ); + assert_eq!( + half / SignedDecimal256::percent(1000), + SignedDecimal256::percent(5) + ); + assert_eq!( + SignedDecimal256::percent(0) / half, + SignedDecimal256::percent(0) + ); + assert_eq!( + SignedDecimal256::percent(1) / half, + SignedDecimal256::percent(2) + ); + assert_eq!( + SignedDecimal256::percent(10) / half, + SignedDecimal256::percent(20) + ); + assert_eq!( + SignedDecimal256::percent(100) / half, + SignedDecimal256::percent(200) + ); + assert_eq!( + SignedDecimal256::percent(1000) / half, + SignedDecimal256::percent(2000) + ); + + // Move right + let a = dec("123127726548762582"); + assert_eq!(a / dec("1"), dec("123127726548762582")); + assert_eq!(a / dec("10"), dec("12312772654876258.2")); + assert_eq!(a / dec("100"), dec("1231277265487625.82")); + assert_eq!(a / dec("1000"), dec("123127726548762.582")); + assert_eq!(a / dec("1000000"), dec("123127726548.762582")); + assert_eq!(a / dec("1000000000"), dec("123127726.548762582")); + assert_eq!(a / dec("1000000000000"), dec("123127.726548762582")); + assert_eq!(a / dec("1000000000000000"), dec("123.127726548762582")); + assert_eq!(a / dec("1000000000000000000"), dec("0.123127726548762582")); + assert_eq!(dec("1") / a, dec("0.000000000000000008")); + assert_eq!(dec("10") / a, dec("0.000000000000000081")); + assert_eq!(dec("100") / a, dec("0.000000000000000812")); + assert_eq!(dec("1000") / a, dec("0.000000000000008121")); + assert_eq!(dec("1000000") / a, dec("0.000000000008121647")); + assert_eq!(dec("1000000000") / a, dec("0.000000008121647560")); + assert_eq!(dec("1000000000000") / a, dec("0.000008121647560868")); + assert_eq!(dec("1000000000000000") / a, dec("0.008121647560868164")); + assert_eq!(dec("1000000000000000000") / a, dec("8.121647560868164773")); + // negative + let a = dec("-123127726548762582"); + assert_eq!(a / dec("1"), dec("-123127726548762582")); + assert_eq!(a / dec("10"), dec("-12312772654876258.2")); + assert_eq!(a / dec("100"), dec("-1231277265487625.82")); + assert_eq!(a / dec("1000"), dec("-123127726548762.582")); + assert_eq!(a / dec("1000000"), dec("-123127726548.762582")); + assert_eq!(a / dec("1000000000"), dec("-123127726.548762582")); + assert_eq!(a / dec("1000000000000"), dec("-123127.726548762582")); + assert_eq!(a / dec("1000000000000000"), dec("-123.127726548762582")); + assert_eq!(a / dec("1000000000000000000"), dec("-0.123127726548762582")); + assert_eq!(dec("1") / a, dec("-0.000000000000000008")); + + // Move left + let a = dec("0.123127726548762582"); + assert_eq!(a / dec("1.0"), dec("0.123127726548762582")); + assert_eq!(a / dec("0.1"), dec("1.23127726548762582")); + assert_eq!(a / dec("0.01"), dec("12.3127726548762582")); + assert_eq!(a / dec("0.001"), dec("123.127726548762582")); + assert_eq!(a / dec("0.000001"), dec("123127.726548762582")); + assert_eq!(a / dec("0.000000001"), dec("123127726.548762582")); + assert_eq!(a / dec("0.000000000001"), dec("123127726548.762582")); + assert_eq!(a / dec("0.000000000000001"), dec("123127726548762.582")); + assert_eq!(a / dec("0.000000000000000001"), dec("123127726548762582")); + // negative + let a = dec("-0.123127726548762582"); + assert_eq!(a / dec("1.0"), dec("-0.123127726548762582")); + assert_eq!(a / dec("0.1"), dec("-1.23127726548762582")); + assert_eq!(a / dec("0.01"), dec("-12.3127726548762582")); + assert_eq!(a / dec("0.001"), dec("-123.127726548762582")); + assert_eq!(a / dec("0.000001"), dec("-123127.726548762582")); + assert_eq!(a / dec("0.000000001"), dec("-123127726.548762582")); + + assert_eq!( + SignedDecimal256::percent(15) / SignedDecimal256::percent(60), + SignedDecimal256::percent(25) + ); + + // works for refs + let a = SignedDecimal256::percent(100); + let b = SignedDecimal256::percent(20); + let expected = SignedDecimal256::percent(500); + assert_eq!(a / b, expected); + assert_eq!(&a / b, expected); + assert_eq!(a / &b, expected); + assert_eq!(&a / &b, expected); + } + + #[test] + fn signed_decimal_256_div_assign_works() { + let mut a = SignedDecimal256::percent(15); + a /= SignedDecimal256::percent(20); + assert_eq!(a, SignedDecimal256::percent(75)); + + // works for refs + let mut a = SignedDecimal256::percent(50); + let b = SignedDecimal256::percent(20); + a /= &b; + assert_eq!(a, SignedDecimal256::percent(250)); + } + + #[test] + #[should_panic(expected = "Division failed - multiplication overflow")] + fn signed_decimal_256_div_overflow_panics() { + let _value = SignedDecimal256::MAX / SignedDecimal256::percent(10); + } + + #[test] + #[should_panic(expected = "Division failed - denominator must not be zero")] + fn signed_decimal_256_div_by_zero_panics() { + let _value = SignedDecimal256::one() / SignedDecimal256::zero(); + } + + #[test] + fn signed_decimal_256_int128_division() { + // a/b + let left = SignedDecimal256::percent(150); // 1.5 + let right = Int256::from(3); + assert_eq!(left / right, SignedDecimal256::percent(50)); + + // negative + let left = SignedDecimal256::percent(-150); // -1.5 + let right = Int256::from(3); + assert_eq!(left / right, SignedDecimal256::percent(-50)); + + // 0/a + let left = SignedDecimal256::zero(); + let right = Int256::from(300); + assert_eq!(left / right, SignedDecimal256::zero()); + } + + #[test] + #[should_panic] + fn signed_decimal_256_int128_divide_by_zero() { + let left = SignedDecimal256::percent(150); // 1.5 + let right = Int256::from(0); + let _result = left / right; + } + + #[test] + fn signed_decimal_256_int128_div_assign() { + // a/b + let mut dec = SignedDecimal256::percent(150); // 1.5 + dec /= Int256::from(3); + assert_eq!(dec, SignedDecimal256::percent(50)); + + // 0/a + let mut dec = SignedDecimal256::zero(); + dec /= Int256::from(300); + assert_eq!(dec, SignedDecimal256::zero()); + } + + #[test] + #[should_panic] + fn signed_decimal_256_int128_div_assign_by_zero() { + // a/0 + let mut dec = SignedDecimal256::percent(50); + dec /= Int256::from(0); + } + + #[test] + fn signed_decimal_256_checked_pow() { + for exp in 0..10 { + assert_eq!( + SignedDecimal256::one().checked_pow(exp).unwrap(), + SignedDecimal256::one() + ); + } + + // This case is mathematically undefined but we ensure consistency with Rust stdandard types + // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494 + assert_eq!( + SignedDecimal256::zero().checked_pow(0).unwrap(), + SignedDecimal256::one() + ); + + for exp in 1..10 { + assert_eq!( + SignedDecimal256::zero().checked_pow(exp).unwrap(), + SignedDecimal256::zero() + ); + } + + for exp in 1..10 { + assert_eq!( + SignedDecimal256::negative_one().checked_pow(exp).unwrap(), + // alternates between 1 and -1 + if exp % 2 == 0 { + SignedDecimal256::one() + } else { + SignedDecimal256::negative_one() + } + ) + } + + for num in &[ + SignedDecimal256::percent(50), + SignedDecimal256::percent(99), + SignedDecimal256::percent(200), + ] { + assert_eq!(num.checked_pow(0).unwrap(), SignedDecimal256::one()) + } + + assert_eq!( + SignedDecimal256::percent(20).checked_pow(2).unwrap(), + SignedDecimal256::percent(4) + ); + + assert_eq!( + SignedDecimal256::percent(20).checked_pow(3).unwrap(), + SignedDecimal256::permille(8) + ); + + assert_eq!( + SignedDecimal256::percent(200).checked_pow(4).unwrap(), + SignedDecimal256::percent(1600) + ); + + assert_eq!( + SignedDecimal256::percent(200).checked_pow(4).unwrap(), + SignedDecimal256::percent(1600) + ); + + assert_eq!( + SignedDecimal256::percent(700).checked_pow(5).unwrap(), + SignedDecimal256::percent(1680700) + ); + + assert_eq!( + SignedDecimal256::percent(700).checked_pow(8).unwrap(), + SignedDecimal256::percent(576480100) + ); + + assert_eq!( + SignedDecimal256::percent(700).checked_pow(10).unwrap(), + SignedDecimal256::percent(28247524900) + ); + + assert_eq!( + SignedDecimal256::percent(120).checked_pow(123).unwrap(), + SignedDecimal256(5486473221892422150877397607i128.into()) + ); + + assert_eq!( + SignedDecimal256::percent(10).checked_pow(2).unwrap(), + SignedDecimal256(10000000000000000i128.into()) + ); + + assert_eq!( + SignedDecimal256::percent(10).checked_pow(18).unwrap(), + SignedDecimal256(1i128.into()) + ); + + let decimals = [ + SignedDecimal256::percent(-50), + SignedDecimal256::percent(-99), + SignedDecimal256::percent(-200), + ]; + let exponents = [1, 2, 3, 4, 5, 8, 10]; + + for d in decimals { + for e in exponents { + // use multiplication as source of truth + let mut mul = Ok(d); + for _ in 1..e { + mul = mul.and_then(|mul| mul.checked_mul(d)); + } + assert_eq!(mul, d.checked_pow(e)); + } + } + } + + #[test] + fn signed_decimal_256_checked_pow_overflow() { + assert_eq!( + SignedDecimal256::MAX.checked_pow(2), + Err(OverflowError { + operation: crate::OverflowOperation::Pow, + operand1: SignedDecimal256::MAX.to_string(), + operand2: "2".to_string(), + }) + ); + } + + #[test] + fn signed_decimal_256_to_string() { + // Integers + assert_eq!(SignedDecimal256::zero().to_string(), "0"); + assert_eq!(SignedDecimal256::one().to_string(), "1"); + assert_eq!(SignedDecimal256::percent(500).to_string(), "5"); + assert_eq!(SignedDecimal256::percent(-500).to_string(), "-5"); + + // SignedDecimal256s + assert_eq!(SignedDecimal256::percent(125).to_string(), "1.25"); + assert_eq!(SignedDecimal256::percent(42638).to_string(), "426.38"); + assert_eq!(SignedDecimal256::percent(3).to_string(), "0.03"); + assert_eq!(SignedDecimal256::permille(987).to_string(), "0.987"); + assert_eq!(SignedDecimal256::percent(-125).to_string(), "-1.25"); + assert_eq!(SignedDecimal256::percent(-42638).to_string(), "-426.38"); + assert_eq!(SignedDecimal256::percent(-3).to_string(), "-0.03"); + assert_eq!(SignedDecimal256::permille(-987).to_string(), "-0.987"); + + assert_eq!( + SignedDecimal256(Int256::from(1i128)).to_string(), + "0.000000000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(10i128)).to_string(), + "0.00000000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(100i128)).to_string(), + "0.0000000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(1000i128)).to_string(), + "0.000000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(10000i128)).to_string(), + "0.00000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(100000i128)).to_string(), + "0.0000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(1000000i128)).to_string(), + "0.000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(10000000i128)).to_string(), + "0.00000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(100000000i128)).to_string(), + "0.0000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(1000000000i128)).to_string(), + "0.000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(10000000000i128)).to_string(), + "0.00000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(100000000000i128)).to_string(), + "0.0000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(10000000000000i128)).to_string(), + "0.00001" + ); + assert_eq!( + SignedDecimal256(Int256::from(100000000000000i128)).to_string(), + "0.0001" + ); + assert_eq!( + SignedDecimal256(Int256::from(1000000000000000i128)).to_string(), + "0.001" + ); + assert_eq!( + SignedDecimal256(Int256::from(10000000000000000i128)).to_string(), + "0.01" + ); + assert_eq!( + SignedDecimal256(Int256::from(100000000000000000i128)).to_string(), + "0.1" + ); + assert_eq!( + SignedDecimal256(Int256::from(-1i128)).to_string(), + "-0.000000000000000001" + ); + assert_eq!( + SignedDecimal256(Int256::from(-100000000000000i128)).to_string(), + "-0.0001" + ); + assert_eq!( + SignedDecimal256(Int256::from(-100000000000000000i128)).to_string(), + "-0.1" + ); + } + + #[test] + fn signed_decimal_256_iter_sum() { + let items = vec![ + SignedDecimal256::zero(), + SignedDecimal256(Int256::from(2i128)), + SignedDecimal256(Int256::from(2i128)), + SignedDecimal256(Int256::from(-2i128)), + ]; + assert_eq!( + items.iter().sum::(), + SignedDecimal256(Int256::from(2i128)) + ); + assert_eq!( + items.into_iter().sum::(), + SignedDecimal256(Int256::from(2i128)) + ); + + let empty: Vec = vec![]; + assert_eq!( + SignedDecimal256::zero(), + empty.iter().sum::() + ); + } + + #[test] + fn signed_decimal_256_serialize() { + assert_eq!(to_vec(&SignedDecimal256::zero()).unwrap(), br#""0""#); + assert_eq!(to_vec(&SignedDecimal256::one()).unwrap(), br#""1""#); + assert_eq!(to_vec(&SignedDecimal256::percent(8)).unwrap(), br#""0.08""#); + assert_eq!( + to_vec(&SignedDecimal256::percent(87)).unwrap(), + br#""0.87""# + ); + assert_eq!( + to_vec(&SignedDecimal256::percent(876)).unwrap(), + br#""8.76""# + ); + assert_eq!( + to_vec(&SignedDecimal256::percent(8765)).unwrap(), + br#""87.65""# + ); + assert_eq!( + to_vec(&SignedDecimal256::percent(-87654)).unwrap(), + br#""-876.54""# + ); + assert_eq!( + to_vec(&SignedDecimal256::negative_one()).unwrap(), + br#""-1""# + ); + assert_eq!( + to_vec(&-SignedDecimal256::percent(8)).unwrap(), + br#""-0.08""# + ); + } + + #[test] + fn signed_decimal_256_deserialize() { + assert_eq!( + from_slice::(br#""0""#).unwrap(), + SignedDecimal256::zero() + ); + assert_eq!( + from_slice::(br#""1""#).unwrap(), + SignedDecimal256::one() + ); + assert_eq!( + from_slice::(br#""000""#).unwrap(), + SignedDecimal256::zero() + ); + assert_eq!( + from_slice::(br#""001""#).unwrap(), + SignedDecimal256::one() + ); + + assert_eq!( + from_slice::(br#""0.08""#).unwrap(), + SignedDecimal256::percent(8) + ); + assert_eq!( + from_slice::(br#""0.87""#).unwrap(), + SignedDecimal256::percent(87) + ); + assert_eq!( + from_slice::(br#""8.76""#).unwrap(), + SignedDecimal256::percent(876) + ); + assert_eq!( + from_slice::(br#""87.65""#).unwrap(), + SignedDecimal256::percent(8765) + ); + + // negative numbers + assert_eq!( + from_slice::(br#""-0""#).unwrap(), + SignedDecimal256::zero() + ); + assert_eq!( + from_slice::(br#""-1""#).unwrap(), + SignedDecimal256::negative_one() + ); + assert_eq!( + from_slice::(br#""-001""#).unwrap(), + SignedDecimal256::negative_one() + ); + assert_eq!( + from_slice::(br#""-0.08""#).unwrap(), + SignedDecimal256::percent(-8) + ); + } + + #[test] + fn signed_decimal_256_abs_diff_works() { + let a = SignedDecimal256::percent(285); + let b = SignedDecimal256::percent(200); + let expected = Decimal256::percent(85); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); + + let a = SignedDecimal256::percent(-200); + let b = SignedDecimal256::percent(200); + let expected = Decimal256::percent(400); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); + + let a = SignedDecimal256::percent(-200); + let b = SignedDecimal256::percent(-240); + let expected = Decimal256::percent(40); + assert_eq!(a.abs_diff(b), expected); + assert_eq!(b.abs_diff(a), expected); + } + + #[test] + #[allow(clippy::op_ref)] + fn signed_decimal_256_rem_works() { + // 4.02 % 1.11 = 0.69 + assert_eq!( + SignedDecimal256::percent(402) % SignedDecimal256::percent(111), + SignedDecimal256::percent(69) + ); + + // 15.25 % 4 = 3.25 + assert_eq!( + SignedDecimal256::percent(1525) % SignedDecimal256::percent(400), + SignedDecimal256::percent(325) + ); + + // -20.25 % 5 = -25 + assert_eq!( + SignedDecimal256::percent(-2025) % SignedDecimal256::percent(500), + SignedDecimal256::percent(-25) + ); + + let a = SignedDecimal256::percent(318); + let b = SignedDecimal256::percent(317); + let expected = SignedDecimal256::percent(1); + assert_eq!(a % b, expected); + assert_eq!(a % &b, expected); + assert_eq!(&a % b, expected); + assert_eq!(&a % &b, expected); + } + + #[test] + fn signed_decimal_256_rem_assign_works() { + let mut a = SignedDecimal256::percent(17673); + a %= SignedDecimal256::percent(2362); + assert_eq!(a, SignedDecimal256::percent(1139)); // 176.73 % 23.62 = 11.39 + + let mut a = SignedDecimal256::percent(4262); + let b = SignedDecimal256::percent(1270); + a %= &b; + assert_eq!(a, SignedDecimal256::percent(452)); // 42.62 % 12.7 = 4.52 + + let mut a = SignedDecimal256::percent(-4262); + let b = SignedDecimal256::percent(1270); + a %= &b; + assert_eq!(a, SignedDecimal256::percent(-452)); // -42.62 % 12.7 = -4.52 + } + + #[test] + #[should_panic(expected = "divisor of zero")] + fn signed_decimal_256_rem_panics_for_zero() { + let _ = SignedDecimal256::percent(777) % SignedDecimal256::zero(); + } + + #[test] + fn signed_decimal_256_checked_methods() { + // checked add + assert_eq!( + SignedDecimal256::percent(402) + .checked_add(SignedDecimal256::percent(111)) + .unwrap(), + SignedDecimal256::percent(513) + ); + assert!(matches!( + SignedDecimal256::MAX.checked_add(SignedDecimal256::percent(1)), + Err(OverflowError { .. }) + )); + assert!(matches!( + SignedDecimal256::MIN.checked_add(SignedDecimal256::percent(-1)), + Err(OverflowError { .. }) + )); + + // checked sub + assert_eq!( + SignedDecimal256::percent(1111) + .checked_sub(SignedDecimal256::percent(111)) + .unwrap(), + SignedDecimal256::percent(1000) + ); + assert_eq!( + SignedDecimal256::zero() + .checked_sub(SignedDecimal256::percent(1)) + .unwrap(), + SignedDecimal256::percent(-1) + ); + assert!(matches!( + SignedDecimal256::MIN.checked_sub(SignedDecimal256::percent(1)), + Err(OverflowError { .. }) + )); + assert!(matches!( + SignedDecimal256::MAX.checked_sub(SignedDecimal256::percent(-1)), + Err(OverflowError { .. }) + )); + + // checked div + assert_eq!( + SignedDecimal256::percent(30) + .checked_div(SignedDecimal256::percent(200)) + .unwrap(), + SignedDecimal256::percent(15) + ); + assert_eq!( + SignedDecimal256::percent(88) + .checked_div(SignedDecimal256::percent(20)) + .unwrap(), + SignedDecimal256::percent(440) + ); + assert!(matches!( + SignedDecimal256::MAX.checked_div(SignedDecimal256::zero()), + Err(CheckedFromRatioError::DivideByZero {}) + )); + assert!(matches!( + SignedDecimal256::MAX.checked_div(SignedDecimal256::percent(1)), + Err(CheckedFromRatioError::Overflow {}) + )); + assert_eq!( + SignedDecimal256::percent(-88) + .checked_div(SignedDecimal256::percent(20)) + .unwrap(), + SignedDecimal256::percent(-440) + ); + assert_eq!( + SignedDecimal256::percent(-88) + .checked_div(SignedDecimal256::percent(-20)) + .unwrap(), + SignedDecimal256::percent(440) + ); + + // checked rem + assert_eq!( + SignedDecimal256::percent(402) + .checked_rem(SignedDecimal256::percent(111)) + .unwrap(), + SignedDecimal256::percent(69) + ); + assert_eq!( + SignedDecimal256::percent(1525) + .checked_rem(SignedDecimal256::percent(400)) + .unwrap(), + SignedDecimal256::percent(325) + ); + assert_eq!( + SignedDecimal256::percent(-1525) + .checked_rem(SignedDecimal256::percent(400)) + .unwrap(), + SignedDecimal256::percent(-325) + ); + assert_eq!( + SignedDecimal256::percent(-1525) + .checked_rem(SignedDecimal256::percent(-400)) + .unwrap(), + SignedDecimal256::percent(-325) + ); + assert!(matches!( + SignedDecimal256::MAX.checked_rem(SignedDecimal256::zero()), + Err(DivideByZeroError { .. }) + )); + } + + #[test] + fn signed_decimal_256_pow_works() { + assert_eq!( + SignedDecimal256::percent(200).pow(2), + SignedDecimal256::percent(400) + ); + assert_eq!( + SignedDecimal256::percent(-200).pow(2), + SignedDecimal256::percent(400) + ); + assert_eq!( + SignedDecimal256::percent(-200).pow(3), + SignedDecimal256::percent(-800) + ); + assert_eq!( + SignedDecimal256::percent(200).pow(10), + SignedDecimal256::percent(102400) + ); + } + + #[test] + #[should_panic] + fn signed_decimal_256_pow_overflow_panics() { + _ = SignedDecimal256::MAX.pow(2u32); + } + + #[test] + fn signed_decimal_256_saturating_works() { + assert_eq!( + SignedDecimal256::percent(200).saturating_add(SignedDecimal256::percent(200)), + SignedDecimal256::percent(400) + ); + assert_eq!( + SignedDecimal256::percent(-200).saturating_add(SignedDecimal256::percent(200)), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256::percent(-200).saturating_add(SignedDecimal256::percent(-200)), + SignedDecimal256::percent(-400) + ); + assert_eq!( + SignedDecimal256::MAX.saturating_add(SignedDecimal256::percent(200)), + SignedDecimal256::MAX + ); + assert_eq!( + SignedDecimal256::MIN.saturating_add(SignedDecimal256::percent(-1)), + SignedDecimal256::MIN + ); + assert_eq!( + SignedDecimal256::percent(200).saturating_sub(SignedDecimal256::percent(100)), + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::percent(-200).saturating_sub(SignedDecimal256::percent(100)), + SignedDecimal256::percent(-300) + ); + assert_eq!( + SignedDecimal256::percent(-200).saturating_sub(SignedDecimal256::percent(-100)), + SignedDecimal256::percent(-100) + ); + assert_eq!( + SignedDecimal256::zero().saturating_sub(SignedDecimal256::percent(200)), + SignedDecimal256::from_str("-2").unwrap() + ); + assert_eq!( + SignedDecimal256::MIN.saturating_sub(SignedDecimal256::percent(200)), + SignedDecimal256::MIN + ); + assert_eq!( + SignedDecimal256::MAX.saturating_sub(SignedDecimal256::percent(-200)), + SignedDecimal256::MAX + ); + assert_eq!( + SignedDecimal256::percent(200).saturating_mul(SignedDecimal256::percent(50)), + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::percent(-200).saturating_mul(SignedDecimal256::percent(50)), + SignedDecimal256::percent(-100) + ); + assert_eq!( + SignedDecimal256::percent(-200).saturating_mul(SignedDecimal256::percent(-50)), + SignedDecimal256::percent(100) + ); + assert_eq!( + SignedDecimal256::MAX.saturating_mul(SignedDecimal256::percent(200)), + SignedDecimal256::MAX + ); + assert_eq!( + SignedDecimal256::MIN.saturating_mul(SignedDecimal256::percent(200)), + SignedDecimal256::MIN + ); + assert_eq!( + SignedDecimal256::MIN.saturating_mul(SignedDecimal256::percent(-200)), + SignedDecimal256::MAX + ); + assert_eq!( + SignedDecimal256::percent(400).saturating_pow(2u32), + SignedDecimal256::percent(1600) + ); + assert_eq!( + SignedDecimal256::MAX.saturating_pow(2u32), + SignedDecimal256::MAX + ); + } + + #[test] + fn signed_decimal_256_rounding() { + assert_eq!(SignedDecimal256::one().floor(), SignedDecimal256::one()); + assert_eq!( + SignedDecimal256::percent(150).floor(), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::percent(199).floor(), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::percent(200).floor(), + SignedDecimal256::percent(200) + ); + assert_eq!( + SignedDecimal256::percent(99).floor(), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256(Int256::from(1i128)).floor(), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256(Int256::from(-1i128)).floor(), + SignedDecimal256::negative_one() + ); + assert_eq!( + SignedDecimal256::permille(-1234).floor(), + SignedDecimal256::percent(-200) + ); + + assert_eq!(SignedDecimal256::one().ceil(), SignedDecimal256::one()); + assert_eq!( + SignedDecimal256::percent(150).ceil(), + SignedDecimal256::percent(200) + ); + assert_eq!( + SignedDecimal256::percent(199).ceil(), + SignedDecimal256::percent(200) + ); + assert_eq!( + SignedDecimal256::percent(99).ceil(), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256(Int256::from(1i128)).ceil(), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256(Int256::from(-1i128)).ceil(), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256::permille(-1234).ceil(), + SignedDecimal256::negative_one() + ); + + assert_eq!(SignedDecimal256::one().trunc(), SignedDecimal256::one()); + assert_eq!( + SignedDecimal256::percent(150).trunc(), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::percent(199).trunc(), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::percent(200).trunc(), + SignedDecimal256::percent(200) + ); + assert_eq!( + SignedDecimal256::percent(99).trunc(), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256(Int256::from(1i128)).trunc(), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256(Int256::from(-1i128)).trunc(), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256::permille(-1234).trunc(), + SignedDecimal256::negative_one() + ); + } + + #[test] + #[should_panic(expected = "attempt to ceil with overflow")] + fn signed_decimal_256_ceil_panics() { + let _ = SignedDecimal256::MAX.ceil(); + } + + #[test] + #[should_panic(expected = "attempt to floor with overflow")] + fn signed_decimal_256_floor_panics() { + let _ = SignedDecimal256::MIN.floor(); + } + + #[test] + fn signed_decimal_256_checked_ceil() { + assert_eq!( + SignedDecimal256::percent(199).checked_ceil(), + Ok(SignedDecimal256::percent(200)) + ); + assert_eq!( + SignedDecimal256::MAX.checked_ceil(), + Err(RoundUpOverflowError) + ); + } + + #[test] + fn signed_decimal_256_checked_floor() { + assert_eq!( + SignedDecimal256::percent(199).checked_floor(), + Ok(SignedDecimal256::one()) + ); + assert_eq!( + SignedDecimal256::percent(-199).checked_floor(), + Ok(SignedDecimal256::percent(-200)) + ); + assert_eq!( + SignedDecimal256::MIN.checked_floor(), + Err(RoundDownOverflowError) + ); + } + + #[test] + fn signed_decimal_256_to_int_floor_works() { + let d = SignedDecimal256::from_str("12.000000000000000001").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(12)); + let d = SignedDecimal256::from_str("12.345").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(12)); + let d = SignedDecimal256::from_str("12.999").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(12)); + let d = SignedDecimal256::from_str("0.98451384").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(0)); + let d = SignedDecimal256::from_str("-12.000000000000000001").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(-13)); + let d = SignedDecimal256::from_str("-12.345").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(-13)); + + let d = SignedDecimal256::from_str("75.0").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(75)); + let d = SignedDecimal256::from_str("0.0").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(0)); + let d = SignedDecimal256::from_str("-75.0").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(-75)); + + let d = SignedDecimal256::MAX; + assert_eq!( + d.to_int_floor(), + Int256::from_str("57896044618658097711785492504343953926634992332820282019728") + .unwrap() + ); + let d = SignedDecimal256::MIN; + assert_eq!( + d.to_int_floor(), + Int256::from_str("-57896044618658097711785492504343953926634992332820282019729") + .unwrap() + ); + } + + #[test] + fn signed_decimal_256_to_int_ceil_works() { + let d = SignedDecimal256::from_str("12.000000000000000001").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(13)); + let d = SignedDecimal256::from_str("12.345").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(13)); + let d = SignedDecimal256::from_str("12.999").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(13)); + let d = SignedDecimal256::from_str("-12.000000000000000001").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(-12)); + let d = SignedDecimal256::from_str("-12.345").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(-12)); + + let d = SignedDecimal256::from_str("75.0").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(75)); + let d = SignedDecimal256::from_str("0.0").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(0)); + let d = SignedDecimal256::from_str("-75.0").unwrap(); + assert_eq!(d.to_int_ceil(), Int256::from(-75)); + + let d = SignedDecimal256::MAX; + assert_eq!( + d.to_int_ceil(), + Int256::from_str("57896044618658097711785492504343953926634992332820282019729") + .unwrap() + ); + let d = SignedDecimal256::MIN; + assert_eq!( + d.to_int_ceil(), + Int256::from_str("-57896044618658097711785492504343953926634992332820282019728") + .unwrap() + ); + } + + #[test] + fn signed_decimal_256_to_int_trunc_works() { + let d = SignedDecimal256::from_str("12.000000000000000001").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(12)); + let d = SignedDecimal256::from_str("12.345").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(12)); + let d = SignedDecimal256::from_str("12.999").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(12)); + let d = SignedDecimal256::from_str("-12.000000000000000001").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(-12)); + let d = SignedDecimal256::from_str("-12.345").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(-12)); + + let d = SignedDecimal256::from_str("75.0").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(75)); + let d = SignedDecimal256::from_str("0.0").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(0)); + let d = SignedDecimal256::from_str("-75.0").unwrap(); + assert_eq!(d.to_int_trunc(), Int256::from(-75)); + + let d = SignedDecimal256::MAX; + assert_eq!( + d.to_int_trunc(), + Int256::from_str("57896044618658097711785492504343953926634992332820282019728") + .unwrap() + ); + let d = SignedDecimal256::MIN; + assert_eq!( + d.to_int_trunc(), + Int256::from_str("-57896044618658097711785492504343953926634992332820282019728") + .unwrap() + ); + } + + #[test] + fn signed_decimal_256_neg_works() { + assert_eq!( + -SignedDecimal256::percent(50), + SignedDecimal256::percent(-50) + ); + assert_eq!(-SignedDecimal256::one(), SignedDecimal256::negative_one()); + } + + #[test] + fn signed_decimal_256_partial_eq() { + let test_cases = [ + ("1", "1", true), + ("0.5", "0.5", true), + ("0.5", "0.51", false), + ("0", "0.00000", true), + ("-1", "-1", true), + ("-0.5", "-0.5", true), + ("-0.5", "0.5", false), + ("-0.5", "-0.51", false), + ("-0", "-0.00000", true), + ] + .into_iter() + .map(|(lhs, rhs, expected)| (dec(lhs), dec(rhs), expected)); + + #[allow(clippy::op_ref)] + for (lhs, rhs, expected) in test_cases { + assert_eq!(lhs == rhs, expected); + assert_eq!(&lhs == rhs, expected); + assert_eq!(lhs == &rhs, expected); + assert_eq!(&lhs == &rhs, expected); + } + } + + #[test] + fn signed_decimal_256_implements_debug() { + let decimal = SignedDecimal256::from_str("123.45").unwrap(); + assert_eq!(format!("{decimal:?}"), "SignedDecimal256(123.45)"); + + let test_cases = ["5", "5.01", "42", "0", "2", "-0.000001"]; + for s in test_cases { + let decimal = SignedDecimal256::from_str(s).unwrap(); + let expected = format!("SignedDecimal256({s})"); + assert_eq!(format!("{decimal:?}"), expected); + } + } +} From b385419ae2d94b5e7b18087b3a009d06444674be Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 11 Sep 2023 15:41:46 +0200 Subject: [PATCH 13/26] Add conversions between signed decimals --- packages/std/src/math/signed_decimal.rs | 44 ++++++++++++++++++++- packages/std/src/math/signed_decimal_256.rs | 36 ++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 6d377f4925..de97ce3616 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -13,7 +13,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal, Int256}; +use crate::{forward_ref_partial_eq, Decimal, Int256, SignedDecimal256}; use super::Fraction; use super::Int128; @@ -613,6 +613,18 @@ impl Neg for SignedDecimal { } } +impl TryFrom for SignedDecimal { + type Error = SignedDecimalRangeExceeded; + + fn try_from(value: SignedDecimal256) -> Result { + value + .atomics() + .try_into() + .map(SignedDecimal) + .map_err(|_| SignedDecimalRangeExceeded) + } +} + impl FromStr for SignedDecimal { type Err = StdError; @@ -1417,6 +1429,36 @@ mod tests { } } + #[test] + fn signed_decimal_conversions_work() { + // signed decimal to signed decimal + assert_eq!( + SignedDecimal::try_from(SignedDecimal256::MAX).unwrap_err(), + SignedDecimalRangeExceeded + ); + assert_eq!( + SignedDecimal::try_from(SignedDecimal256::MIN).unwrap_err(), + SignedDecimalRangeExceeded + ); + + assert_eq!( + SignedDecimal::try_from(SignedDecimal256::zero()).unwrap(), + SignedDecimal::zero() + ); + assert_eq!( + SignedDecimal::try_from(SignedDecimal256::one()).unwrap(), + SignedDecimal::one() + ); + assert_eq!( + SignedDecimal::try_from(SignedDecimal256::percent(50)).unwrap(), + SignedDecimal::percent(50) + ); + assert_eq!( + SignedDecimal::try_from(SignedDecimal256::percent(-200)).unwrap(), + SignedDecimal::percent(-200) + ); + } + #[test] fn signed_decimal_atomics_works() { let zero = SignedDecimal::zero(); diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index eafc3f53d3..a08d4a4b2d 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -13,7 +13,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal256, Int512}; +use crate::{forward_ref_partial_eq, Decimal256, Int512, SignedDecimal}; use super::Fraction; use super::Int256; @@ -631,6 +631,12 @@ impl Neg for SignedDecimal256 { } } +impl From for SignedDecimal256 { + fn from(value: SignedDecimal) -> Self { + Self::from_atomics(value.atomics(), SignedDecimal::DECIMAL_PLACES).unwrap() + } +} + impl FromStr for SignedDecimal256 { type Err = StdError; @@ -1461,6 +1467,34 @@ mod tests { } } + #[test] + fn signed_decimal_256_conversions_work() { + assert_eq!( + SignedDecimal256::from(SignedDecimal::zero()), + SignedDecimal256::zero() + ); + assert_eq!( + SignedDecimal256::from(SignedDecimal::one()), + SignedDecimal256::one() + ); + assert_eq!( + SignedDecimal256::from(SignedDecimal::percent(50)), + SignedDecimal256::percent(50) + ); + assert_eq!( + SignedDecimal256::from(SignedDecimal::MAX), + SignedDecimal256::new(Int256::from_i128(i128::MAX)) + ); + assert_eq!( + SignedDecimal256::from(SignedDecimal::percent(-50)), + SignedDecimal256::percent(-50) + ); + assert_eq!( + SignedDecimal256::from(SignedDecimal::MIN), + SignedDecimal256::new(Int256::from_i128(i128::MIN)) + ); + } + #[test] fn signed_decimal_256_atomics_works() { let zero = SignedDecimal256::zero(); From 0609aff944de987e3a7e72629d0ca21b8cdb1be6 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 11 Sep 2023 17:58:52 +0200 Subject: [PATCH 14/26] Add conversions between signed + unsigned decimals --- packages/std/src/math/decimal.rs | 44 ++++++++++++++++++++++++- packages/std/src/math/signed_decimal.rs | 32 +++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index f402613621..f4db352bb2 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -11,7 +11,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal256}; +use crate::{forward_ref_partial_eq, Decimal256, SignedDecimal}; use super::Fraction; use super::Isqrt; @@ -510,6 +510,20 @@ impl TryFrom for Decimal { } } +impl TryFrom for Decimal { + type Error = DecimalRangeExceeded; + + fn try_from(value: SignedDecimal) -> Result { + value + .atomics() + .i128() + .try_into() // convert to u128 + .map(Uint128::new) + .map(Decimal) + .map_err(|_| DecimalRangeExceeded) + } +} + impl FromStr for Decimal { type Err = StdError; @@ -845,6 +859,34 @@ mod tests { ); } + #[test] + fn decimal_try_from_signed_works() { + assert_eq!( + Decimal::try_from(SignedDecimal::MAX).unwrap(), + Decimal::raw(SignedDecimal::MAX.atomics().i128() as u128) + ); + assert_eq!( + Decimal::try_from(SignedDecimal::zero()).unwrap(), + Decimal::zero() + ); + assert_eq!( + Decimal::try_from(SignedDecimal::one()).unwrap(), + Decimal::one() + ); + assert_eq!( + Decimal::try_from(SignedDecimal::percent(50)).unwrap(), + Decimal::percent(50) + ); + assert_eq!( + Decimal::try_from(SignedDecimal::negative_one()), + Err(DecimalRangeExceeded) + ); + assert_eq!( + Decimal::try_from(SignedDecimal::MIN), + Err(DecimalRangeExceeded) + ); + } + #[test] fn decimal_from_atomics_works() { let one = Decimal::one(); diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index de97ce3616..a544ed13b1 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -625,6 +625,20 @@ impl TryFrom for SignedDecimal { } } +impl TryFrom for SignedDecimal { + type Error = SignedDecimalRangeExceeded; + + fn try_from(value: Decimal) -> Result { + value + .atomics() + .u128() + .try_into() // convert to i128 + .map(Int128::new) + .map(SignedDecimal) + .map_err(|_| SignedDecimalRangeExceeded) + } +} + impl FromStr for SignedDecimal { type Err = StdError; @@ -1440,7 +1454,6 @@ mod tests { SignedDecimal::try_from(SignedDecimal256::MIN).unwrap_err(), SignedDecimalRangeExceeded ); - assert_eq!( SignedDecimal::try_from(SignedDecimal256::zero()).unwrap(), SignedDecimal::zero() @@ -1457,6 +1470,23 @@ mod tests { SignedDecimal::try_from(SignedDecimal256::percent(-200)).unwrap(), SignedDecimal::percent(-200) ); + + // unsigned to signed decimal + assert_eq!( + SignedDecimal::try_from(Decimal::MAX).unwrap_err(), + SignedDecimalRangeExceeded + ); + let max = Decimal::raw(SignedDecimal::MAX.atomics().i128() as u128); + let too_big = max + Decimal::raw(1); + assert_eq!( + SignedDecimal::try_from(too_big).unwrap_err(), + SignedDecimalRangeExceeded + ); + assert_eq!( + SignedDecimal::try_from(Decimal::zero()).unwrap(), + SignedDecimal::zero() + ); + assert_eq!(SignedDecimal::try_from(max).unwrap(), SignedDecimal::MAX); } #[test] From 25f9c35faaf6fc8fc093c511428f534615545566 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 14 Sep 2023 13:49:12 +0200 Subject: [PATCH 15/26] Add From and TryFrom impl between decimals --- packages/std/src/math/decimal.rs | 18 ++++++++++---- packages/std/src/math/decimal256.rs | 26 ++++++++++++++++++++- packages/std/src/math/signed_decimal.rs | 18 ++++++++++---- packages/std/src/math/signed_decimal_256.rs | 22 +++++++++++++++-- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index f4db352bb2..de9006f701 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -11,7 +11,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal256, SignedDecimal}; +use crate::{forward_ref_partial_eq, Decimal256, SignedDecimal, SignedDecimal256}; use super::Fraction; use super::Isqrt; @@ -516,9 +516,19 @@ impl TryFrom for Decimal { fn try_from(value: SignedDecimal) -> Result { value .atomics() - .i128() - .try_into() // convert to u128 - .map(Uint128::new) + .try_into() + .map(Decimal) + .map_err(|_| DecimalRangeExceeded) + } +} + +impl TryFrom for Decimal { + type Error = DecimalRangeExceeded; + + fn try_from(value: SignedDecimal256) -> Result { + value + .atomics() + .try_into() .map(Decimal) .map_err(|_| DecimalRangeExceeded) } diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index cfa58f8211..b1aec4ab4e 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -11,7 +11,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal, Uint512}; +use crate::{forward_ref_partial_eq, Decimal, SignedDecimal, SignedDecimal256, Uint512}; use super::Fraction; use super::Isqrt; @@ -525,6 +525,30 @@ impl From for Decimal256 { } } +impl TryFrom for Decimal256 { + type Error = Decimal256RangeExceeded; + + fn try_from(value: SignedDecimal) -> Result { + value + .atomics() + .try_into() + .map(Decimal256) + .map_err(|_| Decimal256RangeExceeded) + } +} + +impl TryFrom for Decimal256 { + type Error = Decimal256RangeExceeded; + + fn try_from(value: SignedDecimal256) -> Result { + value + .atomics() + .try_into() + .map(Decimal256) + .map_err(|_| Decimal256RangeExceeded) + } +} + impl FromStr for Decimal256 { type Err = StdError; diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index a544ed13b1..b7c3e21267 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -13,7 +13,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal, Int256, SignedDecimal256}; +use crate::{forward_ref_partial_eq, Decimal, Decimal256, Int256, SignedDecimal256}; use super::Fraction; use super::Int128; @@ -631,9 +631,19 @@ impl TryFrom for SignedDecimal { fn try_from(value: Decimal) -> Result { value .atomics() - .u128() - .try_into() // convert to i128 - .map(Int128::new) + .try_into() + .map(SignedDecimal) + .map_err(|_| SignedDecimalRangeExceeded) + } +} + +impl TryFrom for SignedDecimal { + type Error = SignedDecimalRangeExceeded; + + fn try_from(value: Decimal256) -> Result { + value + .atomics() + .try_into() .map(SignedDecimal) .map_err(|_| SignedDecimalRangeExceeded) } diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index a08d4a4b2d..a761f13f38 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -13,7 +13,7 @@ use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; -use crate::{forward_ref_partial_eq, Decimal256, Int512, SignedDecimal}; +use crate::{forward_ref_partial_eq, Decimal, Decimal256, Int512, SignedDecimal}; use super::Fraction; use super::Int256; @@ -633,7 +633,25 @@ impl Neg for SignedDecimal256 { impl From for SignedDecimal256 { fn from(value: SignedDecimal) -> Self { - Self::from_atomics(value.atomics(), SignedDecimal::DECIMAL_PLACES).unwrap() + Self::new(value.atomics().into()) + } +} + +impl From for SignedDecimal256 { + fn from(value: Decimal) -> Self { + Self::new(value.atomics().into()) + } +} + +impl TryFrom for SignedDecimal256 { + type Error = SignedDecimal256RangeExceeded; + + fn try_from(value: Decimal256) -> Result { + value + .atomics() + .try_into() + .map(SignedDecimal256) + .map_err(|_| SignedDecimal256RangeExceeded) } } From d82d79444affa8cb55ef664f2c73aaebd26efc9f Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 22 Sep 2023 16:31:56 +0200 Subject: [PATCH 16/26] Remove unnecessary zero check --- packages/std/src/math/signed_decimal.rs | 8 ++------ packages/std/src/math/signed_decimal_256.rs | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index b7c3e21267..e813c8111a 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -509,12 +509,8 @@ impl SignedDecimal { // but avoiding overflow by implementing the formula from `to_int_ceil` directly. let x = self.0; let y = Self::DECIMAL_FRACTIONAL; - if x.is_zero() { - Int128::zero() - } else { - // making sure not to negate `x`, as this would overflow - -Int128::one() - ((-Int128::one() - x) / y) - } + // making sure not to negate `x`, as this would overflow + -Int128::one() - ((-Int128::one() - x) / y) } else { self.to_int_trunc() } diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index a761f13f38..44fcfee83b 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -527,12 +527,8 @@ impl SignedDecimal256 { // but avoiding overflow by implementing the formula from `to_int_ceil` directly. let x = self.0; let y = Self::DECIMAL_FRACTIONAL; - if x.is_zero() { - Int256::zero() - } else { - // making sure not to negate `x`, as this would overflow - -Int256::one() - ((-Int256::one() - x) / y) - } + // making sure not to negate `x`, as this would overflow + -Int256::one() - ((-Int256::one() - x) / y) } else { self.to_int_trunc() } From 0ad3f13cf617ff43859904d09a8efc1e98e83fc9 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 09:52:54 +0200 Subject: [PATCH 17/26] Fix minor things --- packages/std/src/math/decimal.rs | 12 ++++++------ packages/std/src/math/decimal256.rs | 12 ++++++------ packages/std/src/math/signed_decimal.rs | 18 +++++++++--------- packages/std/src/math/signed_decimal_256.rs | 12 ++++++------ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index de9006f701..d22f35ee27 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -289,7 +289,7 @@ impl Decimal { .try_into() .map(Self) .map_err(|_| OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: self.to_string(), operand2: other.to_string(), }) @@ -331,7 +331,7 @@ impl Decimal { } inner(self, exp).map_err(|_| OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: self.to_string(), operand2: exp.to_string(), }) @@ -1121,7 +1121,7 @@ mod tests { } #[test] - fn decimal_from_str_errors_for_broken_fractinal_part() { + fn decimal_from_str_errors_for_broken_fractional_part() { match Decimal::from_str("1.").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), @@ -1537,7 +1537,7 @@ mod tests { assert_eq!( Decimal::MAX.checked_mul(Decimal::percent(200)), Err(OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: Decimal::MAX.to_string(), operand2: Decimal::percent(200).to_string(), }) @@ -1779,7 +1779,7 @@ mod tests { assert_eq!(Decimal::one().checked_pow(exp).unwrap(), Decimal::one()); } - // This case is mathematically undefined but we ensure consistency with Rust stdandard types + // This case is mathematically undefined but we ensure consistency with Rust standard types // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494 assert_eq!(Decimal::zero().checked_pow(0).unwrap(), Decimal::one()); @@ -1851,7 +1851,7 @@ mod tests { assert_eq!( Decimal::MAX.checked_pow(2), Err(OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: Decimal::MAX.to_string(), operand2: "2".to_string(), }) diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index b1aec4ab4e..fde449c952 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -304,7 +304,7 @@ impl Decimal256 { .try_into() .map(Self) .map_err(|_| OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: self.to_string(), operand2: other.to_string(), }) @@ -346,7 +346,7 @@ impl Decimal256 { } inner(self, exp).map_err(|_| OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: self.to_string(), operand2: exp.to_string(), }) @@ -1169,7 +1169,7 @@ mod tests { } #[test] - fn decimal256_from_str_errors_for_broken_fractinal_part() { + fn decimal256_from_str_errors_for_broken_fractional_part() { match Decimal256::from_str("1.").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), @@ -1605,7 +1605,7 @@ mod tests { assert_eq!( Decimal256::MAX.checked_mul(Decimal256::percent(200)), Err(OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: Decimal256::MAX.to_string(), operand2: Decimal256::percent(200).to_string(), }) @@ -1854,7 +1854,7 @@ mod tests { ); } - // This case is mathematically undefined but we ensure consistency with Rust stdandard types + // This case is mathematically undefined but we ensure consistency with Rust standard types // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494 assert_eq!( Decimal256::zero().checked_pow(0).unwrap(), @@ -1932,7 +1932,7 @@ mod tests { assert_eq!( Decimal256::MAX.checked_pow(2), Err(OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: Decimal256::MAX.to_string(), operand2: "2".to_string(), }) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index e813c8111a..943535f889 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -378,7 +378,7 @@ impl SignedDecimal { .try_into() .map(Self) .map_err(|_| OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: self.to_string(), operand2: other.to_string(), }) @@ -420,7 +420,7 @@ impl SignedDecimal { } inner(self, exp).map_err(|_| OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: self.to_string(), operand2: exp.to_string(), }) @@ -502,7 +502,7 @@ impl SignedDecimal { /// let d = SignedDecimal::from_str("-0.05").unwrap(); /// assert_eq!(d.to_int_floor(), Int128::new(-1)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_int_floor(self) -> Int128 { if self.is_negative() { // Using `x.to_int_floor() = -(-x).to_int_ceil()` for a negative `x`, @@ -534,7 +534,7 @@ impl SignedDecimal { /// let d = SignedDecimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_trunc(), Int128::new(75)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_int_trunc(self) -> Int128 { self.0 / Self::DECIMAL_FRACTIONAL } @@ -557,7 +557,7 @@ impl SignedDecimal { /// let d = SignedDecimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_ceil(), Int128::new(75)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_int_ceil(self) -> Int128 { if self.is_negative() { self.to_int_trunc() @@ -1364,7 +1364,7 @@ mod tests { } #[test] - fn signed_decimal_from_str_errors_for_broken_fractinal_part() { + fn signed_decimal_from_str_errors_for_broken_fractional_part() { match SignedDecimal::from_str("1.").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), @@ -1964,7 +1964,7 @@ mod tests { assert_eq!( SignedDecimal::MAX.checked_mul(SignedDecimal::percent(200)), Err(OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: SignedDecimal::MAX.to_string(), operand2: SignedDecimal::percent(200).to_string(), }) @@ -2233,7 +2233,7 @@ mod tests { ); } - // This case is mathematically undefined but we ensure consistency with Rust stdandard types + // This case is mathematically undefined but we ensure consistency with Rust standard types // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494 assert_eq!( SignedDecimal::zero().checked_pow(0).unwrap(), @@ -2341,7 +2341,7 @@ mod tests { assert_eq!( SignedDecimal::MAX.checked_pow(2), Err(OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: SignedDecimal::MAX.to_string(), operand2: "2".to_string(), }) diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index 44fcfee83b..c6fd1f9171 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -396,7 +396,7 @@ impl SignedDecimal256 { .try_into() .map(Self) .map_err(|_| OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: self.to_string(), operand2: other.to_string(), }) @@ -438,7 +438,7 @@ impl SignedDecimal256 { } inner(self, exp).map_err(|_| OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: self.to_string(), operand2: exp.to_string(), }) @@ -1376,7 +1376,7 @@ mod tests { } #[test] - fn signed_decimal_256_from_str_errors_for_broken_fractinal_part() { + fn signed_decimal_256_from_str_errors_for_broken_fractional_part() { match SignedDecimal256::from_str("1.").unwrap_err() { StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), e => panic!("Unexpected error: {e:?}"), @@ -2044,7 +2044,7 @@ mod tests { assert_eq!( SignedDecimal256::MAX.checked_mul(SignedDecimal256::percent(200)), Err(OverflowError { - operation: crate::OverflowOperation::Mul, + operation: OverflowOperation::Mul, operand1: SignedDecimal256::MAX.to_string(), operand2: SignedDecimal256::percent(200).to_string(), }) @@ -2334,7 +2334,7 @@ mod tests { ); } - // This case is mathematically undefined but we ensure consistency with Rust stdandard types + // This case is mathematically undefined but we ensure consistency with Rust standard types // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494 assert_eq!( SignedDecimal256::zero().checked_pow(0).unwrap(), @@ -2442,7 +2442,7 @@ mod tests { assert_eq!( SignedDecimal256::MAX.checked_pow(2), Err(OverflowError { - operation: crate::OverflowOperation::Pow, + operation: OverflowOperation::Pow, operand1: SignedDecimal256::MAX.to_string(), operand2: "2".to_string(), }) From 52127d96cce9efbb5af5bc7af741feefb3cb542c Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 10:23:27 +0200 Subject: [PATCH 18/26] Constify SignedDecimal256::negative_one --- packages/std/src/math/decimal256.rs | 10 ++-------- packages/std/src/math/signed_decimal_256.rs | 15 +++++---------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index fde449c952..8c9ccc7c19 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -33,15 +33,9 @@ pub struct Decimal256RangeExceeded; impl Decimal256 { const DECIMAL_FRACTIONAL: Uint256 = // 1*10**18 - Uint256::from_be_bytes([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, - 179, 167, 100, 0, 0, - ]); + Uint256::from_u128(1_000_000_000_000_000_000); const DECIMAL_FRACTIONAL_SQUARED: Uint256 = // 1*10**36 - Uint256::from_be_bytes([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 151, 206, 123, 201, 7, 21, 179, - 75, 159, 16, 0, 0, 0, 0, - ]); + Uint256::from_u128(1_000_000_000_000_000_000_000_000_000_000_000_000); /// The number of decimal places. Since decimal types are fixed-point rather than /// floating-point, this is a constant. diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index c6fd1f9171..b09230d621 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -38,15 +38,9 @@ pub struct SignedDecimal256RangeExceeded; impl SignedDecimal256 { const DECIMAL_FRACTIONAL: Int256 = // 1*10**18 - Int256::from_be_bytes([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, - 179, 167, 100, 0, 0, - ]); + Int256::from_i128(1_000_000_000_000_000_000); const DECIMAL_FRACTIONAL_SQUARED: Int256 = // 1*10**36 - Int256::from_be_bytes([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 151, 206, 123, 201, 7, 21, 179, - 75, 159, 16, 0, 0, 0, 0, - ]); + Int256::from_i128(1_000_000_000_000_000_000_000_000_000_000_000_000); /// The number of decimal places. Since decimal types are fixed-point rather than /// floating-point, this is a constant. @@ -112,8 +106,9 @@ impl SignedDecimal256 { /// Create a -1.0 SignedDecimal256 #[inline] - pub fn negative_one() -> Self { - Self(-Self::DECIMAL_FRACTIONAL) // TODO: constify + pub const fn negative_one() -> Self { + // -DECIMAL_FRATIONAL + Self(Int256::from_i128(-1_000_000_000_000_000_000)) } /// Create a 0.0 SignedDecimal256 From 1718a1724962dc8a11fdf7bdee02f2c0a5a22510 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 10:43:35 +0200 Subject: [PATCH 19/26] Add must_use messages --- packages/std/src/math/decimal.rs | 4 ++-- packages/std/src/math/decimal256.rs | 4 ++-- packages/std/src/math/signed_decimal_256.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index d22f35ee27..12837bc214 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -435,7 +435,7 @@ impl Decimal { /// let d = Decimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_uint_floor(), Uint128::new(75)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_uint_floor(self) -> Uint128 { self.0 / Self::DECIMAL_FRACTIONAL } @@ -458,7 +458,7 @@ impl Decimal { /// let d = Decimal::from_str("75.0").unwrap(); /// assert_eq!(d.to_uint_ceil(), Uint128::new(75)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_uint_ceil(self) -> Uint128 { // Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q // from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow. diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index 8c9ccc7c19..f0db608d2a 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -448,7 +448,7 @@ impl Decimal256 { /// let d = Decimal256::from_str("75.0").unwrap(); /// assert_eq!(d.to_uint_floor(), Uint256::from(75u64)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_uint_floor(self) -> Uint256 { self.0 / Self::DECIMAL_FRACTIONAL } @@ -471,7 +471,7 @@ impl Decimal256 { /// let d = Decimal256::from_str("75.0").unwrap(); /// assert_eq!(d.to_uint_ceil(), Uint256::from(75u64)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_uint_ceil(self) -> Uint256 { // Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q // from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow. diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index b09230d621..ca46e1a6c7 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -515,7 +515,7 @@ impl SignedDecimal256 { /// let d = SignedDecimal256::from_str("-0.05").unwrap(); /// assert_eq!(d.to_int_floor(), Int256::from(-1)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_int_floor(self) -> Int256 { if self.is_negative() { // Using `x.to_int_floor() = -(-x).to_int_ceil()` for a negative `x`, @@ -547,7 +547,7 @@ impl SignedDecimal256 { /// let d = SignedDecimal256::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_trunc(), Int256::from(75)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_int_trunc(self) -> Int256 { self.0 / Self::DECIMAL_FRACTIONAL } @@ -570,7 +570,7 @@ impl SignedDecimal256 { /// let d = SignedDecimal256::from_str("75.0").unwrap(); /// assert_eq!(d.to_int_ceil(), Int256::from(75)); /// ``` - #[must_use] + #[must_use = "this returns the result of the operation, without modifying the original"] pub fn to_int_ceil(self) -> Int256 { if self.is_negative() { self.to_int_trunc() From 9d1be62ed1bed935cf0f7b9c2d86e9d0188f893e Mon Sep 17 00:00:00 2001 From: Dariusz Depta Date: Fri, 22 Sep 2023 17:56:50 +0200 Subject: [PATCH 20/26] Added test cases. --- packages/std/src/math/signed_decimal.rs | 193 ++++++++++++++---------- 1 file changed, 111 insertions(+), 82 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 943535f889..ba029f96f6 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -907,6 +907,7 @@ impl<'de> de::Visitor<'de> for SignedDecimalVisitor { mod tests { use super::*; use crate::{from_slice, to_vec}; + use schemars::schema_for; fn dec(input: &str) -> SignedDecimal { SignedDecimal::from_str(input).unwrap() @@ -1347,106 +1348,72 @@ mod tests { #[test] fn signed_decimal_from_str_errors_for_broken_whole_part() { - match SignedDecimal::from_str("").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str(" ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str("-").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } + let expected_err = StdError::generic_err("Error parsing whole"); + assert_eq!(expected_err, SignedDecimal::from_str("").unwrap_err()); + assert_eq!(expected_err, SignedDecimal::from_str(" ").unwrap_err()); + assert_eq!(expected_err, SignedDecimal::from_str("-").unwrap_err()); } #[test] fn signed_decimal_from_str_errors_for_broken_fractional_part() { - match SignedDecimal::from_str("1.").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str("1. ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str("1.e").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str("1.2e3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str("1.-2").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } + let expected_err = StdError::generic_err("Error parsing fractional"); + assert_eq!(expected_err, SignedDecimal::from_str("1.").unwrap_err()); + assert_eq!(expected_err, SignedDecimal::from_str("1. ").unwrap_err()); + assert_eq!(expected_err, SignedDecimal::from_str("1.e").unwrap_err()); + assert_eq!(expected_err, SignedDecimal::from_str("1.2e3").unwrap_err()); + assert_eq!(expected_err, SignedDecimal::from_str("1.-2").unwrap_err()); } #[test] fn signed_decimal_from_str_errors_for_more_than_18_fractional_digits() { - match SignedDecimal::from_str("7.1234567890123456789").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits",) - } - e => panic!("Unexpected error: {e:?}"), - } - + let expected_err = StdError::generic_err("Cannot parse more than 18 fractional digits"); + assert_eq!( + expected_err, + SignedDecimal::from_str("7.1234567890123456789").unwrap_err() + ); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. - match SignedDecimal::from_str("7.1230000000000000000").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits") - } - e => panic!("Unexpected error: {e:?}"), - } + assert_eq!( + expected_err, + SignedDecimal::from_str("7.1230000000000000000").unwrap_err() + ); } #[test] fn signed_decimal_from_str_errors_for_invalid_number_of_dots() { - match SignedDecimal::from_str("1.2.3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal::from_str("1.2.3.4").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } + let expected_err = StdError::generic_err("Unexpected number of dots"); + assert_eq!(expected_err, SignedDecimal::from_str("1.2.3").unwrap_err()); + assert_eq!( + expected_err, + SignedDecimal::from_str("1.2.3.4").unwrap_err() + ); } #[test] fn signed_decimal_from_str_errors_for_more_than_max_value() { + let expected_err = StdError::generic_err("Value too big"); // Integer - match SignedDecimal::from_str("170141183460469231732").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match SignedDecimal::from_str("-170141183460469231732").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert_eq!( + expected_err, + SignedDecimal::from_str("170141183460469231732").unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal::from_str("-170141183460469231732").unwrap_err() + ); // SignedDecimal - match SignedDecimal::from_str("170141183460469231732.0").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match SignedDecimal::from_str("170141183460469231731.687303715884105728").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert_eq!( + expected_err, + SignedDecimal::from_str("170141183460469231732.0").unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal::from_str("170141183460469231731.687303715884105728").unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err() + ); } #[test] @@ -2823,6 +2790,9 @@ mod tests { SignedDecimal::percent(1600) ); assert_eq!(SignedDecimal::MAX.saturating_pow(2u32), SignedDecimal::MAX); + assert_eq!(SignedDecimal::MAX.saturating_pow(3u32), SignedDecimal::MAX); + assert_eq!(SignedDecimal::MIN.saturating_pow(2u32), SignedDecimal::MAX); + assert_eq!(SignedDecimal::MIN.saturating_pow(3u32), SignedDecimal::MIN); } #[test] @@ -2928,6 +2898,10 @@ mod tests { SignedDecimal::MIN.checked_floor(), Err(RoundDownOverflowError) ); + assert_eq!( + SignedDecimal::negative_one().checked_floor(), + Ok(SignedDecimal::negative_one()) + ); } #[test] @@ -2944,14 +2918,18 @@ mod tests { assert_eq!(d.to_int_floor(), Int128::new(-13)); let d = SignedDecimal::from_str("-12.345").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(-13)); - let d = SignedDecimal::from_str("75.0").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(75)); + let d = SignedDecimal::from_str("0.0001").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(0)); let d = SignedDecimal::from_str("0.0").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(0)); + let d = SignedDecimal::from_str("-0.0").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(0)); + let d = SignedDecimal::from_str("-0.0001").unwrap(); + assert_eq!(d.to_int_floor(), Int128::new(-1)); let d = SignedDecimal::from_str("-75.0").unwrap(); assert_eq!(d.to_int_floor(), Int128::new(-75)); - let d = SignedDecimal::MAX; assert_eq!(d.to_int_floor(), Int128::new(170141183460469231731)); let d = SignedDecimal::MIN; @@ -3053,4 +3031,55 @@ mod tests { assert_eq!(format!("{decimal:?}"), expected); } } + + #[test] + fn signed_decimal_can_be_instantiated_from_decimal256() { + let d: SignedDecimal = Decimal256::zero().try_into().unwrap(); + assert_eq!(d, SignedDecimal::zero()); + } + + #[test] + fn signed_decimal_may_fail_when_instantiated_from_decimal256() { + let err = >::try_into(Decimal256::MAX).unwrap_err(); + assert_eq!("SignedDecimalRangeExceeded", format!("{:?}", err)); + assert_eq!("SignedDecimal range exceeded", format!("{}", err)); + } + + #[test] + fn signed_decimal_can_be_serialized_and_deserialized() { + // properly deserialized + let value: SignedDecimal = serde_json::from_str(r#""123""#).unwrap(); + assert_eq!(SignedDecimal::from_str("123").unwrap(), value); + + // properly serialized + let value = SignedDecimal::from_str("456").unwrap(); + assert_eq!(r#""456""#, serde_json::to_string(&value).unwrap()); + + // invalid: not a string encoded decimal + assert_eq!( + "invalid type: integer `123`, expected string-encoded decimal at line 1 column 3", + serde_json::from_str::("123") + .err() + .unwrap() + .to_string() + ); + + // invalid: not properly defined signed decimal value + assert_eq!( + "Error parsing decimal '1.e': Generic error: Error parsing fractional at line 1 column 5", + serde_json::from_str::(r#""1.e""#) + .err() + .unwrap() + .to_string() + ); + } + + #[test] + fn signed_decimal_has_defined_json_schema() { + let schema = schema_for!(SignedDecimal); + assert_eq!( + "SignedDecimal", + schema.schema.metadata.unwrap().title.unwrap() + ); + } } From 7e6f24e0135c126beb36e471503cf51e7bc420fc Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 10:53:26 +0200 Subject: [PATCH 21/26] Adjust assert order --- packages/std/src/math/signed_decimal.rs | 50 ++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index ba029f96f6..3ad803ae7d 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -1349,42 +1349,42 @@ mod tests { #[test] fn signed_decimal_from_str_errors_for_broken_whole_part() { let expected_err = StdError::generic_err("Error parsing whole"); - assert_eq!(expected_err, SignedDecimal::from_str("").unwrap_err()); - assert_eq!(expected_err, SignedDecimal::from_str(" ").unwrap_err()); - assert_eq!(expected_err, SignedDecimal::from_str("-").unwrap_err()); + assert_eq!(SignedDecimal::from_str("").unwrap_err(), expected_err); + assert_eq!(SignedDecimal::from_str(" ").unwrap_err(), expected_err); + assert_eq!(SignedDecimal::from_str("-").unwrap_err(), expected_err); } #[test] fn signed_decimal_from_str_errors_for_broken_fractional_part() { let expected_err = StdError::generic_err("Error parsing fractional"); - assert_eq!(expected_err, SignedDecimal::from_str("1.").unwrap_err()); - assert_eq!(expected_err, SignedDecimal::from_str("1. ").unwrap_err()); - assert_eq!(expected_err, SignedDecimal::from_str("1.e").unwrap_err()); - assert_eq!(expected_err, SignedDecimal::from_str("1.2e3").unwrap_err()); - assert_eq!(expected_err, SignedDecimal::from_str("1.-2").unwrap_err()); + assert_eq!(SignedDecimal::from_str("1.").unwrap_err(), expected_err); + assert_eq!(SignedDecimal::from_str("1. ").unwrap_err(), expected_err); + assert_eq!(SignedDecimal::from_str("1.e").unwrap_err(), expected_err); + assert_eq!(SignedDecimal::from_str("1.2e3").unwrap_err(), expected_err); + assert_eq!(SignedDecimal::from_str("1.-2").unwrap_err(), expected_err); } #[test] fn signed_decimal_from_str_errors_for_more_than_18_fractional_digits() { let expected_err = StdError::generic_err("Cannot parse more than 18 fractional digits"); assert_eq!( - expected_err, - SignedDecimal::from_str("7.1234567890123456789").unwrap_err() + SignedDecimal::from_str("7.1234567890123456789").unwrap_err(), + expected_err ); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. assert_eq!( - expected_err, - SignedDecimal::from_str("7.1230000000000000000").unwrap_err() + SignedDecimal::from_str("7.1230000000000000000").unwrap_err(), + expected_err ); } #[test] fn signed_decimal_from_str_errors_for_invalid_number_of_dots() { let expected_err = StdError::generic_err("Unexpected number of dots"); - assert_eq!(expected_err, SignedDecimal::from_str("1.2.3").unwrap_err()); + assert_eq!(SignedDecimal::from_str("1.2.3").unwrap_err(), expected_err); assert_eq!( - expected_err, - SignedDecimal::from_str("1.2.3.4").unwrap_err() + SignedDecimal::from_str("1.2.3.4").unwrap_err(), + expected_err ); } @@ -1393,26 +1393,26 @@ mod tests { let expected_err = StdError::generic_err("Value too big"); // Integer assert_eq!( - expected_err, - SignedDecimal::from_str("170141183460469231732").unwrap_err() + SignedDecimal::from_str("170141183460469231732").unwrap_err(), + expected_err ); assert_eq!( - expected_err, - SignedDecimal::from_str("-170141183460469231732").unwrap_err() + SignedDecimal::from_str("-170141183460469231732").unwrap_err(), + expected_err ); // SignedDecimal assert_eq!( - expected_err, - SignedDecimal::from_str("170141183460469231732.0").unwrap_err() + SignedDecimal::from_str("170141183460469231732.0").unwrap_err(), + expected_err ); assert_eq!( - expected_err, - SignedDecimal::from_str("170141183460469231731.687303715884105728").unwrap_err() + SignedDecimal::from_str("170141183460469231731.687303715884105728").unwrap_err(), + expected_err ); assert_eq!( - expected_err, - SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err() + SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err(), + expected_err ); } From d87684220910a892ddea3eac630b13c045fc5785 Mon Sep 17 00:00:00 2001 From: Dariusz Depta Date: Mon, 25 Sep 2023 10:45:27 +0200 Subject: [PATCH 22/26] Added test cases. --- packages/std/src/math/signed_decimal_256.rs | 251 ++++++++++++-------- 1 file changed, 150 insertions(+), 101 deletions(-) diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index ca46e1a6c7..44c5c4e71d 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -908,6 +908,7 @@ impl<'de> de::Visitor<'de> for SignedDecimal256Visitor { mod tests { use super::*; use crate::{from_slice, to_vec}; + use schemars::schema_for; fn dec(input: &str) -> SignedDecimal256 { SignedDecimal256::from_str(input).unwrap() @@ -1354,126 +1355,96 @@ mod tests { #[test] fn signed_decimal_256_from_str_errors_for_broken_whole_part() { - match SignedDecimal256::from_str("").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str(" ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str("-").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } + let expected_err = StdError::generic_err("Error parsing whole"); + assert_eq!(expected_err, SignedDecimal256::from_str("").unwrap_err()); + assert_eq!(expected_err, SignedDecimal256::from_str(" ").unwrap_err()); + assert_eq!(expected_err, SignedDecimal256::from_str("-").unwrap_err()); } #[test] fn signed_decimal_256_from_str_errors_for_broken_fractional_part() { - match SignedDecimal256::from_str("1.").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str("1. ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str("1.e").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str("1.2e3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str("1.-2").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } + let expected_err = StdError::generic_err("Error parsing fractional"); + assert_eq!(expected_err, SignedDecimal256::from_str("1.").unwrap_err()); + assert_eq!(expected_err, SignedDecimal256::from_str("1. ").unwrap_err()); + assert_eq!(expected_err, SignedDecimal256::from_str("1.e").unwrap_err()); + assert_eq!( + expected_err, + SignedDecimal256::from_str("1.2e3").unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal256::from_str("1.-2").unwrap_err() + ); } #[test] fn signed_decimal_256_from_str_errors_for_more_than_18_fractional_digits() { - match SignedDecimal256::from_str("7.1234567890123456789").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits",) - } - e => panic!("Unexpected error: {e:?}"), - } - + let expected_err = StdError::generic_err("Cannot parse more than 18 fractional digits"); + assert_eq!( + expected_err, + SignedDecimal256::from_str("7.1234567890123456789").unwrap_err() + ); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. - match SignedDecimal256::from_str("7.1230000000000000000").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits") - } - e => panic!("Unexpected error: {e:?}"), - } + assert_eq!( + expected_err, + SignedDecimal256::from_str("7.1230000000000000000").unwrap_err() + ); } #[test] fn signed_decimal_256_from_str_errors_for_invalid_number_of_dots() { - match SignedDecimal256::from_str("1.2.3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } - - match SignedDecimal256::from_str("1.2.3.4").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } + let expected_err = StdError::generic_err("Unexpected number of dots"); + assert_eq!( + expected_err, + SignedDecimal256::from_str("1.2.3").unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal256::from_str("1.2.3.4").unwrap_err() + ); } #[test] fn signed_decimal_256_from_str_errors_for_more_than_max_value() { + let expected_err = StdError::generic_err("Value too big"); // Integer - match SignedDecimal256::from_str( - "57896044618658097711785492504343953926634992332820282019729", - ) - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match SignedDecimal256::from_str( - "-57896044618658097711785492504343953926634992332820282019729", - ) - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert_eq!( + expected_err, + SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019729", + ) + .unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019729", + ) + .unwrap_err() + ); // SignedDecimal256 - match SignedDecimal256::from_str( - "57896044618658097711785492504343953926634992332820282019729.0", - ) - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match SignedDecimal256::from_str( - "57896044618658097711785492504343953926634992332820282019728.792003956564819968", - ) - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match SignedDecimal256::from_str( - "-57896044618658097711785492504343953926634992332820282019728.792003956564819969", - ) - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert_eq!( + expected_err, + SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019729.0", + ) + .unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019728.792003956564819968", + ) + .unwrap_err() + ); + assert_eq!( + expected_err, + SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019728.792003956564819969", + ) + .unwrap_err() + ); } #[test] @@ -2937,6 +2908,18 @@ mod tests { SignedDecimal256::MAX.saturating_pow(2u32), SignedDecimal256::MAX ); + assert_eq!( + SignedDecimal256::MAX.saturating_pow(3u32), + SignedDecimal256::MAX + ); + assert_eq!( + SignedDecimal256::MIN.saturating_pow(2u32), + SignedDecimal256::MAX + ); + assert_eq!( + SignedDecimal256::MIN.saturating_pow(3u32), + SignedDecimal256::MIN + ); } #[test] @@ -3066,6 +3049,10 @@ mod tests { SignedDecimal256::MIN.checked_floor(), Err(RoundDownOverflowError) ); + assert_eq!( + SignedDecimal256::negative_one().checked_floor(), + Ok(SignedDecimal256::negative_one()) + ); } #[test] @@ -3082,11 +3069,16 @@ mod tests { assert_eq!(d.to_int_floor(), Int256::from(-13)); let d = SignedDecimal256::from_str("-12.345").unwrap(); assert_eq!(d.to_int_floor(), Int256::from(-13)); - + let d = SignedDecimal256::from_str("0.0001").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(0)); let d = SignedDecimal256::from_str("75.0").unwrap(); assert_eq!(d.to_int_floor(), Int256::from(75)); let d = SignedDecimal256::from_str("0.0").unwrap(); assert_eq!(d.to_int_floor(), Int256::from(0)); + let d = SignedDecimal256::from_str("-0.0").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(0)); + let d = SignedDecimal256::from_str("-0.0001").unwrap(); + assert_eq!(d.to_int_floor(), Int256::from(-1)); let d = SignedDecimal256::from_str("-75.0").unwrap(); assert_eq!(d.to_int_floor(), Int256::from(-75)); @@ -3218,4 +3210,61 @@ mod tests { assert_eq!(format!("{decimal:?}"), expected); } } + + #[test] + fn signed_decimal_256_can_be_instantiated_from_decimal() { + let d: SignedDecimal256 = Decimal::one().into(); + assert_eq!(d, SignedDecimal256::one()); + } + + #[test] + fn signed_decimal_256_can_be_instantiated_from_decimal_256() { + let d: SignedDecimal256 = Decimal256::zero().try_into().unwrap(); + assert_eq!(d, SignedDecimal256::zero()); + } + + #[test] + fn signed_decimal_256_may_fail_when_instantiated_from_decimal_256() { + let err = >::try_into(Decimal256::MAX).unwrap_err(); + assert_eq!("SignedDecimal256RangeExceeded", format!("{:?}", err)); + assert_eq!("SignedDecimal256 range exceeded", format!("{}", err)); + } + + #[test] + fn signed_decimal_256_can_be_serialized_and_deserialized() { + // properly deserialized + let value: SignedDecimal256 = serde_json::from_str(r#""123""#).unwrap(); + assert_eq!(SignedDecimal256::from_str("123").unwrap(), value); + + // properly serialized + let value = SignedDecimal256::from_str("456").unwrap(); + assert_eq!(r#""456""#, serde_json::to_string(&value).unwrap()); + + // invalid: not a string encoded decimal + assert_eq!( + "invalid type: integer `123`, expected string-encoded decimal at line 1 column 3", + serde_json::from_str::("123") + .err() + .unwrap() + .to_string() + ); + + // invalid: not properly defined signed decimal value + assert_eq!( + "Error parsing decimal '1.e': Generic error: Error parsing fractional at line 1 column 5", + serde_json::from_str::(r#""1.e""#) + .err() + .unwrap() + .to_string() + ); + } + + #[test] + fn signed_decimal_256_has_defined_json_schema() { + let schema = schema_for!(SignedDecimal256); + assert_eq!( + "SignedDecimal256", + schema.schema.metadata.unwrap().title.unwrap() + ); + } } From 51162f10aafcf5d1929ac20836c923e7aabd29c1 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 11:12:43 +0200 Subject: [PATCH 23/26] Adjust assert order --- packages/std/src/math/signed_decimal_256.rs | 56 ++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index 44c5c4e71d..67ad5165d1 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -1356,24 +1356,24 @@ mod tests { #[test] fn signed_decimal_256_from_str_errors_for_broken_whole_part() { let expected_err = StdError::generic_err("Error parsing whole"); - assert_eq!(expected_err, SignedDecimal256::from_str("").unwrap_err()); - assert_eq!(expected_err, SignedDecimal256::from_str(" ").unwrap_err()); - assert_eq!(expected_err, SignedDecimal256::from_str("-").unwrap_err()); + assert_eq!(SignedDecimal256::from_str("").unwrap_err(), expected_err); + assert_eq!(SignedDecimal256::from_str(" ").unwrap_err(), expected_err); + assert_eq!(SignedDecimal256::from_str("-").unwrap_err(), expected_err); } #[test] fn signed_decimal_256_from_str_errors_for_broken_fractional_part() { let expected_err = StdError::generic_err("Error parsing fractional"); - assert_eq!(expected_err, SignedDecimal256::from_str("1.").unwrap_err()); - assert_eq!(expected_err, SignedDecimal256::from_str("1. ").unwrap_err()); - assert_eq!(expected_err, SignedDecimal256::from_str("1.e").unwrap_err()); + assert_eq!(SignedDecimal256::from_str("1.").unwrap_err(), expected_err); + assert_eq!(SignedDecimal256::from_str("1. ").unwrap_err(), expected_err); + assert_eq!(SignedDecimal256::from_str("1.e").unwrap_err(), expected_err); assert_eq!( - expected_err, - SignedDecimal256::from_str("1.2e3").unwrap_err() + SignedDecimal256::from_str("1.2e3").unwrap_err(), + expected_err ); assert_eq!( - expected_err, - SignedDecimal256::from_str("1.-2").unwrap_err() + SignedDecimal256::from_str("1.-2").unwrap_err(), + expected_err ); } @@ -1381,13 +1381,13 @@ mod tests { fn signed_decimal_256_from_str_errors_for_more_than_18_fractional_digits() { let expected_err = StdError::generic_err("Cannot parse more than 18 fractional digits"); assert_eq!( - expected_err, - SignedDecimal256::from_str("7.1234567890123456789").unwrap_err() + SignedDecimal256::from_str("7.1234567890123456789").unwrap_err(), + expected_err ); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. assert_eq!( - expected_err, - SignedDecimal256::from_str("7.1230000000000000000").unwrap_err() + SignedDecimal256::from_str("7.1230000000000000000").unwrap_err(), + expected_err ); } @@ -1395,12 +1395,12 @@ mod tests { fn signed_decimal_256_from_str_errors_for_invalid_number_of_dots() { let expected_err = StdError::generic_err("Unexpected number of dots"); assert_eq!( - expected_err, - SignedDecimal256::from_str("1.2.3").unwrap_err() + SignedDecimal256::from_str("1.2.3").unwrap_err(), + expected_err ); assert_eq!( - expected_err, - SignedDecimal256::from_str("1.2.3.4").unwrap_err() + SignedDecimal256::from_str("1.2.3.4").unwrap_err(), + expected_err ); } @@ -1409,41 +1409,41 @@ mod tests { let expected_err = StdError::generic_err("Value too big"); // Integer assert_eq!( - expected_err, SignedDecimal256::from_str( "57896044618658097711785492504343953926634992332820282019729", ) - .unwrap_err() + .unwrap_err(), + expected_err ); assert_eq!( - expected_err, SignedDecimal256::from_str( "-57896044618658097711785492504343953926634992332820282019729", ) - .unwrap_err() + .unwrap_err(), + expected_err ); // SignedDecimal256 assert_eq!( - expected_err, SignedDecimal256::from_str( "57896044618658097711785492504343953926634992332820282019729.0", ) - .unwrap_err() + .unwrap_err(), + expected_err ); assert_eq!( - expected_err, SignedDecimal256::from_str( "57896044618658097711785492504343953926634992332820282019728.792003956564819968", ) - .unwrap_err() + .unwrap_err(), + expected_err ); assert_eq!( - expected_err, SignedDecimal256::from_str( "-57896044618658097711785492504343953926634992332820282019728.792003956564819969", ) - .unwrap_err() + .unwrap_err(), + expected_err ); } From 68802e0f3c3f68acfc6633df9c9d9dd2cf46d363 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 11:15:02 +0200 Subject: [PATCH 24/26] Fix clippy lints --- packages/std/src/math/signed_decimal.rs | 4 ++-- packages/std/src/math/signed_decimal_256.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 3ad803ae7d..5a92106a7a 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -3041,8 +3041,8 @@ mod tests { #[test] fn signed_decimal_may_fail_when_instantiated_from_decimal256() { let err = >::try_into(Decimal256::MAX).unwrap_err(); - assert_eq!("SignedDecimalRangeExceeded", format!("{:?}", err)); - assert_eq!("SignedDecimal range exceeded", format!("{}", err)); + assert_eq!("SignedDecimalRangeExceeded", format!("{err:?}")); + assert_eq!("SignedDecimal range exceeded", format!("{err}")); } #[test] diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index 67ad5165d1..a9cd4d494c 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -3226,8 +3226,8 @@ mod tests { #[test] fn signed_decimal_256_may_fail_when_instantiated_from_decimal_256() { let err = >::try_into(Decimal256::MAX).unwrap_err(); - assert_eq!("SignedDecimal256RangeExceeded", format!("{:?}", err)); - assert_eq!("SignedDecimal256 range exceeded", format!("{}", err)); + assert_eq!("SignedDecimal256RangeExceeded", format!("{err:?}")); + assert_eq!("SignedDecimal256 range exceeded", format!("{err}")); } #[test] From f8d5d8e4a65b5290934fc5a62cb7391ba450d602 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 15:38:41 +0200 Subject: [PATCH 25/26] Add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1e7575a9..93b8860e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to - cosmwasm-std: Add `to_json_{vec,binary,string}` and `from_json` and deprecate `to_{vec,binary}` in favor of `to_json_{vec,binary}` and `from_{slice,binary}` in favor of `from_json`. ([#1886]) +- cosmwasm-std: Add `SignedDecimal` and `SignedDecimal256` ([#1807]). [#1854]: https://github.com/CosmWasm/cosmwasm/pull/1854 [#1861]: https://github.com/CosmWasm/cosmwasm/pull/1861 @@ -29,6 +30,7 @@ and this project adheres to [#1867]: https://github.com/CosmWasm/cosmwasm/pull/1867 [#1870]: https://github.com/CosmWasm/cosmwasm/pull/1870 [#1886]: https://github.com/CosmWasm/cosmwasm/pull/1886 +[#1807]: https://github.com/CosmWasm/cosmwasm/pull/1807 ## [1.4.0] - 2023-09-04 From 9989ab9e7262b360c3f83d938230fd7fb998c7e9 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 25 Sep 2023 15:51:51 +0200 Subject: [PATCH 26/26] Replace deprecated calls --- packages/std/src/math/signed_decimal.rs | 59 +++++++++++++-------- packages/std/src/math/signed_decimal_256.rs | 47 ++++++++-------- 2 files changed, 62 insertions(+), 44 deletions(-) diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index 5a92106a7a..bdb45cf04e 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -906,7 +906,7 @@ impl<'de> de::Visitor<'de> for SignedDecimalVisitor { #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_json, to_json_vec}; use schemars::schema_for; fn dec(input: &str) -> SignedDecimal { @@ -2438,74 +2438,89 @@ mod tests { #[test] fn signed_decimal_serialize() { - assert_eq!(to_vec(&SignedDecimal::zero()).unwrap(), br#""0""#); - assert_eq!(to_vec(&SignedDecimal::one()).unwrap(), br#""1""#); - assert_eq!(to_vec(&SignedDecimal::percent(8)).unwrap(), br#""0.08""#); - assert_eq!(to_vec(&SignedDecimal::percent(87)).unwrap(), br#""0.87""#); - assert_eq!(to_vec(&SignedDecimal::percent(876)).unwrap(), br#""8.76""#); + assert_eq!(to_json_vec(&SignedDecimal::zero()).unwrap(), br#""0""#); + assert_eq!(to_json_vec(&SignedDecimal::one()).unwrap(), br#""1""#); assert_eq!( - to_vec(&SignedDecimal::percent(8765)).unwrap(), + to_json_vec(&SignedDecimal::percent(8)).unwrap(), + br#""0.08""# + ); + assert_eq!( + to_json_vec(&SignedDecimal::percent(87)).unwrap(), + br#""0.87""# + ); + assert_eq!( + to_json_vec(&SignedDecimal::percent(876)).unwrap(), + br#""8.76""# + ); + assert_eq!( + to_json_vec(&SignedDecimal::percent(8765)).unwrap(), br#""87.65""# ); assert_eq!( - to_vec(&SignedDecimal::percent(-87654)).unwrap(), + to_json_vec(&SignedDecimal::percent(-87654)).unwrap(), br#""-876.54""# ); - assert_eq!(to_vec(&SignedDecimal::negative_one()).unwrap(), br#""-1""#); - assert_eq!(to_vec(&-SignedDecimal::percent(8)).unwrap(), br#""-0.08""#); + assert_eq!( + to_json_vec(&SignedDecimal::negative_one()).unwrap(), + br#""-1""# + ); + assert_eq!( + to_json_vec(&-SignedDecimal::percent(8)).unwrap(), + br#""-0.08""# + ); } #[test] fn signed_decimal_deserialize() { assert_eq!( - from_slice::(br#""0""#).unwrap(), + from_json::(br#""0""#).unwrap(), SignedDecimal::zero() ); assert_eq!( - from_slice::(br#""1""#).unwrap(), + from_json::(br#""1""#).unwrap(), SignedDecimal::one() ); assert_eq!( - from_slice::(br#""000""#).unwrap(), + from_json::(br#""000""#).unwrap(), SignedDecimal::zero() ); assert_eq!( - from_slice::(br#""001""#).unwrap(), + from_json::(br#""001""#).unwrap(), SignedDecimal::one() ); assert_eq!( - from_slice::(br#""0.08""#).unwrap(), + from_json::(br#""0.08""#).unwrap(), SignedDecimal::percent(8) ); assert_eq!( - from_slice::(br#""0.87""#).unwrap(), + from_json::(br#""0.87""#).unwrap(), SignedDecimal::percent(87) ); assert_eq!( - from_slice::(br#""8.76""#).unwrap(), + from_json::(br#""8.76""#).unwrap(), SignedDecimal::percent(876) ); assert_eq!( - from_slice::(br#""87.65""#).unwrap(), + from_json::(br#""87.65""#).unwrap(), SignedDecimal::percent(8765) ); // negative numbers assert_eq!( - from_slice::(br#""-0""#).unwrap(), + from_json::(br#""-0""#).unwrap(), SignedDecimal::zero() ); assert_eq!( - from_slice::(br#""-1""#).unwrap(), + from_json::(br#""-1""#).unwrap(), SignedDecimal::negative_one() ); assert_eq!( - from_slice::(br#""-001""#).unwrap(), + from_json::(br#""-001""#).unwrap(), SignedDecimal::negative_one() ); assert_eq!( - from_slice::(br#""-0.08""#).unwrap(), + from_json::(br#""-0.08""#).unwrap(), SignedDecimal::percent(-8) ); } diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index a9cd4d494c..9468729c02 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -907,7 +907,7 @@ impl<'de> de::Visitor<'de> for SignedDecimal256Visitor { #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_json, to_json_vec}; use schemars::schema_for; fn dec(input: &str) -> SignedDecimal256 { @@ -2541,31 +2541,34 @@ mod tests { #[test] fn signed_decimal_256_serialize() { - assert_eq!(to_vec(&SignedDecimal256::zero()).unwrap(), br#""0""#); - assert_eq!(to_vec(&SignedDecimal256::one()).unwrap(), br#""1""#); - assert_eq!(to_vec(&SignedDecimal256::percent(8)).unwrap(), br#""0.08""#); + assert_eq!(to_json_vec(&SignedDecimal256::zero()).unwrap(), br#""0""#); + assert_eq!(to_json_vec(&SignedDecimal256::one()).unwrap(), br#""1""#); assert_eq!( - to_vec(&SignedDecimal256::percent(87)).unwrap(), + to_json_vec(&SignedDecimal256::percent(8)).unwrap(), + br#""0.08""# + ); + assert_eq!( + to_json_vec(&SignedDecimal256::percent(87)).unwrap(), br#""0.87""# ); assert_eq!( - to_vec(&SignedDecimal256::percent(876)).unwrap(), + to_json_vec(&SignedDecimal256::percent(876)).unwrap(), br#""8.76""# ); assert_eq!( - to_vec(&SignedDecimal256::percent(8765)).unwrap(), + to_json_vec(&SignedDecimal256::percent(8765)).unwrap(), br#""87.65""# ); assert_eq!( - to_vec(&SignedDecimal256::percent(-87654)).unwrap(), + to_json_vec(&SignedDecimal256::percent(-87654)).unwrap(), br#""-876.54""# ); assert_eq!( - to_vec(&SignedDecimal256::negative_one()).unwrap(), + to_json_vec(&SignedDecimal256::negative_one()).unwrap(), br#""-1""# ); assert_eq!( - to_vec(&-SignedDecimal256::percent(8)).unwrap(), + to_json_vec(&-SignedDecimal256::percent(8)).unwrap(), br#""-0.08""# ); } @@ -2573,54 +2576,54 @@ mod tests { #[test] fn signed_decimal_256_deserialize() { assert_eq!( - from_slice::(br#""0""#).unwrap(), + from_json::(br#""0""#).unwrap(), SignedDecimal256::zero() ); assert_eq!( - from_slice::(br#""1""#).unwrap(), + from_json::(br#""1""#).unwrap(), SignedDecimal256::one() ); assert_eq!( - from_slice::(br#""000""#).unwrap(), + from_json::(br#""000""#).unwrap(), SignedDecimal256::zero() ); assert_eq!( - from_slice::(br#""001""#).unwrap(), + from_json::(br#""001""#).unwrap(), SignedDecimal256::one() ); assert_eq!( - from_slice::(br#""0.08""#).unwrap(), + from_json::(br#""0.08""#).unwrap(), SignedDecimal256::percent(8) ); assert_eq!( - from_slice::(br#""0.87""#).unwrap(), + from_json::(br#""0.87""#).unwrap(), SignedDecimal256::percent(87) ); assert_eq!( - from_slice::(br#""8.76""#).unwrap(), + from_json::(br#""8.76""#).unwrap(), SignedDecimal256::percent(876) ); assert_eq!( - from_slice::(br#""87.65""#).unwrap(), + from_json::(br#""87.65""#).unwrap(), SignedDecimal256::percent(8765) ); // negative numbers assert_eq!( - from_slice::(br#""-0""#).unwrap(), + from_json::(br#""-0""#).unwrap(), SignedDecimal256::zero() ); assert_eq!( - from_slice::(br#""-1""#).unwrap(), + from_json::(br#""-1""#).unwrap(), SignedDecimal256::negative_one() ); assert_eq!( - from_slice::(br#""-001""#).unwrap(), + from_json::(br#""-001""#).unwrap(), SignedDecimal256::negative_one() ); assert_eq!( - from_slice::(br#""-0.08""#).unwrap(), + from_json::(br#""-0.08""#).unwrap(), SignedDecimal256::percent(-8) ); }