Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}