From cb4cc5ea39d7a55564321241409db7399a3c01cb Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 3 Jan 2023 20:12:33 +0100 Subject: [PATCH 01/13] Support fraction multiply --- packages/std/src/errors/mod.rs | 5 +- packages/std/src/errors/std_error.rs | 11 +- packages/std/src/lib.rs | 2 +- packages/std/src/math/decimal.rs | 4 +- packages/std/src/math/decimal256.rs | 4 +- packages/std/src/math/fraction.rs | 27 ++++- packages/std/src/math/mod.rs | 2 +- packages/std/src/math/uint128.rs | 172 ++++++++++++++++++++++++++- 8 files changed, 214 insertions(+), 13 deletions(-) diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index bf7e61f433..01793bfe17 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -5,8 +5,9 @@ mod verification_error; pub use recover_pubkey_error::RecoverPubkeyError; pub use std_error::{ - CheckedFromRatioError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError, - OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult, + CheckedFromRatioError, CheckedMultiplyFractionalError, CheckedMultiplyRatioError, + ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, + 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 fa5c27f328..101c1a040d 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -543,7 +543,7 @@ impl ConversionOverflowError { } #[derive(Error, Debug, PartialEq, Eq)] -#[error("Cannot devide {operand} by zero")] +#[error("Cannot divide {operand} by zero")] pub struct DivideByZeroError { pub operand: String, } @@ -556,6 +556,15 @@ impl DivideByZeroError { } } +#[derive(Error, Debug, PartialEq, Eq)] +pub enum CheckedMultiplyFractionalError { + #[error("{0}")] + DivideByZero(#[from] DivideByZeroError), + + #[error("{0}")] + ConversionOverflow(#[from] ConversionOverflowError), +} + #[derive(Error, Debug, PartialEq, Eq)] pub enum CheckedMultiplyRatioError { #[error("Denominator must not be zero")] diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index a15f0f4c62..af696d5d12 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -46,7 +46,7 @@ pub use crate::ibc::{ #[cfg(feature = "iterator")] pub use crate::iterator::{Order, Record}; pub use crate::math::{ - Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128, + Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fractional, Isqrt, Uint128, Uint256, Uint512, Uint64, }; pub use crate::never::Never; diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index 72975e22e3..867b8dc6bd 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -12,7 +12,7 @@ use crate::errors::{ OverflowOperation, RoundUpOverflowError, StdError, }; -use super::Fraction; +use super::Fractional; use super::Isqrt; use super::{Uint128, Uint256}; @@ -359,7 +359,7 @@ impl Decimal { } } -impl Fraction for Decimal { +impl Fractional for Decimal { #[inline] fn numerator(&self) -> Uint128 { self.0 diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index 0c2b134b5b..7d5c3c981f 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -13,7 +13,7 @@ use crate::errors::{ }; use crate::{Decimal, Uint512}; -use super::Fraction; +use super::Fractional; use super::Isqrt; use super::Uint256; @@ -376,7 +376,7 @@ impl Decimal256 { } } -impl Fraction for Decimal256 { +impl Fractional for Decimal256 { #[inline] fn numerator(&self) -> Uint256 { self.0 diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index ca187ad78c..489db1a3d9 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -1,7 +1,9 @@ +use serde::{Deserialize, Serialize}; + /// A fraction `p`/`q` with integers `p` and `q`. /// /// `p` is called the numerator and `q` is called the denominator. -pub trait Fraction: Sized { +pub trait Fractional: Sized { /// Returns the numerator `p` fn numerator(&self) -> T; /// Returns the denominator `q` @@ -12,3 +14,26 @@ pub trait Fraction: Sized { /// If `p` is zero, None is returned. fn inv(&self) -> Option; } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Fraction(T, T); + +impl Fraction { + pub fn new(numerator: T, denominator: T) -> Self { + Self(numerator, denominator) + } +} + +impl Fractional for Fraction { + fn numerator(&self) -> T { + self.0.clone() + } + + fn denominator(&self) -> T { + self.1.clone() + } + + fn inv(&self) -> Option { + unimplemented!() + } +} diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs index 706d23005d..8151fd6f56 100644 --- a/packages/std/src/math/mod.rs +++ b/packages/std/src/math/mod.rs @@ -9,7 +9,7 @@ mod uint64; pub use decimal::{Decimal, DecimalRangeExceeded}; pub use decimal256::{Decimal256, Decimal256RangeExceeded}; -pub use fraction::Fraction; +pub use fraction::Fractional; pub use isqrt::Isqrt; pub use uint128::Uint128; pub use uint256::Uint256; diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index a6f3681c40..3d999ea600 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -8,9 +8,10 @@ use std::ops::{ use std::str::FromStr; use crate::errors::{ - CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, StdError, + CheckedMultiplyFractionalError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + OverflowOperation, StdError, }; -use crate::{ConversionOverflowError, Uint256, Uint64}; +use crate::{ConversionOverflowError, Fractional, Uint256, Uint64}; /// A thin wrapper around u128 that is using strings for JSON encoding/decoding, /// such that the full u128 range can be used for clients that convert JSON numbers to floats, @@ -136,6 +137,37 @@ impl Uint128 { .unwrap() } + pub fn mul_floored, T: Into>(self, rhs: F) -> Self { + self.checked_mul_floored(rhs).unwrap() + } + + pub fn checked_mul_floored, T: Into>( + self, + rhs: F, + ) -> Result { + let res = self + .full_mul(rhs.numerator()) + .checked_div(Uint256::from(rhs.denominator().into()))?; + Ok(res.try_into()?) + } + + pub fn mul_ceil + Clone, T: Into>(self, rhs: F) -> Self { + self.checked_mul_ceil(rhs).unwrap() + } + + pub fn checked_mul_ceil + Clone, T: Into>( + self, + rhs: F, + ) -> Result { + let mut result = self.checked_mul_floored(rhs.clone())?; + let numerator = Uint256::from(rhs.numerator().into()); + let denominator = Uint256::from(rhs.denominator().into()); + if !numerator.checked_rem(denominator)?.is_zero() { + result += Uint128::one(); + }; + Ok(result) + } + pub fn checked_add(self, other: Self) -> Result { self.0 .checked_add(other.0) @@ -538,7 +570,9 @@ impl PartialEq for &Uint128 { #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::errors::CheckedMultiplyFractionalError::{ConversionOverflow, DivideByZero}; + use crate::math::fraction::Fraction; + use crate::{from_slice, to_vec, Decimal}; #[test] fn size_of_works() { @@ -1039,4 +1073,136 @@ mod tests { assert_eq!(&lhs == &rhs, expected); } } + + #[test] + fn mul_floored_works_with_zero() { + let fraction = Fraction::new(Uint128::zero(), Uint128::new(21)); + let res = Uint128::new(123456).mul_floored(fraction); + assert_eq!(Uint128::zero(), res) + } + + #[test] + fn mul_floored_does_nothing_with_one() { + let fraction = Fraction::new(Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).mul_floored(fraction); + assert_eq!(Uint128::new(123456), res) + } + + #[test] + fn mul_floored_rounds_down_with_normal_case() { + let fraction = Fraction::new(8u128, 21u128); + let res = Uint128::new(123456).mul_floored(fraction); // 47030.8571 + assert_eq!(Uint128::new(47030), res) + } + + #[test] + fn mul_floored_works_with_decimal() { + let decimal = Decimal::from_ratio(8u128, 21u128); + let res = Uint128::new(123456).mul_floored(decimal); // 47030.8571 + assert_eq!(Uint128::new(47030), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn mul_floored_panics_on_overflow() { + let fraction = Fraction::new(21u128, 8u128); + Uint128::MAX.mul_floored(fraction); + } + + #[test] + fn checked_mul_floored_does_not_panic_on_overflow() { + let fraction = Fraction::new(21u128, 8u128); + assert_eq!( + Uint128::MAX.checked_mul_floored(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn mul_floored_panics_on_zero_div() { + let fraction = Fraction::new(21u128, 0u128); + Uint128::new(123456).mul_floored(fraction); + } + + #[test] + fn checked_mul_floored_does_not_panic_on_zero_div() { + let fraction = Fraction::new(21u128, 0u128); + assert_eq!( + Uint128::new(123456).checked_mul_floored(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); + } + + #[test] + fn mul_ceil_works_with_zero() { + let fraction = Fraction::new(Uint128::zero(), Uint128::new(21)); + let res = Uint128::new(123456).mul_ceil(fraction); + assert_eq!(Uint128::zero(), res) + } + + #[test] + fn mul_ceil_does_nothing_with_one() { + let fraction = Fraction::new(Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).mul_ceil(fraction); + assert_eq!(Uint128::new(123456), res) + } + + #[test] + fn mul_ceil_rounds_up_with_normal_case() { + let fraction = Fraction::new(8u128, 21u128); + let res = Uint128::new(123456).mul_ceil(fraction); // 47030.8571 + assert_eq!(Uint128::new(47031), res) + } + + #[test] + fn mul_ceil_works_with_decimal() { + let decimal = Decimal::from_ratio(8u128, 21u128); + let res = Uint128::new(123456).mul_ceil(decimal); // 47030.8571 + assert_eq!(Uint128::new(47031), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn mul_ceil_panics_on_overflow() { + let fraction = Fraction::new(21u128, 8u128); + Uint128::MAX.mul_ceil(fraction); + } + + #[test] + fn checked_mul_ceil_does_not_panic_on_overflow() { + let fraction = Fraction::new(21u128, 8u128); + assert_eq!( + Uint128::MAX.checked_mul_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn mul_ceil_panics_on_zero_div() { + let fraction = Fraction::new(21u128, 0u128); + Uint128::new(123456).mul_ceil(fraction); + } + + #[test] + fn checked_mul_ceil_does_not_panic_on_zero_div() { + let fraction = Fraction::new(21u128, 0u128); + assert_eq!( + Uint128::new(123456).checked_mul_ceil(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); + } } From 1135c8af73c8d8bcab85d582b265eab24bca4475 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 3 Jan 2023 21:47:14 +0100 Subject: [PATCH 02/13] Add macro abstraction support --- packages/std/src/errors/std_error.rs | 3 ++ packages/std/src/math/fraction.rs | 63 ++++++++++++++++++++++++++++ packages/std/src/math/uint128.rs | 56 +++++++------------------ 3 files changed, 81 insertions(+), 41 deletions(-) diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs index 101c1a040d..89df9b793a 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -563,6 +563,9 @@ pub enum CheckedMultiplyFractionalError { #[error("{0}")] ConversionOverflow(#[from] ConversionOverflowError), + + #[error("{0}")] + Overflow(#[from] OverflowError), } #[derive(Error, Debug, PartialEq, Eq)] diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index 489db1a3d9..10e824c8fe 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -1,5 +1,8 @@ use serde::{Deserialize, Serialize}; +use crate::errors::CheckedMultiplyFractionalError; +use crate::Uint512; + /// A fraction `p`/`q` with integers `p` and `q`. /// /// `p` is called the numerator and `q` is called the denominator. @@ -37,3 +40,63 @@ impl Fractional for Fraction { unimplemented!() } } + +pub trait FractionMath { + fn checked_mul_floored, T: Into>( + self, + rhs: F, + ) -> Result + where + Self: Sized; + + fn mul_floored, T: Into>(self, rhs: F) -> Self; + + fn checked_mul_ceil + Clone, T: Into>( + self, + rhs: F, + ) -> Result + where + Self: Sized; + + fn mul_ceil + Clone, T: Into>(self, rhs: F) -> Self; +} + +#[macro_export] +macro_rules! fraction_math { + ($name:ident) => { + impl FractionMath for $name { + fn checked_mul_floored, T: Into>( + self, + rhs: F, + ) -> Result { + let res = Uint512::from(self) + .checked_mul(rhs.numerator().into())? + .checked_div(rhs.denominator().into())?; + Ok(res.try_into()?) + } + + fn mul_floored, T: Into>(self, rhs: F) -> Self { + self.checked_mul_floored(rhs).unwrap() + } + + fn mul_ceil + Clone, T: Into>(self, rhs: F) -> Self { + self.checked_mul_ceil(rhs).unwrap() + } + + fn checked_mul_ceil + Clone, T: Into>( + self, + rhs: F, + ) -> Result { + let floor_result = self.checked_mul_floored(rhs.clone())?; + let numerator = rhs.numerator().into(); + let denominator = rhs.denominator().into(); + if !numerator.checked_rem(denominator)?.is_zero() { + let ceil_result = Uint512::one().checked_add(floor_result.into())?; + Ok(ceil_result.try_into()?) + } else { + Ok(floor_result) + } + } + } + }; +} diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 3d999ea600..91e315aa1f 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -1,17 +1,19 @@ -use forward_ref::{forward_ref_binop, forward_ref_op_assign}; -use schemars::JsonSchema; -use serde::{de, ser, Deserialize, Deserializer, Serialize}; use std::fmt::{self}; use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shr, ShrAssign, Sub, SubAssign, }; use std::str::FromStr; +use forward_ref::{forward_ref_binop, forward_ref_op_assign}; +use schemars::JsonSchema; +use serde::{de, ser, Deserialize, Deserializer, Serialize}; + +use crate::errors::CheckedMultiplyFractionalError; use crate::errors::{ - CheckedMultiplyFractionalError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, - OverflowOperation, StdError, + CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, StdError, }; -use crate::{ConversionOverflowError, Fractional, Uint256, Uint64}; +use crate::math::fraction::FractionMath; +use crate::{fraction_math, ConversionOverflowError, Fractional, Uint256, Uint512, Uint64}; /// A thin wrapper around u128 that is using strings for JSON encoding/decoding, /// such that the full u128 range can be used for clients that convert JSON numbers to floats, @@ -137,37 +139,6 @@ impl Uint128 { .unwrap() } - pub fn mul_floored, T: Into>(self, rhs: F) -> Self { - self.checked_mul_floored(rhs).unwrap() - } - - pub fn checked_mul_floored, T: Into>( - self, - rhs: F, - ) -> Result { - let res = self - .full_mul(rhs.numerator()) - .checked_div(Uint256::from(rhs.denominator().into()))?; - Ok(res.try_into()?) - } - - pub fn mul_ceil + Clone, T: Into>(self, rhs: F) -> Self { - self.checked_mul_ceil(rhs).unwrap() - } - - pub fn checked_mul_ceil + Clone, T: Into>( - self, - rhs: F, - ) -> Result { - let mut result = self.checked_mul_floored(rhs.clone())?; - let numerator = Uint256::from(rhs.numerator().into()); - let denominator = Uint256::from(rhs.denominator().into()); - if !numerator.checked_rem(denominator)?.is_zero() { - result += Uint128::one(); - }; - Ok(result) - } - pub fn checked_add(self, other: Self) -> Result { self.0 .checked_add(other.0) @@ -262,6 +233,8 @@ impl Uint128 { } } +fraction_math!(Uint128); + // `From` is implemented manually instead of // using `impl> From for Uint128` because // of the conflict with `TryFrom<&str>` as described here @@ -569,11 +542,12 @@ impl PartialEq for &Uint128 { #[cfg(test)] mod tests { - use super::*; use crate::errors::CheckedMultiplyFractionalError::{ConversionOverflow, DivideByZero}; - use crate::math::fraction::Fraction; + use crate::math::fraction::{Fraction, FractionMath}; use crate::{from_slice, to_vec, Decimal}; + use super::*; + #[test] fn size_of_works() { assert_eq!(std::mem::size_of::(), 16); @@ -1115,7 +1089,7 @@ mod tests { assert_eq!( Uint128::MAX.checked_mul_floored(fraction), Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint256", + source_type: "Uint512", target_type: "Uint128", value: "893241213167463466591358344508391555069".to_string() })), @@ -1181,7 +1155,7 @@ mod tests { assert_eq!( Uint128::MAX.checked_mul_ceil(fraction), Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint256", + source_type: "Uint512", target_type: "Uint128", value: "893241213167463466591358344508391555069".to_string() })), From 33df9fdcec4b0ec1adeae22f4ce4dcb1dbf08e15 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 3 Jan 2023 22:00:58 +0100 Subject: [PATCH 03/13] Higher bit size tests --- packages/std/src/math/uint128.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 91e315aa1f..c455b6bfa7 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -544,7 +544,7 @@ impl PartialEq for &Uint128 { mod tests { use crate::errors::CheckedMultiplyFractionalError::{ConversionOverflow, DivideByZero}; use crate::math::fraction::{Fraction, FractionMath}; - use crate::{from_slice, to_vec, Decimal}; + use crate::{from_slice, to_vec, Decimal, Decimal256}; use super::*; @@ -1069,6 +1069,13 @@ mod tests { assert_eq!(Uint128::new(47030), res) } + #[test] + fn mul_floored_works_with_higher_bit_sizes() { + let fraction = Fraction::new(Uint256::from(8u128), Uint256::from(21u128)); + let res = Uint128::new(123456).mul_floored(fraction); // 47030.8571 + assert_eq!(Uint128::new(47030), res) + } + #[test] fn mul_floored_works_with_decimal() { let decimal = Decimal::from_ratio(8u128, 21u128); @@ -1076,6 +1083,13 @@ mod tests { assert_eq!(Uint128::new(47030), res) } + #[test] + fn mul_floored_works_with_decimal256() { + let decimal = Decimal256::from_ratio(8u128, 21u128); + let res = Uint128::new(123456).mul_floored(decimal); // 47030.8571 + assert_eq!(Uint128::new(47030), res) + } + #[test] #[should_panic(expected = "ConversionOverflowError")] fn mul_floored_panics_on_overflow() { @@ -1135,6 +1149,13 @@ mod tests { assert_eq!(Uint128::new(47031), res) } + #[test] + fn mul_ceil_works_with_higher_bit_sizes() { + let fraction = Fraction::new(Uint256::from(8u128), Uint256::from(21u128)); + let res = Uint128::new(123456).mul_ceil(fraction); // 47030.8571 + assert_eq!(Uint128::new(47031), res) + } + #[test] fn mul_ceil_works_with_decimal() { let decimal = Decimal::from_ratio(8u128, 21u128); @@ -1142,6 +1163,13 @@ mod tests { assert_eq!(Uint128::new(47031), res) } + #[test] + fn mul_ceil_works_with_decimal256() { + let decimal = Decimal256::from_ratio(8u128, 21u128); + let res = Uint128::new(123456).mul_ceil(decimal); // 47030.8571 + assert_eq!(Uint128::new(47031), res) + } + #[test] #[should_panic(expected = "ConversionOverflowError")] fn mul_ceil_panics_on_overflow() { From 70fc13509ad04d9845c8991257efb3261b0f08c4 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 4 Jan 2023 17:02:56 +0100 Subject: [PATCH 04/13] Macro & math updates --- packages/std/src/errors/mod.rs | 2 +- packages/std/src/errors/std_error.rs | 2 +- packages/std/src/lib.rs | 2 +- packages/std/src/math/decimal.rs | 4 +- packages/std/src/math/decimal256.rs | 4 +- packages/std/src/math/fraction.rs | 72 +++++++----------------- packages/std/src/math/mod.rs | 2 +- packages/std/src/math/uint128.rs | 82 ++++++++++++---------------- 8 files changed, 63 insertions(+), 107 deletions(-) diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index 01793bfe17..705382b732 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -5,7 +5,7 @@ mod verification_error; pub use recover_pubkey_error::RecoverPubkeyError; pub use std_error::{ - CheckedFromRatioError, CheckedMultiplyFractionalError, CheckedMultiplyRatioError, + CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult, }; diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs index 89df9b793a..79b6a82f27 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -557,7 +557,7 @@ impl DivideByZeroError { } #[derive(Error, Debug, PartialEq, Eq)] -pub enum CheckedMultiplyFractionalError { +pub enum CheckedMultiplyFractionError { #[error("{0}")] DivideByZero(#[from] DivideByZeroError), diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index af696d5d12..a15f0f4c62 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -46,7 +46,7 @@ pub use crate::ibc::{ #[cfg(feature = "iterator")] pub use crate::iterator::{Order, Record}; pub use crate::math::{ - Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fractional, Isqrt, Uint128, + Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128, Uint256, Uint512, Uint64, }; pub use crate::never::Never; diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index 867b8dc6bd..72975e22e3 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -12,7 +12,7 @@ use crate::errors::{ OverflowOperation, RoundUpOverflowError, StdError, }; -use super::Fractional; +use super::Fraction; use super::Isqrt; use super::{Uint128, Uint256}; @@ -359,7 +359,7 @@ impl Decimal { } } -impl Fractional for Decimal { +impl Fraction for Decimal { #[inline] fn numerator(&self) -> Uint128 { self.0 diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index 7d5c3c981f..0c2b134b5b 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -13,7 +13,7 @@ use crate::errors::{ }; use crate::{Decimal, Uint512}; -use super::Fractional; +use super::Fraction; use super::Isqrt; use super::Uint256; @@ -376,7 +376,7 @@ impl Decimal256 { } } -impl Fractional for Decimal256 { +impl Fraction for Decimal256 { #[inline] fn numerator(&self) -> Uint256 { self.0 diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index 10e824c8fe..7d6afedcba 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -1,12 +1,7 @@ -use serde::{Deserialize, Serialize}; - -use crate::errors::CheckedMultiplyFractionalError; -use crate::Uint512; - /// A fraction `p`/`q` with integers `p` and `q`. /// /// `p` is called the numerator and `q` is called the denominator. -pub trait Fractional: Sized { +pub trait Fraction: Sized { /// Returns the numerator `p` fn numerator(&self) -> T; /// Returns the denominator `q` @@ -18,16 +13,7 @@ pub trait Fractional: Sized { fn inv(&self) -> Option; } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct Fraction(T, T); - -impl Fraction { - pub fn new(numerator: T, denominator: T) -> Self { - Self(numerator, denominator) - } -} - -impl Fractional for Fraction { +impl Fraction for (T, T) { fn numerator(&self) -> T { self.0.clone() } @@ -37,66 +23,46 @@ impl Fractional for Fraction { } fn inv(&self) -> Option { - unimplemented!() + Some((self.1.clone(), self.0.clone())) } } -pub trait FractionMath { - fn checked_mul_floored, T: Into>( - self, - rhs: F, - ) -> Result - where - Self: Sized; - - fn mul_floored, T: Into>(self, rhs: F) -> Self; - - fn checked_mul_ceil + Clone, T: Into>( - self, - rhs: F, - ) -> Result - where - Self: Sized; - - fn mul_ceil + Clone, T: Into>(self, rhs: F) -> Self; -} - #[macro_export] -macro_rules! fraction_math { - ($name:ident) => { - impl FractionMath for $name { - fn checked_mul_floored, T: Into>( +macro_rules! impl_mul_fraction { + ($UintBase:ident, $UintLarger:ident) => { + impl $UintBase { + pub fn checked_mul_floored, T: Into<$UintBase>>( self, rhs: F, - ) -> Result { - let res = Uint512::from(self) - .checked_mul(rhs.numerator().into())? - .checked_div(rhs.denominator().into())?; + ) -> Result { + let res = self + .full_mul(rhs.numerator().into()) + .checked_div($UintLarger::from(rhs.denominator().into()))?; Ok(res.try_into()?) } - fn mul_floored, T: Into>(self, rhs: F) -> Self { + pub fn mul_floored, T: Into<$UintBase>>(self, rhs: F) -> Self { self.checked_mul_floored(rhs).unwrap() } - fn mul_ceil + Clone, T: Into>(self, rhs: F) -> Self { - self.checked_mul_ceil(rhs).unwrap() - } - - fn checked_mul_ceil + Clone, T: Into>( + pub fn checked_mul_ceil + Clone, T: Into<$UintBase>>( self, rhs: F, - ) -> Result { + ) -> Result { let floor_result = self.checked_mul_floored(rhs.clone())?; let numerator = rhs.numerator().into(); let denominator = rhs.denominator().into(); if !numerator.checked_rem(denominator)?.is_zero() { - let ceil_result = Uint512::one().checked_add(floor_result.into())?; + let ceil_result = $UintLarger::one().checked_add(floor_result.into())?; Ok(ceil_result.try_into()?) } else { Ok(floor_result) } } + + pub fn mul_ceil + Clone, T: Into<$UintBase>>(self, rhs: F) -> Self { + self.checked_mul_ceil(rhs).unwrap() + } } }; } diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs index 8151fd6f56..706d23005d 100644 --- a/packages/std/src/math/mod.rs +++ b/packages/std/src/math/mod.rs @@ -9,7 +9,7 @@ mod uint64; pub use decimal::{Decimal, DecimalRangeExceeded}; pub use decimal256::{Decimal256, Decimal256RangeExceeded}; -pub use fraction::Fractional; +pub use fraction::Fraction; pub use isqrt::Isqrt; pub use uint128::Uint128; pub use uint256::Uint256; diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index c455b6bfa7..48234453f8 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -8,12 +8,11 @@ use forward_ref::{forward_ref_binop, forward_ref_op_assign}; use schemars::JsonSchema; use serde::{de, ser, Deserialize, Deserializer, Serialize}; -use crate::errors::CheckedMultiplyFractionalError; use crate::errors::{ - CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, StdError, + CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + OverflowOperation, StdError, }; -use crate::math::fraction::FractionMath; -use crate::{fraction_math, ConversionOverflowError, Fractional, Uint256, Uint512, Uint64}; +use crate::{impl_mul_fraction, ConversionOverflowError, Fraction, Uint256, Uint64}; /// A thin wrapper around u128 that is using strings for JSON encoding/decoding, /// such that the full u128 range can be used for clients that convert JSON numbers to floats, @@ -233,7 +232,7 @@ impl Uint128 { } } -fraction_math!(Uint128); +impl_mul_fraction!(Uint128, Uint256); // `From` is implemented manually instead of // using `impl> From for Uint128` because @@ -542,9 +541,8 @@ impl PartialEq for &Uint128 { #[cfg(test)] mod tests { - use crate::errors::CheckedMultiplyFractionalError::{ConversionOverflow, DivideByZero}; - use crate::math::fraction::{Fraction, FractionMath}; - use crate::{from_slice, to_vec, Decimal, Decimal256}; + use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; + use crate::{from_slice, to_vec, Decimal}; use super::*; @@ -1050,30 +1048,33 @@ mod tests { #[test] fn mul_floored_works_with_zero() { - let fraction = Fraction::new(Uint128::zero(), Uint128::new(21)); + let fraction = (Uint128::zero(), Uint128::new(21)); let res = Uint128::new(123456).mul_floored(fraction); assert_eq!(Uint128::zero(), res) } #[test] fn mul_floored_does_nothing_with_one() { - let fraction = Fraction::new(Uint128::one(), Uint128::one()); + let fraction = (Uint128::one(), Uint128::one()); let res = Uint128::new(123456).mul_floored(fraction); assert_eq!(Uint128::new(123456), res) } #[test] fn mul_floored_rounds_down_with_normal_case() { - let fraction = Fraction::new(8u128, 21u128); + let fraction = (8u128, 21u128); let res = Uint128::new(123456).mul_floored(fraction); // 47030.8571 assert_eq!(Uint128::new(47030), res) } #[test] - fn mul_floored_works_with_higher_bit_sizes() { - let fraction = Fraction::new(Uint256::from(8u128), Uint256::from(21u128)); - let res = Uint128::new(123456).mul_floored(fraction); // 47030.8571 - assert_eq!(Uint128::new(47030), res) + fn mul_floored_works_when_operation_temporarily_takes_above_max() { + let fraction = (8u128, 21u128); + let res = Uint128::MAX.mul_floored(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.14285 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), + res + ) } #[test] @@ -1083,27 +1084,20 @@ mod tests { assert_eq!(Uint128::new(47030), res) } - #[test] - fn mul_floored_works_with_decimal256() { - let decimal = Decimal256::from_ratio(8u128, 21u128); - let res = Uint128::new(123456).mul_floored(decimal); // 47030.8571 - assert_eq!(Uint128::new(47030), res) - } - #[test] #[should_panic(expected = "ConversionOverflowError")] fn mul_floored_panics_on_overflow() { - let fraction = Fraction::new(21u128, 8u128); + let fraction = (21u128, 8u128); Uint128::MAX.mul_floored(fraction); } #[test] fn checked_mul_floored_does_not_panic_on_overflow() { - let fraction = Fraction::new(21u128, 8u128); + let fraction = (21u128, 8u128); assert_eq!( Uint128::MAX.checked_mul_floored(fraction), Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint512", + source_type: "Uint256", target_type: "Uint128", value: "893241213167463466591358344508391555069".to_string() })), @@ -1113,13 +1107,13 @@ mod tests { #[test] #[should_panic(expected = "DivideByZeroError")] fn mul_floored_panics_on_zero_div() { - let fraction = Fraction::new(21u128, 0u128); + let fraction = (21u128, 0u128); Uint128::new(123456).mul_floored(fraction); } #[test] fn checked_mul_floored_does_not_panic_on_zero_div() { - let fraction = Fraction::new(21u128, 0u128); + let fraction = (21u128, 0u128); assert_eq!( Uint128::new(123456).checked_mul_floored(fraction), Err(DivideByZero(DivideByZeroError { @@ -1130,30 +1124,33 @@ mod tests { #[test] fn mul_ceil_works_with_zero() { - let fraction = Fraction::new(Uint128::zero(), Uint128::new(21)); + let fraction = (Uint128::zero(), Uint128::new(21)); let res = Uint128::new(123456).mul_ceil(fraction); assert_eq!(Uint128::zero(), res) } #[test] fn mul_ceil_does_nothing_with_one() { - let fraction = Fraction::new(Uint128::one(), Uint128::one()); + let fraction = (Uint128::one(), Uint128::one()); let res = Uint128::new(123456).mul_ceil(fraction); assert_eq!(Uint128::new(123456), res) } #[test] fn mul_ceil_rounds_up_with_normal_case() { - let fraction = Fraction::new(8u128, 21u128); + let fraction = (8u128, 21u128); let res = Uint128::new(123456).mul_ceil(fraction); // 47030.8571 assert_eq!(Uint128::new(47031), res) } #[test] - fn mul_ceil_works_with_higher_bit_sizes() { - let fraction = Fraction::new(Uint256::from(8u128), Uint256::from(21u128)); - let res = Uint128::new(123456).mul_ceil(fraction); // 47030.8571 - assert_eq!(Uint128::new(47031), res) + fn mul_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = (8u128, 21u128); + let res = Uint128::MAX.mul_ceil(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.14285 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698), + res + ) } #[test] @@ -1163,27 +1160,20 @@ mod tests { assert_eq!(Uint128::new(47031), res) } - #[test] - fn mul_ceil_works_with_decimal256() { - let decimal = Decimal256::from_ratio(8u128, 21u128); - let res = Uint128::new(123456).mul_ceil(decimal); // 47030.8571 - assert_eq!(Uint128::new(47031), res) - } - #[test] #[should_panic(expected = "ConversionOverflowError")] fn mul_ceil_panics_on_overflow() { - let fraction = Fraction::new(21u128, 8u128); + let fraction = (21u128, 8u128); Uint128::MAX.mul_ceil(fraction); } #[test] fn checked_mul_ceil_does_not_panic_on_overflow() { - let fraction = Fraction::new(21u128, 8u128); + let fraction = (21u128, 8u128); assert_eq!( Uint128::MAX.checked_mul_ceil(fraction), Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint512", + source_type: "Uint256", target_type: "Uint128", value: "893241213167463466591358344508391555069".to_string() })), @@ -1193,13 +1183,13 @@ mod tests { #[test] #[should_panic(expected = "DivideByZeroError")] fn mul_ceil_panics_on_zero_div() { - let fraction = Fraction::new(21u128, 0u128); + let fraction = (21u128, 0u128); Uint128::new(123456).mul_ceil(fraction); } #[test] fn checked_mul_ceil_does_not_panic_on_zero_div() { - let fraction = Fraction::new(21u128, 0u128); + let fraction = (21u128, 0u128); assert_eq!( Uint128::new(123456).checked_mul_ceil(fraction), Err(DivideByZero(DivideByZeroError { From eddb6b30d49e5893ceafd4f15ae6af179b35d86d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 4 Jan 2023 22:16:37 +0100 Subject: [PATCH 05/13] extend macro to Uint64/256 --- packages/std/src/math/fraction.rs | 18 +-- packages/std/src/math/uint128.rs | 4 +- packages/std/src/math/uint256.rs | 187 +++++++++++++++++++++++++++++- packages/std/src/math/uint64.rs | 142 ++++++++++++++++++++++- 4 files changed, 333 insertions(+), 18 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index 7d6afedcba..eca7cea8f0 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -29,23 +29,24 @@ impl Fraction for (T, T) { #[macro_export] macro_rules! impl_mul_fraction { - ($UintBase:ident, $UintLarger:ident) => { - impl $UintBase { - pub fn checked_mul_floored, T: Into<$UintBase>>( + ($Uint:ident) => { + impl $Uint { + pub fn checked_mul_floored, T: Into<$Uint>>( self, rhs: F, ) -> Result { + let divisor = rhs.denominator().into(); let res = self .full_mul(rhs.numerator().into()) - .checked_div($UintLarger::from(rhs.denominator().into()))?; + .checked_div(divisor.into())?; Ok(res.try_into()?) } - pub fn mul_floored, T: Into<$UintBase>>(self, rhs: F) -> Self { + pub fn mul_floored, T: Into<$Uint>>(self, rhs: F) -> Self { self.checked_mul_floored(rhs).unwrap() } - pub fn checked_mul_ceil + Clone, T: Into<$UintBase>>( + pub fn checked_mul_ceil + Clone, T: Into<$Uint>>( self, rhs: F, ) -> Result { @@ -53,14 +54,13 @@ macro_rules! impl_mul_fraction { let numerator = rhs.numerator().into(); let denominator = rhs.denominator().into(); if !numerator.checked_rem(denominator)?.is_zero() { - let ceil_result = $UintLarger::one().checked_add(floor_result.into())?; - Ok(ceil_result.try_into()?) + Ok($Uint::one().checked_add(floor_result)?) } else { Ok(floor_result) } } - pub fn mul_ceil + Clone, T: Into<$UintBase>>(self, rhs: F) -> Self { + pub fn mul_ceil + Clone, T: Into<$Uint>>(self, rhs: F) -> Self { self.checked_mul_ceil(rhs).unwrap() } } diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 48234453f8..1372c64aaf 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -232,7 +232,7 @@ impl Uint128 { } } -impl_mul_fraction!(Uint128, Uint256); +impl_mul_fraction!(Uint128); // `From` is implemented manually instead of // using `impl> From for Uint128` because @@ -1175,7 +1175,7 @@ mod tests { Err(ConversionOverflow(ConversionOverflowError { source_type: "Uint256", target_type: "Uint128", - value: "893241213167463466591358344508391555069".to_string() + value: "893241213167463466591358344508391555069".to_string() // raises prior to rounding up })), ); } diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index 743281735d..f7ad54e32c 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -9,10 +9,10 @@ use std::ops::{ use std::str::FromStr; use crate::errors::{ - CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError, OverflowError, - OverflowOperation, StdError, + CheckedMultiplyFractionError, CheckedMultiplyRatioError, ConversionOverflowError, + DivideByZeroError, OverflowError, OverflowOperation, StdError, }; -use crate::{Uint128, Uint512, Uint64}; +use crate::{impl_mul_fraction, Fraction, Uint128, Uint512, Uint64}; /// This module is purely a workaround that lets us ignore lints for all the code /// the `construct_uint!` macro generates. @@ -336,6 +336,8 @@ impl Uint256 { } } +impl_mul_fraction!(Uint256); + impl From for Uint256 { fn from(val: Uint128) -> Self { val.u128().into() @@ -666,7 +668,8 @@ impl PartialEq for &Uint256 { #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; + use crate::{from_slice, to_vec, Decimal, Decimal256}; #[test] fn size_of_works() { @@ -1664,4 +1667,180 @@ mod tests { assert_eq!(&lhs == &rhs, expected); } } + + #[test] + fn mul_floored_works_with_zero() { + let fraction = (Uint256::zero(), Uint256::from(21u32)); + let res = Uint256::from(123456u32).mul_floored(fraction); + assert_eq!(Uint256::zero(), res) + } + + #[test] + fn mul_floored_does_nothing_with_one() { + let fraction = (Uint256::one(), Uint256::one()); + let res = Uint256::from(123456u32).mul_floored(fraction); + assert_eq!(Uint256::from(123456u32), res) + } + + #[test] + fn mul_floored_rounds_down_with_normal_case() { + let fraction = (Uint256::from(8u128), Uint256::from(21u128)); + let res = Uint256::from(123456u32).mul_floored(fraction); // 47030.8571 + assert_eq!(Uint256::from(47030u32), res) + } + + #[test] + fn mul_floored_works_when_operation_temporarily_takes_above_max() { + let fraction = (8u128, 21u128); + let res = Uint256::MAX.mul_floored(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571 + assert_eq!( + Uint256::from_str( + "44111272090406169685169899050928726801245708444053548205507651050633573196165" + ) + .unwrap(), + res + ) + } + + #[test] + fn mul_floored_works_with_decimal() { + let decimal = Decimal::from_ratio(8u128, 21u128); + let res = Uint256::from(123456u32).mul_floored(decimal); // 47030.8571 + assert_eq!(Uint256::from(47030u32), res) + } + + #[test] + fn mul_floored_works_with_decimal256() { + let decimal = Decimal256::from_ratio(8u128, 21u128); + let res = Uint256::from(123456u32).mul_floored(decimal); // 47030.8571 + assert_eq!(Uint256::from(47030u32), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn mul_floored_panics_on_overflow() { + let fraction = (21u128, 8u128); + Uint256::MAX.mul_floored(fraction); + } + + #[test] + fn checked_mul_floored_does_not_panic_on_overflow() { + let fraction = (21u128, 8u128); + assert_eq!( + Uint256::MAX.checked_mul_floored(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint512", + target_type: "Uint256", + value: + "303954234247955012986873835647805758114833709747306480603576158020771965304829" + .to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn mul_floored_panics_on_zero_div() { + let fraction = (21u128, 0u128); + Uint256::from(123456u32).mul_floored(fraction); + } + + #[test] + fn checked_mul_floored_does_not_panic_on_zero_div() { + let fraction = (21u128, 0u128); + assert_eq!( + Uint256::from(123456u32).checked_mul_floored(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); + } + + #[test] + fn mul_ceil_works_with_zero() { + let fraction = (Uint256::zero(), Uint256::from(21u32)); + let res = Uint256::from(123456u32).mul_ceil(fraction); + assert_eq!(Uint256::zero(), res) + } + + #[test] + fn mul_ceil_does_nothing_with_one() { + let fraction = (Uint256::one(), Uint256::one()); + let res = Uint256::from(123456u32).mul_ceil(fraction); + assert_eq!(Uint256::from(123456u32), res) + } + + #[test] + fn mul_ceil_rounds_up_with_normal_case() { + let fraction = (8u128, 21u128); + let res = Uint256::from(123456u32).mul_ceil(fraction); // 47030.8571 + assert_eq!(Uint256::from(47031u32), res) + } + + #[test] + fn mul_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = (8u128, 21u128); + let res = Uint256::MAX.mul_ceil(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571 + assert_eq!( + Uint256::from_str( + "44111272090406169685169899050928726801245708444053548205507651050633573196166" + ) + .unwrap(), + res + ) + } + + #[test] + fn mul_ceil_works_with_decimal() { + let decimal = Decimal::from_ratio(8u128, 21u128); + let res = Uint256::from(123456u32).mul_ceil(decimal); // 47030.8571 + assert_eq!(Uint256::from(47031u32), res) + } + + #[test] + fn mul_ceil_works_with_decimal256() { + let decimal = Decimal256::from_ratio(8u128, 21u128); + let res = Uint256::from(123456u32).mul_ceil(decimal); // 47030.8571 + assert_eq!(Uint256::from(47031u32), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn mul_ceil_panics_on_overflow() { + let fraction = (21u128, 8u128); + Uint256::MAX.mul_ceil(fraction); + } + + #[test] + fn checked_mul_ceil_does_not_panic_on_overflow() { + let fraction = (21u128, 8u128); + assert_eq!( + Uint256::MAX.checked_mul_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint512", + target_type: "Uint256", + value: + "303954234247955012986873835647805758114833709747306480603576158020771965304829" // raises prior to rounding up + .to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn mul_ceil_panics_on_zero_div() { + let fraction = (21u128, 0u128); + Uint256::from(123456u32).mul_ceil(fraction); + } + + #[test] + fn checked_mul_ceil_does_not_panic_on_zero_div() { + let fraction = (21u128, 0u128); + assert_eq!( + Uint256::from(123456u32).checked_mul_ceil(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); + } } diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index 97032633e2..afe0baeb58 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -7,9 +7,10 @@ use std::ops::{ }; use crate::errors::{ - CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, StdError, + CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + OverflowOperation, StdError, }; -use crate::Uint128; +use crate::{impl_mul_fraction, Fraction, Uint128}; /// A thin wrapper around u64 that is using strings for JSON encoding/decoding, /// such that the full u64 range can be used for clients that convert JSON numbers to floats, @@ -226,6 +227,8 @@ impl Uint64 { } } +impl_mul_fraction!(Uint64); + // `From` is implemented manually instead of // using `impl> From for Uint64` because // of the conflict with `TryFrom<&str>` as described here @@ -492,7 +495,8 @@ impl PartialEq for &Uint64 { #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; + use crate::{from_slice, to_vec, ConversionOverflowError}; #[test] fn size_of_works() { @@ -955,4 +959,136 @@ mod tests { assert_eq!(&lhs == &rhs, expected); } } + + #[test] + fn mul_floored_works_with_zero() { + let fraction = (0u32, 21u32); + let res = Uint64::new(123456).mul_floored(fraction); + assert_eq!(Uint64::zero(), res) + } + + #[test] + fn mul_floored_does_nothing_with_one() { + let fraction = (Uint64::one(), Uint64::one()); + let res = Uint64::new(123456).mul_floored(fraction); + assert_eq!(Uint64::new(123456), res) + } + + #[test] + fn mul_floored_rounds_down_with_normal_case() { + let fraction = (8u64, 21u64); + let res = Uint64::new(123456).mul_floored(fraction); // 47030.8571 + assert_eq!(Uint64::new(47030), res) + } + + #[test] + fn mul_floored_works_when_operation_temporarily_takes_above_max() { + let fraction = (8u64, 21u64); + let res = Uint64::MAX.mul_floored(fraction); // 7_027_331_075_698_876_805.71428571 + assert_eq!(Uint64::new(7_027_331_075_698_876_805), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn mul_floored_panics_on_overflow() { + let fraction = (21u64, 8u64); + Uint64::MAX.mul_floored(fraction); + } + + #[test] + fn checked_mul_floored_does_not_panic_on_overflow() { + let fraction = (21u64, 8u64); + assert_eq!( + Uint64::MAX.checked_mul_floored(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint128", + target_type: "Uint64", + value: "48422703193487572989".to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn mul_floored_panics_on_zero_div() { + let fraction = (21u64, 0u64); + Uint64::new(123456).mul_floored(fraction); + } + + #[test] + fn checked_mul_floored_does_not_panic_on_zero_div() { + let fraction = (21u64, 0u64); + assert_eq!( + Uint64::new(123456).checked_mul_floored(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); + } + + #[test] + fn mul_ceil_works_with_zero() { + let fraction = (Uint64::zero(), Uint64::new(21)); + let res = Uint64::new(123456).mul_ceil(fraction); + assert_eq!(Uint64::zero(), res) + } + + #[test] + fn mul_ceil_does_nothing_with_one() { + let fraction = (Uint64::one(), Uint64::one()); + let res = Uint64::new(123456).mul_ceil(fraction); + assert_eq!(Uint64::new(123456), res) + } + + #[test] + fn mul_ceil_rounds_up_with_normal_case() { + let fraction = (8u64, 21u64); + let res = Uint64::new(123456).mul_ceil(fraction); // 47030.8571 + assert_eq!(Uint64::new(47031), res) + } + + #[test] + fn mul_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = (8u64, 21u64); + let res = Uint64::MAX.mul_ceil(fraction); // 7_027_331_075_698_876_805.71428571 + assert_eq!(Uint64::new(7_027_331_075_698_876_806), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn mul_ceil_panics_on_overflow() { + let fraction = (21u64, 8u64); + Uint64::MAX.mul_ceil(fraction); + } + + #[test] + fn checked_mul_ceil_does_not_panic_on_overflow() { + let fraction = (21u64, 8u64); + assert_eq!( + Uint64::MAX.checked_mul_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint128", + target_type: "Uint64", + value: "48422703193487572989".to_string() // raises prior to rounding up + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn mul_ceil_panics_on_zero_div() { + let fraction = (21u64, 0u64); + Uint64::new(123456).mul_ceil(fraction); + } + + #[test] + fn checked_mul_ceil_does_not_panic_on_zero_div() { + let fraction = (21u64, 0u64); + assert_eq!( + Uint64::new(123456).checked_mul_ceil(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); + } } From 9a51c7c9c74eb02212d5fe0770b65e409e5d8cc6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Sat, 7 Jan 2023 00:46:11 +0100 Subject: [PATCH 06/13] tests + trait update --- packages/std/src/math/fraction.rs | 39 +++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index eca7cea8f0..6b2762f868 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -1,3 +1,5 @@ +use crate::Uint256; + /// A fraction `p`/`q` with integers `p` and `q`. /// /// `p` is called the numerator and `q` is called the denominator. @@ -13,17 +15,21 @@ pub trait Fraction: Sized { fn inv(&self) -> Option; } -impl Fraction for (T, T) { +impl> Fraction for (T, T) { fn numerator(&self) -> T { - self.0.clone() + self.0 } fn denominator(&self) -> T { - self.1.clone() + self.1 } fn inv(&self) -> Option { - Some((self.1.clone(), self.0.clone())) + if self.numerator().into() == Uint256::zero() { + None + } else { + Some((self.1, self.0)) + } } } @@ -46,13 +52,13 @@ macro_rules! impl_mul_fraction { self.checked_mul_floored(rhs).unwrap() } - pub fn checked_mul_ceil + Clone, T: Into<$Uint>>( + pub fn checked_mul_ceil, T: Into<$Uint>>( self, rhs: F, ) -> Result { - let floor_result = self.checked_mul_floored(rhs.clone())?; let numerator = rhs.numerator().into(); let denominator = rhs.denominator().into(); + let floor_result = self.checked_mul_floored(rhs)?; if !numerator.checked_rem(denominator)?.is_zero() { Ok($Uint::one().checked_add(floor_result)?) } else { @@ -60,9 +66,28 @@ macro_rules! impl_mul_fraction { } } - pub fn mul_ceil + Clone, T: Into<$Uint>>(self, rhs: F) -> Self { + pub fn mul_ceil, T: Into<$Uint>>(self, rhs: F) -> Self { self.checked_mul_ceil(rhs).unwrap() } } }; } + +#[cfg(test)] +mod tests { + use crate::{Fraction, Uint128, Uint64}; + + #[test] + fn fraction_tuple_methods() { + let fraction = (Uint64::one(), Uint64::new(2)); + assert_eq!(Uint64::one(), fraction.numerator()); + assert_eq!(Uint64::new(2), fraction.denominator()); + assert_eq!(Some((Uint64::new(2), Uint64::one())), fraction.inv()); + } + + #[test] + fn inverse_with_zero_denominator() { + let fraction = (Uint128::zero(), Uint128::one()); + assert_eq!(None, fraction.inv()); + } +} From df4503ce11984e96c8f2fbb08123c7ff6e8498c7 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Sun, 8 Jan 2023 11:16:33 +0100 Subject: [PATCH 07/13] Ensure no rounds on even divides --- packages/std/src/math/fraction.rs | 8 +++++--- packages/std/src/math/uint128.rs | 14 ++++++++++++++ packages/std/src/math/uint256.rs | 14 ++++++++++++++ packages/std/src/math/uint64.rs | 14 ++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index 6b2762f868..bd8762fac5 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -56,10 +56,12 @@ macro_rules! impl_mul_fraction { self, rhs: F, ) -> Result { - let numerator = rhs.numerator().into(); - let denominator = rhs.denominator().into(); + let divisor = rhs.denominator().into(); + let remainder = self + .full_mul(rhs.numerator().into()) + .checked_rem(divisor.into())?; let floor_result = self.checked_mul_floored(rhs)?; - if !numerator.checked_rem(denominator)?.is_zero() { + if !remainder.is_zero() { Ok($Uint::one().checked_add(floor_result)?) } else { Ok(floor_result) diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 1372c64aaf..390880398a 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -1067,6 +1067,13 @@ mod tests { assert_eq!(Uint128::new(47030), res) } + #[test] + fn mul_floored_does_not_round_on_even_divide() { + let fraction = (2u128, 5u128); + let res = Uint128::new(25).mul_floored(fraction); + assert_eq!(Uint128::new(10), res) + } + #[test] fn mul_floored_works_when_operation_temporarily_takes_above_max() { let fraction = (8u128, 21u128); @@ -1143,6 +1150,13 @@ mod tests { assert_eq!(Uint128::new(47031), res) } + #[test] + fn mul_ceil_does_not_round_on_even_divide() { + let fraction = (2u128, 5u128); + let res = Uint128::new(25).mul_ceil(fraction); + assert_eq!(Uint128::new(10), res) + } + #[test] fn mul_ceil_works_when_operation_temporarily_takes_above_max() { let fraction = (8u128, 21u128); diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index f7ad54e32c..68c9bf0e71 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -1689,6 +1689,13 @@ mod tests { assert_eq!(Uint256::from(47030u32), res) } + #[test] + fn mul_floored_does_not_round_on_even_divide() { + let fraction = (2u128, 5u128); + let res = Uint256::from(25u32).mul_floored(fraction); + assert_eq!(Uint256::from(10u32), res) + } + #[test] fn mul_floored_works_when_operation_temporarily_takes_above_max() { let fraction = (8u128, 21u128); @@ -1777,6 +1784,13 @@ mod tests { assert_eq!(Uint256::from(47031u32), res) } + #[test] + fn mul_ceil_does_not_round_on_even_divide() { + let fraction = (2u128, 5u128); + let res = Uint256::from(25u32).mul_ceil(fraction); + assert_eq!(Uint256::from(10u32), res) + } + #[test] fn mul_ceil_works_when_operation_temporarily_takes_above_max() { let fraction = (8u128, 21u128); diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index afe0baeb58..2600dbab94 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -981,6 +981,13 @@ mod tests { assert_eq!(Uint64::new(47030), res) } + #[test] + fn mul_floored_does_not_round_on_even_divide() { + let fraction = (2u64, 5u64); + let res = Uint64::new(25).mul_floored(fraction); + assert_eq!(Uint64::new(10), res) + } + #[test] fn mul_floored_works_when_operation_temporarily_takes_above_max() { let fraction = (8u64, 21u64); @@ -1047,6 +1054,13 @@ mod tests { assert_eq!(Uint64::new(47031), res) } + #[test] + fn mul_ceil_does_not_round_on_even_divide() { + let fraction = (2u64, 5u64); + let res = Uint64::new(25).mul_ceil(fraction); + assert_eq!(Uint64::new(10), res) + } + #[test] fn mul_ceil_works_when_operation_temporarily_takes_above_max() { let fraction = (8u64, 21u64); From cdc653dd7524720391625fc6d4857245a16199d5 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Sun, 8 Jan 2023 11:17:53 +0100 Subject: [PATCH 08/13] Improve zero denom check --- packages/std/src/math/fraction.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index bd8762fac5..5f8a117e92 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -1,5 +1,3 @@ -use crate::Uint256; - /// A fraction `p`/`q` with integers `p` and `q`. /// /// `p` is called the numerator and `q` is called the denominator. @@ -15,7 +13,7 @@ pub trait Fraction: Sized { fn inv(&self) -> Option; } -impl> Fraction for (T, T) { +impl + PartialEq> Fraction for (T, T) { fn numerator(&self) -> T { self.0 } @@ -25,7 +23,7 @@ impl> Fraction for (T, T) { } fn inv(&self) -> Option { - if self.numerator().into() == Uint256::zero() { + if self.numerator() == 0u8.into() { None } else { Some((self.1, self.0)) From 8b63fcd4faef054fe71d9edfbab15582d643adb9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Sun, 8 Jan 2023 12:07:58 +0100 Subject: [PATCH 09/13] make floor/ceil tenses consistent --- packages/std/src/math/fraction.rs | 8 +++--- packages/std/src/math/uint128.rs | 40 ++++++++++++++-------------- packages/std/src/math/uint256.rs | 44 +++++++++++++++---------------- packages/std/src/math/uint64.rs | 36 ++++++++++++------------- 4 files changed, 64 insertions(+), 64 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index 5f8a117e92..e170869bf3 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -35,7 +35,7 @@ impl + PartialEq> Fraction for (T, T) { macro_rules! impl_mul_fraction { ($Uint:ident) => { impl $Uint { - pub fn checked_mul_floored, T: Into<$Uint>>( + pub fn checked_mul_floor, T: Into<$Uint>>( self, rhs: F, ) -> Result { @@ -46,8 +46,8 @@ macro_rules! impl_mul_fraction { Ok(res.try_into()?) } - pub fn mul_floored, T: Into<$Uint>>(self, rhs: F) -> Self { - self.checked_mul_floored(rhs).unwrap() + pub fn mul_floor, T: Into<$Uint>>(self, rhs: F) -> Self { + self.checked_mul_floor(rhs).unwrap() } pub fn checked_mul_ceil, T: Into<$Uint>>( @@ -58,7 +58,7 @@ macro_rules! impl_mul_fraction { let remainder = self .full_mul(rhs.numerator().into()) .checked_rem(divisor.into())?; - let floor_result = self.checked_mul_floored(rhs)?; + let floor_result = self.checked_mul_floor(rhs)?; if !remainder.is_zero() { Ok($Uint::one().checked_add(floor_result)?) } else { diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 390880398a..9bc21ac54d 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -1047,37 +1047,37 @@ mod tests { } #[test] - fn mul_floored_works_with_zero() { + fn mul_floor_works_with_zero() { let fraction = (Uint128::zero(), Uint128::new(21)); - let res = Uint128::new(123456).mul_floored(fraction); + let res = Uint128::new(123456).mul_floor(fraction); assert_eq!(Uint128::zero(), res) } #[test] - fn mul_floored_does_nothing_with_one() { + fn mul_floor_does_nothing_with_one() { let fraction = (Uint128::one(), Uint128::one()); - let res = Uint128::new(123456).mul_floored(fraction); + let res = Uint128::new(123456).mul_floor(fraction); assert_eq!(Uint128::new(123456), res) } #[test] - fn mul_floored_rounds_down_with_normal_case() { + fn mul_floor_rounds_down_with_normal_case() { let fraction = (8u128, 21u128); - let res = Uint128::new(123456).mul_floored(fraction); // 47030.8571 + let res = Uint128::new(123456).mul_floor(fraction); // 47030.8571 assert_eq!(Uint128::new(47030), res) } #[test] - fn mul_floored_does_not_round_on_even_divide() { + fn mul_floor_does_not_round_on_even_divide() { let fraction = (2u128, 5u128); - let res = Uint128::new(25).mul_floored(fraction); + let res = Uint128::new(25).mul_floor(fraction); assert_eq!(Uint128::new(10), res) } #[test] - fn mul_floored_works_when_operation_temporarily_takes_above_max() { + fn mul_floor_works_when_operation_temporarily_takes_above_max() { let fraction = (8u128, 21u128); - let res = Uint128::MAX.mul_floored(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.14285 + let res = Uint128::MAX.mul_floor(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.14285 assert_eq!( Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), res @@ -1085,24 +1085,24 @@ mod tests { } #[test] - fn mul_floored_works_with_decimal() { + fn mul_floor_works_with_decimal() { let decimal = Decimal::from_ratio(8u128, 21u128); - let res = Uint128::new(123456).mul_floored(decimal); // 47030.8571 + let res = Uint128::new(123456).mul_floor(decimal); // 47030.8571 assert_eq!(Uint128::new(47030), res) } #[test] #[should_panic(expected = "ConversionOverflowError")] - fn mul_floored_panics_on_overflow() { + fn mul_floor_panics_on_overflow() { let fraction = (21u128, 8u128); - Uint128::MAX.mul_floored(fraction); + Uint128::MAX.mul_floor(fraction); } #[test] - fn checked_mul_floored_does_not_panic_on_overflow() { + fn checked_mul_floor_does_not_panic_on_overflow() { let fraction = (21u128, 8u128); assert_eq!( - Uint128::MAX.checked_mul_floored(fraction), + Uint128::MAX.checked_mul_floor(fraction), Err(ConversionOverflow(ConversionOverflowError { source_type: "Uint256", target_type: "Uint128", @@ -1113,16 +1113,16 @@ mod tests { #[test] #[should_panic(expected = "DivideByZeroError")] - fn mul_floored_panics_on_zero_div() { + fn mul_floor_panics_on_zero_div() { let fraction = (21u128, 0u128); - Uint128::new(123456).mul_floored(fraction); + Uint128::new(123456).mul_floor(fraction); } #[test] - fn checked_mul_floored_does_not_panic_on_zero_div() { + fn checked_mul_floor_does_not_panic_on_zero_div() { let fraction = (21u128, 0u128); assert_eq!( - Uint128::new(123456).checked_mul_floored(fraction), + Uint128::new(123456).checked_mul_floor(fraction), Err(DivideByZero(DivideByZeroError { operand: "2592576".to_string() })), diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index 68c9bf0e71..664cdb8651 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -1669,37 +1669,37 @@ mod tests { } #[test] - fn mul_floored_works_with_zero() { + fn mul_floor_works_with_zero() { let fraction = (Uint256::zero(), Uint256::from(21u32)); - let res = Uint256::from(123456u32).mul_floored(fraction); + let res = Uint256::from(123456u32).mul_floor(fraction); assert_eq!(Uint256::zero(), res) } #[test] - fn mul_floored_does_nothing_with_one() { + fn mul_floor_does_nothing_with_one() { let fraction = (Uint256::one(), Uint256::one()); - let res = Uint256::from(123456u32).mul_floored(fraction); + let res = Uint256::from(123456u32).mul_floor(fraction); assert_eq!(Uint256::from(123456u32), res) } #[test] - fn mul_floored_rounds_down_with_normal_case() { + fn mul_floor_rounds_down_with_normal_case() { let fraction = (Uint256::from(8u128), Uint256::from(21u128)); - let res = Uint256::from(123456u32).mul_floored(fraction); // 47030.8571 + let res = Uint256::from(123456u32).mul_floor(fraction); // 47030.8571 assert_eq!(Uint256::from(47030u32), res) } #[test] - fn mul_floored_does_not_round_on_even_divide() { + fn mul_floor_does_not_round_on_even_divide() { let fraction = (2u128, 5u128); - let res = Uint256::from(25u32).mul_floored(fraction); + let res = Uint256::from(25u32).mul_floor(fraction); assert_eq!(Uint256::from(10u32), res) } #[test] - fn mul_floored_works_when_operation_temporarily_takes_above_max() { + fn mul_floor_works_when_operation_temporarily_takes_above_max() { let fraction = (8u128, 21u128); - let res = Uint256::MAX.mul_floored(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571 + let res = Uint256::MAX.mul_floor(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571 assert_eq!( Uint256::from_str( "44111272090406169685169899050928726801245708444053548205507651050633573196165" @@ -1710,31 +1710,31 @@ mod tests { } #[test] - fn mul_floored_works_with_decimal() { + fn mul_floor_works_with_decimal() { let decimal = Decimal::from_ratio(8u128, 21u128); - let res = Uint256::from(123456u32).mul_floored(decimal); // 47030.8571 + let res = Uint256::from(123456u32).mul_floor(decimal); // 47030.8571 assert_eq!(Uint256::from(47030u32), res) } #[test] - fn mul_floored_works_with_decimal256() { + fn mul_floor_works_with_decimal256() { let decimal = Decimal256::from_ratio(8u128, 21u128); - let res = Uint256::from(123456u32).mul_floored(decimal); // 47030.8571 + let res = Uint256::from(123456u32).mul_floor(decimal); // 47030.8571 assert_eq!(Uint256::from(47030u32), res) } #[test] #[should_panic(expected = "ConversionOverflowError")] - fn mul_floored_panics_on_overflow() { + fn mul_floor_panics_on_overflow() { let fraction = (21u128, 8u128); - Uint256::MAX.mul_floored(fraction); + Uint256::MAX.mul_floor(fraction); } #[test] - fn checked_mul_floored_does_not_panic_on_overflow() { + fn checked_mul_floor_does_not_panic_on_overflow() { let fraction = (21u128, 8u128); assert_eq!( - Uint256::MAX.checked_mul_floored(fraction), + Uint256::MAX.checked_mul_floor(fraction), Err(ConversionOverflow(ConversionOverflowError { source_type: "Uint512", target_type: "Uint256", @@ -1747,16 +1747,16 @@ mod tests { #[test] #[should_panic(expected = "DivideByZeroError")] - fn mul_floored_panics_on_zero_div() { + fn mul_floor_panics_on_zero_div() { let fraction = (21u128, 0u128); - Uint256::from(123456u32).mul_floored(fraction); + Uint256::from(123456u32).mul_floor(fraction); } #[test] - fn checked_mul_floored_does_not_panic_on_zero_div() { + fn checked_mul_floor_does_not_panic_on_zero_div() { let fraction = (21u128, 0u128); assert_eq!( - Uint256::from(123456u32).checked_mul_floored(fraction), + Uint256::from(123456u32).checked_mul_floor(fraction), Err(DivideByZero(DivideByZeroError { operand: "2592576".to_string() })), diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index 2600dbab94..7cba8cb0e9 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -961,52 +961,52 @@ mod tests { } #[test] - fn mul_floored_works_with_zero() { + fn mul_floor_works_with_zero() { let fraction = (0u32, 21u32); - let res = Uint64::new(123456).mul_floored(fraction); + let res = Uint64::new(123456).mul_floor(fraction); assert_eq!(Uint64::zero(), res) } #[test] - fn mul_floored_does_nothing_with_one() { + fn mul_floor_does_nothing_with_one() { let fraction = (Uint64::one(), Uint64::one()); - let res = Uint64::new(123456).mul_floored(fraction); + let res = Uint64::new(123456).mul_floor(fraction); assert_eq!(Uint64::new(123456), res) } #[test] - fn mul_floored_rounds_down_with_normal_case() { + fn mul_floor_rounds_down_with_normal_case() { let fraction = (8u64, 21u64); - let res = Uint64::new(123456).mul_floored(fraction); // 47030.8571 + let res = Uint64::new(123456).mul_floor(fraction); // 47030.8571 assert_eq!(Uint64::new(47030), res) } #[test] - fn mul_floored_does_not_round_on_even_divide() { + fn mul_floor_does_not_round_on_even_divide() { let fraction = (2u64, 5u64); - let res = Uint64::new(25).mul_floored(fraction); + let res = Uint64::new(25).mul_floor(fraction); assert_eq!(Uint64::new(10), res) } #[test] - fn mul_floored_works_when_operation_temporarily_takes_above_max() { + fn mul_floor_works_when_operation_temporarily_takes_above_max() { let fraction = (8u64, 21u64); - let res = Uint64::MAX.mul_floored(fraction); // 7_027_331_075_698_876_805.71428571 + let res = Uint64::MAX.mul_floor(fraction); // 7_027_331_075_698_876_805.71428571 assert_eq!(Uint64::new(7_027_331_075_698_876_805), res) } #[test] #[should_panic(expected = "ConversionOverflowError")] - fn mul_floored_panics_on_overflow() { + fn mul_floor_panics_on_overflow() { let fraction = (21u64, 8u64); - Uint64::MAX.mul_floored(fraction); + Uint64::MAX.mul_floor(fraction); } #[test] - fn checked_mul_floored_does_not_panic_on_overflow() { + fn checked_mul_floor_does_not_panic_on_overflow() { let fraction = (21u64, 8u64); assert_eq!( - Uint64::MAX.checked_mul_floored(fraction), + Uint64::MAX.checked_mul_floor(fraction), Err(ConversionOverflow(ConversionOverflowError { source_type: "Uint128", target_type: "Uint64", @@ -1017,16 +1017,16 @@ mod tests { #[test] #[should_panic(expected = "DivideByZeroError")] - fn mul_floored_panics_on_zero_div() { + fn mul_floor_panics_on_zero_div() { let fraction = (21u64, 0u64); - Uint64::new(123456).mul_floored(fraction); + Uint64::new(123456).mul_floor(fraction); } #[test] - fn checked_mul_floored_does_not_panic_on_zero_div() { + fn checked_mul_floor_does_not_panic_on_zero_div() { let fraction = (21u64, 0u64); assert_eq!( - Uint64::new(123456).checked_mul_floored(fraction), + Uint64::new(123456).checked_mul_floor(fraction), Err(DivideByZero(DivideByZeroError { operand: "2592576".to_string() })), From e79ab95eef56cfb881e39c4c430a3d6f1322cbc4 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 12 Jan 2023 11:10:33 +0100 Subject: [PATCH 10/13] Add fraction division support --- packages/std/src/math/fraction.rs | 47 +++++++++ packages/std/src/math/uint128.rs | 142 ++++++++++++++++++++++++++++ packages/std/src/math/uint256.rs | 152 ++++++++++++++++++++++++++++++ packages/std/src/math/uint64.rs | 110 +++++++++++++++++++++ 4 files changed, 451 insertions(+) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index e170869bf3..de3a85a0d5 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -69,6 +69,53 @@ macro_rules! impl_mul_fraction { pub fn mul_ceil, T: Into<$Uint>>(self, rhs: F) -> Self { self.checked_mul_ceil(rhs).unwrap() } + + pub fn div_floor, T: Into<$Uint>>(self, rhs: F) -> Self + where + Self: Sized, + { + self.checked_div_floor(rhs).unwrap() + } + + pub fn checked_div_floor, T: Into<$Uint>>( + self, + rhs: F, + ) -> Result + where + Self: Sized, + { + let divisor = rhs.numerator().into(); + let res = self + .full_mul(rhs.denominator().into()) + .checked_div(divisor.into())?; + Ok(res.try_into()?) + } + + pub fn div_ceil, T: Into<$Uint>>(self, rhs: F) -> Self + where + Self: Sized, + { + self.checked_div_ceil(rhs).unwrap() + } + + pub fn checked_div_ceil, T: Into<$Uint>>( + self, + rhs: F, + ) -> Result + where + Self: Sized, + { + let divisor = rhs.numerator().into(); + let remainder = self + .full_mul(rhs.denominator().into()) + .checked_rem(divisor.into())?; + let floor_result = self.checked_div_floor(rhs)?; + if !remainder.is_zero() { + Ok($Uint::one().checked_add(floor_result)?) + } else { + Ok(floor_result) + } + } } }; } diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 9bc21ac54d..2296f40bff 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -1211,4 +1211,146 @@ mod tests { })), ); } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn div_floor_raises_with_zero() { + let fraction = (Uint128::zero(), Uint128::new(21)); + Uint128::new(123456).div_floor(fraction); + } + + #[test] + fn div_floor_does_nothing_with_one() { + let fraction = (Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).div_floor(fraction); + assert_eq!(Uint128::new(123456), res) + } + + #[test] + fn div_floor_rounds_down_with_normal_case() { + let fraction = (5u128, 21u128); + let res = Uint128::new(123456).div_floor(fraction); // 518515.2 + assert_eq!(Uint128::new(518515), res) + } + + #[test] + fn div_floor_does_not_round_on_even_divide() { + let fraction = (5u128, 2u128); + let res = Uint128::new(25).div_floor(fraction); + assert_eq!(Uint128::new(10), res) + } + + #[test] + fn div_floor_works_when_operation_temporarily_takes_above_max() { + let fraction = (21u128, 8u128); + let res = Uint128::MAX.div_floor(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), + res + ) + } + + #[test] + fn div_floor_works_with_decimal() { + let decimal = Decimal::from_ratio(21u128, 8u128); + let res = Uint128::new(123456).div_floor(decimal); // 47030.8571 + assert_eq!(Uint128::new(47030), res) + } + + #[test] + fn div_floor_works_with_decimal_evenly() { + let res = Uint128::new(60).div_floor(Decimal::from_atomics(6u128, 0).unwrap()); + assert_eq!(res, Uint128::new(10)); + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn div_floor_panics_on_overflow() { + let fraction = (8u128, 21u128); + Uint128::MAX.div_floor(fraction); + } + + #[test] + fn div_floor_does_not_panic_on_overflow() { + let fraction = (8u128, 21u128); + assert_eq!( + Uint128::MAX.checked_div_floor(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn div_ceil_raises_with_zero() { + let fraction = (Uint128::zero(), Uint128::new(21)); + Uint128::new(123456).div_ceil(fraction); + } + + #[test] + fn div_ceil_does_nothing_with_one() { + let fraction = (Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).div_ceil(fraction); + assert_eq!(Uint128::new(123456), res) + } + + #[test] + fn div_ceil_rounds_up_with_normal_case() { + let fraction = (5u128, 21u128); + let res = Uint128::new(123456).div_ceil(fraction); // 518515.2 + assert_eq!(Uint128::new(518516), res) + } + + #[test] + fn div_ceil_does_not_round_on_even_divide() { + let fraction = (5u128, 2u128); + let res = Uint128::new(25).div_ceil(fraction); + assert_eq!(Uint128::new(10), res) + } + + #[test] + fn div_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = (21u128, 8u128); + let res = Uint128::MAX.div_ceil(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698), + res + ) + } + + #[test] + fn div_ceil_works_with_decimal() { + let decimal = Decimal::from_ratio(21u128, 8u128); + let res = Uint128::new(123456).div_ceil(decimal); // 47030.8571 + assert_eq!(Uint128::new(47031), res) + } + + #[test] + fn div_ceil_works_with_decimal_evenly() { + let res = Uint128::new(60).div_ceil(Decimal::from_atomics(6u128, 0).unwrap()); + assert_eq!(res, Uint128::new(10)); + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn div_ceil_panics_on_overflow() { + let fraction = (8u128, 21u128); + Uint128::MAX.div_ceil(fraction); + } + + #[test] + fn div_ceil_does_not_panic_on_overflow() { + let fraction = (8u128, 21u128); + assert_eq!( + Uint128::MAX.checked_div_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() + })), + ); + } } diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index 664cdb8651..d613436730 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -1857,4 +1857,156 @@ mod tests { })), ); } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn div_floor_raises_with_zero() { + let fraction = (Uint256::zero(), Uint256::from(21u32)); + Uint256::from(123456u128).div_floor(fraction); + } + + #[test] + fn div_floor_does_nothing_with_one() { + let fraction = (Uint256::one(), Uint256::one()); + let res = Uint256::from(123456u128).div_floor(fraction); + assert_eq!(Uint256::from(123456u128), res) + } + + #[test] + fn div_floor_rounds_down_with_normal_case() { + let fraction = (5u128, 21u128); + let res = Uint256::from(123456u128).div_floor(fraction); // 518515.2 + assert_eq!(Uint256::from(518515u128), res) + } + + #[test] + fn div_floor_does_not_round_on_even_divide() { + let fraction = (5u128, 2u128); + let res = Uint256::from(25u128).div_floor(fraction); + assert_eq!(Uint256::from(10u128), res) + } + + #[test] + fn div_floor_works_when_operation_temporarily_takes_above_max() { + let fraction = (21u128, 8u128); + let res = Uint256::MAX.div_floor(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571 + assert_eq!( + Uint256::from_str( + "44111272090406169685169899050928726801245708444053548205507651050633573196165" + ) + .unwrap(), + res + ) + } + + #[test] + fn div_floor_works_with_decimal() { + let decimal = Decimal::from_ratio(21u128, 8u128); + let res = Uint256::from(123456u128).div_floor(decimal); // 47030.8571 + assert_eq!(Uint256::from(47030u128), res) + } + + #[test] + fn div_floor_works_with_decimal_evenly() { + let res = Uint256::from(60u128).div_floor(Decimal::from_atomics(6u128, 0).unwrap()); + assert_eq!(res, Uint256::from(10u128)); + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn div_floor_panics_on_overflow() { + let fraction = (8u128, 21u128); + Uint256::MAX.div_floor(fraction); + } + + #[test] + fn div_floor_does_not_panic_on_overflow() { + let fraction = (8u128, 21u128); + assert_eq!( + Uint256::MAX.checked_div_floor(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint512", + target_type: "Uint256", + value: + "303954234247955012986873835647805758114833709747306480603576158020771965304829" + .to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn div_ceil_raises_with_zero() { + let fraction = (Uint256::zero(), Uint256::from(21u128)); + Uint256::from(123456u128).div_ceil(fraction); + } + + #[test] + fn div_ceil_does_nothing_with_one() { + let fraction = (Uint256::one(), Uint256::one()); + let res = Uint256::from(123456u128).div_ceil(fraction); + assert_eq!(Uint256::from(123456u128), res) + } + + #[test] + fn div_ceil_rounds_up_with_normal_case() { + let fraction = (5u128, 21u128); + let res = Uint256::from(123456u128).div_ceil(fraction); // 518515.2 + assert_eq!(Uint256::from(518516u128), res) + } + + #[test] + fn div_ceil_does_not_round_on_even_divide() { + let fraction = (5u128, 2u128); + let res = Uint256::from(25u128).div_ceil(fraction); + assert_eq!(Uint256::from(10u128), res) + } + + #[test] + fn div_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = (21u128, 8u128); + let res = Uint256::MAX.div_ceil(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571 + assert_eq!( + Uint256::from_str( + "44111272090406169685169899050928726801245708444053548205507651050633573196166" + ) + .unwrap(), + res + ) + } + + #[test] + fn div_ceil_works_with_decimal() { + let decimal = Decimal::from_ratio(21u128, 8u128); + let res = Uint256::from(123456u128).div_ceil(decimal); // 47030.8571 + assert_eq!(Uint256::from(47031u128), res) + } + + #[test] + fn div_ceil_works_with_decimal_evenly() { + let res = Uint256::from(60u128).div_ceil(Decimal::from_atomics(6u128, 0).unwrap()); + assert_eq!(res, Uint256::from(10u128)); + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn div_ceil_panics_on_overflow() { + let fraction = (8u128, 21u128); + Uint256::MAX.div_ceil(fraction); + } + + #[test] + fn div_ceil_does_not_panic_on_overflow() { + let fraction = (8u128, 21u128); + assert_eq!( + Uint256::MAX.checked_div_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint512", + target_type: "Uint256", + value: + "303954234247955012986873835647805758114833709747306480603576158020771965304829" + .to_string() // raises prior to rounding up + })), + ); + } } diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index 7cba8cb0e9..d6fc5fca95 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -1105,4 +1105,114 @@ mod tests { })), ); } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn div_floor_raises_with_zero() { + let fraction = (Uint64::zero(), Uint64::new(21)); + Uint64::new(123456).div_floor(fraction); + } + + #[test] + fn div_floor_does_nothing_with_one() { + let fraction = (Uint64::one(), Uint64::one()); + let res = Uint64::new(123456).div_floor(fraction); + assert_eq!(Uint64::new(123456), res) + } + + #[test] + fn div_floor_rounds_down_with_normal_case() { + let fraction = (5u64, 21u64); + let res = Uint64::new(123456).div_floor(fraction); // 518515.2 + assert_eq!(Uint64::new(518515), res) + } + + #[test] + fn div_floor_does_not_round_on_even_divide() { + let fraction = (5u64, 2u64); + let res = Uint64::new(25).div_floor(fraction); + assert_eq!(Uint64::new(10), res) + } + + #[test] + fn div_floor_works_when_operation_temporarily_takes_above_max() { + let fraction = (21u64, 8u64); + let res = Uint64::MAX.div_floor(fraction); // 7_027_331_075_698_876_805.71428 + assert_eq!(Uint64::new(7_027_331_075_698_876_805), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn div_floor_panics_on_overflow() { + let fraction = (8u64, 21u64); + Uint64::MAX.div_floor(fraction); + } + + #[test] + fn div_floor_does_not_panic_on_overflow() { + let fraction = (8u64, 21u64); + assert_eq!( + Uint64::MAX.checked_div_floor(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint128", + target_type: "Uint64", + value: "48422703193487572989".to_string() + })), + ); + } + + #[test] + #[should_panic(expected = "DivideByZeroError")] + fn div_ceil_raises_with_zero() { + let fraction = (Uint64::zero(), Uint64::new(21)); + Uint64::new(123456).div_ceil(fraction); + } + + #[test] + fn div_ceil_does_nothing_with_one() { + let fraction = (Uint64::one(), Uint64::one()); + let res = Uint64::new(123456).div_ceil(fraction); + assert_eq!(Uint64::new(123456), res) + } + + #[test] + fn div_ceil_rounds_up_with_normal_case() { + let fraction = (5u64, 21u64); + let res = Uint64::new(123456).div_ceil(fraction); // 518515.2 + assert_eq!(Uint64::new(518516), res) + } + + #[test] + fn div_ceil_does_not_round_on_even_divide() { + let fraction = (5u64, 2u64); + let res = Uint64::new(25).div_ceil(fraction); + assert_eq!(Uint64::new(10), res) + } + + #[test] + fn div_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = (21u64, 8u64); + let res = Uint64::MAX.div_ceil(fraction); // 7_027_331_075_698_876_805.71428 + assert_eq!(Uint64::new(7_027_331_075_698_876_806), res) + } + + #[test] + #[should_panic(expected = "ConversionOverflowError")] + fn div_ceil_panics_on_overflow() { + let fraction = (8u64, 21u64); + Uint64::MAX.div_ceil(fraction); + } + + #[test] + fn div_ceil_does_not_panic_on_overflow() { + let fraction = (8u64, 21u64); + assert_eq!( + Uint64::MAX.checked_div_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint128", + target_type: "Uint64", + value: "48422703193487572989".to_string() + })), + ); + } } From c1b78e9d0c1143377a4e3729410384ae0e43941b Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 18 Jan 2023 15:53:29 +0100 Subject: [PATCH 11/13] Ceil math without redundant expansion --- packages/std/src/math/fraction.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index de3a85a0d5..44025ac212 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -54,11 +54,10 @@ macro_rules! impl_mul_fraction { self, rhs: F, ) -> Result { - let divisor = rhs.denominator().into(); - let remainder = self - .full_mul(rhs.numerator().into()) - .checked_rem(divisor.into())?; - let floor_result = self.checked_mul_floor(rhs)?; + let dividend = self.full_mul(rhs.numerator().into()); + let divisor = rhs.denominator().into().into(); + let floor_result = dividend.checked_div(divisor)?.try_into()?; + let remainder = dividend.checked_rem(divisor)?; if !remainder.is_zero() { Ok($Uint::one().checked_add(floor_result)?) } else { @@ -105,11 +104,10 @@ macro_rules! impl_mul_fraction { where Self: Sized, { - let divisor = rhs.numerator().into(); - let remainder = self - .full_mul(rhs.denominator().into()) - .checked_rem(divisor.into())?; - let floor_result = self.checked_div_floor(rhs)?; + let dividend = self.full_mul(rhs.denominator().into()); + let divisor = rhs.numerator().into().into(); + let floor_result = dividend.checked_div(divisor)?.try_into()?; + let remainder = dividend.checked_rem(divisor)?; if !remainder.is_zero() { Ok($Uint::one().checked_add(floor_result)?) } else { From fac345e88e618052c9076d17a613958173b47594 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 18 Jan 2023 16:32:06 +0100 Subject: [PATCH 12/13] Add rustdocs --- packages/std/src/math/fraction.rs | 66 ++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/std/src/math/fraction.rs b/packages/std/src/math/fraction.rs index 44025ac212..0056f4a0a0 100644 --- a/packages/std/src/math/fraction.rs +++ b/packages/std/src/math/fraction.rs @@ -35,6 +35,17 @@ impl + PartialEq> Fraction for (T, T) { macro_rules! impl_mul_fraction { ($Uint:ident) => { impl $Uint { + /// Multiply `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]). + /// Result is rounded down. + /// + /// ## Examples + /// + /// ``` + /// use cosmwasm_std::Uint128; + /// let fraction = (8u128, 21u128); + /// let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); + /// assert_eq!(Uint128::new(47030), res); // 47030.8571 rounds down + /// ``` pub fn checked_mul_floor, T: Into<$Uint>>( self, rhs: F, @@ -46,10 +57,22 @@ macro_rules! impl_mul_fraction { Ok(res.try_into()?) } + /// Same operation as `checked_mul_floor` except unwrapped pub fn mul_floor, T: Into<$Uint>>(self, rhs: F) -> Self { self.checked_mul_floor(rhs).unwrap() } + /// Multiply `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]). + /// Result is rounded up. + /// + /// ## Examples + /// + /// ``` + /// use cosmwasm_std::Uint128; + /// let fraction = (8u128, 21u128); + /// let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); + /// assert_eq!(Uint128::new(47031), res); // 47030.8571 rounds up + /// ``` pub fn checked_mul_ceil, T: Into<$Uint>>( self, rhs: F, @@ -65,17 +88,22 @@ macro_rules! impl_mul_fraction { } } + /// Same operation as `checked_mul_ceil` except unwrapped pub fn mul_ceil, T: Into<$Uint>>(self, rhs: F) -> Self { self.checked_mul_ceil(rhs).unwrap() } - pub fn div_floor, T: Into<$Uint>>(self, rhs: F) -> Self - where - Self: Sized, - { - self.checked_div_floor(rhs).unwrap() - } - + /// Divide `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]). + /// Result is rounded down. + /// + /// ## Examples + /// + /// ``` + /// use cosmwasm_std::Uint128; + /// let fraction = (4u128, 5u128); + /// let res = Uint128::new(789).checked_div_floor(fraction).unwrap(); + /// assert_eq!(Uint128::new(986), res); // 986.25 rounds down + /// ``` pub fn checked_div_floor, T: Into<$Uint>>( self, rhs: F, @@ -90,13 +118,25 @@ macro_rules! impl_mul_fraction { Ok(res.try_into()?) } - pub fn div_ceil, T: Into<$Uint>>(self, rhs: F) -> Self + /// Same operation as `checked_div_floor` except unwrapped + pub fn div_floor, T: Into<$Uint>>(self, rhs: F) -> Self where Self: Sized, { - self.checked_div_ceil(rhs).unwrap() + self.checked_div_floor(rhs).unwrap() } + /// Divide `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]). + /// Result is rounded up. + /// + /// ## Examples + /// + /// ``` + /// use cosmwasm_std::Uint128; + /// let fraction = (4u128, 5u128); + /// let res = Uint128::new(789).checked_div_ceil(fraction).unwrap(); + /// assert_eq!(Uint128::new(987), res); // 986.25 rounds up + /// ``` pub fn checked_div_ceil, T: Into<$Uint>>( self, rhs: F, @@ -114,6 +154,14 @@ macro_rules! impl_mul_fraction { Ok(floor_result) } } + + /// Same operation as `checked_div_ceil` except unwrapped + pub fn div_ceil, T: Into<$Uint>>(self, rhs: F) -> Self + where + Self: Sized, + { + self.checked_div_ceil(rhs).unwrap() + } } }; } From d8c52e53eb86d2dab2fbe652fd4ee683ba00cbab Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 18 Jan 2023 16:40:32 +0100 Subject: [PATCH 13/13] add to changlog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0079be3a8..b7b4fd59eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,11 +33,15 @@ and this project adheres to ([#1561]). - cosmwasm-vm: Add `Cache::remove_wasm` to remove obsolete Wasm blobs and their compiled modules. +- cosmwasm-std: Implement fraction multiplication and division. Assists with + Uint & Decimal arithmetic and exposes methods for flooring/ceiling result + ([#1485], [#1566]). [#1436]: https://github.com/CosmWasm/cosmwasm/issues/1436 [#1437]: https://github.com/CosmWasm/cosmwasm/issues/1437 -[#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481 [#1478]: https://github.com/CosmWasm/cosmwasm/pull/1478 +[#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481 +[#1485]: https://github.com/CosmWasm/cosmwasm/issues/1485 [#1513]: https://github.com/CosmWasm/cosmwasm/pull/1513 [#1533]: https://github.com/CosmWasm/cosmwasm/pull/1533 [#1550]: https://github.com/CosmWasm/cosmwasm/issues/1550 @@ -45,6 +49,7 @@ and this project adheres to [#1554]: https://github.com/CosmWasm/cosmwasm/pull/1554 [#1560]: https://github.com/CosmWasm/cosmwasm/pull/1560 [#1561]: https://github.com/CosmWasm/cosmwasm/pull/1561 +[#1566]: https://github.com/CosmWasm/cosmwasm/pull/1566 ### Changed