Skip to content

Commit

Permalink
Merge pull request #1347 from CosmWasm/1186-decimals-rounding
Browse files Browse the repository at this point in the history
`Decimal`/`Decimal256` - implement rounding `ceil` and `floor`
  • Loading branch information
ueco-jb authored Jul 11, 2022
2 parents 3b561e5 + 4f4bc2a commit 0f90b99
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion packages/std/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 4 additions & 0 deletions packages/std/src/errors/std_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
60 changes: 59 additions & 1 deletion packages/std/src/math/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use thiserror::Error;

use crate::errors::{
CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
OverflowOperation, StdError,
OverflowOperation, RoundUpOverflowError, StdError,
};

use super::Fraction;
Expand Down Expand Up @@ -184,6 +184,31 @@ 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<Self, RoundUpOverflowError> {
let floor = self.floor();
if &floor == self {
Ok(floor)
} else {
floor
.checked_add(Decimal::one())
.map_err(|_| RoundUpOverflowError)
}
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
Expand Down Expand Up @@ -1872,4 +1897,37 @@ 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());
}

#[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(RoundUpOverflowError { .. })
));
}
}
57 changes: 56 additions & 1 deletion packages/std/src/math/decimal256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use thiserror::Error;

use crate::errors::{
CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
OverflowOperation, StdError,
OverflowOperation, RoundUpOverflowError, StdError,
};
use crate::{Decimal, Uint512};

Expand Down Expand Up @@ -197,6 +197,31 @@ 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<Self, RoundUpOverflowError> {
let floor = self.floor();
if &floor == self {
Ok(floor)
} else {
floor
.checked_add(Decimal256::one())
.map_err(|_| RoundUpOverflowError)
}
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
Expand Down Expand Up @@ -2022,4 +2047,34 @@ 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());
}

#[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_eq!(Decimal256::MAX.checked_ceil(), Err(RoundUpOverflowError));
}
}

0 comments on commit 0f90b99

Please sign in to comment.