From a5f67adf5b051c0b5da3c499c43f5f7c12fd323f Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Mon, 11 Jul 2022 12:15:38 +0200 Subject: [PATCH 1/4] Decimal/256: Implement ceil/floor rounding --- packages/std/src/math/decimal.rs | 28 ++++++++++++++++++++++++++++ packages/std/src/math/decimal256.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index cd00a4ccdb..006b337fc8 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -184,6 +184,19 @@ impl Decimal { Self::DECIMAL_PLACES as u32 } + pub fn floor(&self) -> Self { + Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) + } + + pub fn ceil(&self) -> Self { + let floor = self.floor(); + if &floor == self { + floor + } else { + floor + Decimal::one() + } + } + pub fn checked_add(self, other: Self) -> Result { self.0 .checked_add(other.0) @@ -1872,4 +1885,19 @@ mod tests { ); assert_eq!(Decimal::MAX.saturating_pow(2u32), Decimal::MAX); } + + #[test] + fn decimal_rounding() { + assert_eq!(Decimal::one().floor(), Decimal::one()); + assert_eq!(Decimal::percent(150).floor(), Decimal::one()); + assert_eq!(Decimal::percent(199).floor(), Decimal::one()); + assert_eq!(Decimal::percent(200).floor(), Decimal::percent(200)); + assert_eq!(Decimal::percent(99).floor(), Decimal::zero()); + + assert_eq!(Decimal::one().ceil(), Decimal::one()); + assert_eq!(Decimal::percent(150).ceil(), Decimal::percent(200)); + assert_eq!(Decimal::percent(199).ceil(), Decimal::percent(200)); + assert_eq!(Decimal::percent(99).ceil(), Decimal::one()); + assert_eq!(Decimal(Uint128::from(1u128)).ceil(), Decimal::one()); + } } diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index 508191c6fa..9111ce2106 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -197,6 +197,19 @@ impl Decimal256 { Self::DECIMAL_PLACES as u32 } + pub fn floor(&self) -> Self { + Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) + } + + pub fn ceil(&self) -> Self { + let floor = self.floor(); + if &floor == self { + floor + } else { + floor + Decimal256::one() + } + } + pub fn checked_add(self, other: Self) -> Result { self.0 .checked_add(other.0) @@ -2022,4 +2035,19 @@ mod tests { ); assert_eq!(Decimal256::MAX.saturating_pow(2u32), Decimal256::MAX); } + + #[test] + fn decimal256_rounding() { + assert_eq!(Decimal256::one().floor(), Decimal256::one()); + assert_eq!(Decimal256::percent(150).floor(), Decimal256::one()); + assert_eq!(Decimal256::percent(199).floor(), Decimal256::one()); + assert_eq!(Decimal256::percent(200).floor(), Decimal256::percent(200)); + assert_eq!(Decimal256::percent(99).floor(), Decimal256::zero()); + + assert_eq!(Decimal256::one().ceil(), Decimal256::one()); + assert_eq!(Decimal256::percent(150).ceil(), Decimal256::percent(200)); + assert_eq!(Decimal256::percent(199).ceil(), Decimal256::percent(200)); + assert_eq!(Decimal256::percent(99).ceil(), Decimal256::one()); + assert_eq!(Decimal256(Uint256::from(1u128)).ceil(), Decimal256::one()); + } } From 61d9523339b8c515388a06ba051c68d07e5597d7 Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Mon, 11 Jul 2022 12:16:30 +0200 Subject: [PATCH 2/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6ff727a6..af7a4f1f2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to - cosmwasm-std: Implement `checked_add`/`_sub`/`_div`/`_rem` for `Decimal`/`Decimal256`. - cosmwasm-std: Implement `pow`/`saturating_pow` for `Decimal`/`Decimal256`. +- cosmwasm-std: Implement `ceil`/`floor` for `Decimal`/`Decimal256`. [#1334]: https://github.com/CosmWasm/cosmwasm/pull/1334 From d2f3548e467e015507f88b678ea1fd05a7c7b380 Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Mon, 11 Jul 2022 12:57:15 +0200 Subject: [PATCH 3/4] Decimal/256: Implement checked_ceil; add doc comments --- packages/std/src/math/decimal.rs | 32 +++++++++++++++++++++++++++-- packages/std/src/math/decimal256.rs | 32 +++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index 006b337fc8..d7f48016db 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -184,16 +184,26 @@ impl Decimal { Self::DECIMAL_PLACES as u32 } + /// Rounds value down after decimal places. pub fn floor(&self) -> Self { Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) } + /// Rounds value up after decimal places. Panics on overflow. 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 { - floor + Ok(floor) } else { - floor + Decimal::one() + floor.checked_add(Decimal::one()) } } @@ -1900,4 +1910,22 @@ mod tests { assert_eq!(Decimal::percent(99).ceil(), Decimal::one()); assert_eq!(Decimal(Uint128::from(1u128)).ceil(), Decimal::one()); } + + #[test] + #[should_panic(expected = "attempt to ceil with overflow")] + fn decimal_ceil_panics() { + let _ = Decimal::MAX.ceil(); + } + + #[test] + fn decimal_checked_ceil() { + assert_eq!( + Decimal::percent(199).checked_ceil(), + Ok(Decimal::percent(200)) + ); + assert!(matches!( + Decimal::MAX.checked_ceil(), + Err(OverflowError { .. }) + )); + } } diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index 9111ce2106..b8e8aa826a 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -197,16 +197,26 @@ impl Decimal256 { Self::DECIMAL_PLACES as u32 } + /// Rounds value down after decimal places. pub fn floor(&self) -> Self { Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL) } + /// Rounds value up after decimal places. Panics on overflow. 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 { - floor + Ok(floor) } else { - floor + Decimal256::one() + floor.checked_add(Decimal256::one()) } } @@ -2050,4 +2060,22 @@ mod tests { assert_eq!(Decimal256::percent(99).ceil(), Decimal256::one()); assert_eq!(Decimal256(Uint256::from(1u128)).ceil(), Decimal256::one()); } + + #[test] + #[should_panic(expected = "attempt to ceil with overflow")] + fn decimal256_ceil_panics() { + let _ = Decimal256::MAX.ceil(); + } + + #[test] + fn decimal256_checked_ceil() { + assert_eq!( + Decimal256::percent(199).checked_ceil(), + Ok(Decimal256::percent(200)) + ); + assert!(matches!( + Decimal256::MAX.checked_ceil(), + Err(OverflowError { .. }) + )); + } } From 4f4bc2a4a8ebe35f7db66df337cb90bf7985e787 Mon Sep 17 00:00:00 2001 From: Jakub Bogucki Date: Mon, 11 Jul 2022 15:56:57 +0200 Subject: [PATCH 4/4] Add new error for rounding up overflow --- packages/std/src/errors/mod.rs | 2 +- packages/std/src/errors/std_error.rs | 4 ++++ packages/std/src/math/decimal.rs | 10 ++++++---- packages/std/src/math/decimal256.rs | 13 ++++++------- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index 64b1e96f92..bf7e61f433 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -6,7 +6,7 @@ mod verification_error; pub use recover_pubkey_error::RecoverPubkeyError; pub use std_error::{ CheckedFromRatioError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError, - OverflowError, OverflowOperation, StdError, StdResult, + 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 165ba20fb6..4993c29d5c 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -544,6 +544,10 @@ pub enum CheckedFromRatioError { Overflow, } +#[derive(Error, Debug, PartialEq, Eq)] +#[error("Round up operation failed because of overflow")] +pub struct RoundUpOverflowError; + #[cfg(test)] mod tests { use super::*; diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index d7f48016db..3d88fc0f56 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -9,7 +9,7 @@ use thiserror::Error; use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, - OverflowOperation, StdError, + OverflowOperation, RoundUpOverflowError, StdError, }; use super::Fraction; @@ -198,12 +198,14 @@ impl Decimal { } /// Rounds value up after decimal places. Returns OverflowError on overflow. - pub fn checked_ceil(&self) -> Result { + pub fn checked_ceil(&self) -> Result { let floor = self.floor(); if &floor == self { Ok(floor) } else { - floor.checked_add(Decimal::one()) + floor + .checked_add(Decimal::one()) + .map_err(|_| RoundUpOverflowError) } } @@ -1925,7 +1927,7 @@ mod tests { ); assert!(matches!( Decimal::MAX.checked_ceil(), - Err(OverflowError { .. }) + Err(RoundUpOverflowError { .. }) )); } } diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index b8e8aa826a..ede9a0a969 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -9,7 +9,7 @@ use thiserror::Error; use crate::errors::{ CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, - OverflowOperation, StdError, + OverflowOperation, RoundUpOverflowError, StdError, }; use crate::{Decimal, Uint512}; @@ -211,12 +211,14 @@ impl Decimal256 { } /// Rounds value up after decimal places. Returns OverflowError on overflow. - pub fn checked_ceil(&self) -> Result { + pub fn checked_ceil(&self) -> Result { let floor = self.floor(); if &floor == self { Ok(floor) } else { - floor.checked_add(Decimal256::one()) + floor + .checked_add(Decimal256::one()) + .map_err(|_| RoundUpOverflowError) } } @@ -2073,9 +2075,6 @@ mod tests { Decimal256::percent(199).checked_ceil(), Ok(Decimal256::percent(200)) ); - assert!(matches!( - Decimal256::MAX.checked_ceil(), - Err(OverflowError { .. }) - )); + assert_eq!(Decimal256::MAX.checked_ceil(), Err(RoundUpOverflowError)); } }