Skip to content

Commit 0f90b99

Browse files
authored
Merge pull request #1347 from CosmWasm/1186-decimals-rounding
`Decimal`/`Decimal256` - implement rounding `ceil` and `floor`
2 parents 3b561e5 + 4f4bc2a commit 0f90b99

File tree

5 files changed

+121
-3
lines changed

5 files changed

+121
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to
1616
- cosmwasm-std: Implement `checked_add`/`_sub`/`_div`/`_rem` for
1717
`Decimal`/`Decimal256`.
1818
- cosmwasm-std: Implement `pow`/`saturating_pow` for `Decimal`/`Decimal256`.
19+
- cosmwasm-std: Implement `ceil`/`floor` for `Decimal`/`Decimal256`.
1920

2021
[#1334]: https://github.com/CosmWasm/cosmwasm/pull/1334
2122

packages/std/src/errors/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod verification_error;
66
pub use recover_pubkey_error::RecoverPubkeyError;
77
pub use std_error::{
88
CheckedFromRatioError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError,
9-
OverflowError, OverflowOperation, StdError, StdResult,
9+
OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult,
1010
};
1111
pub use system_error::SystemError;
1212
pub use verification_error::VerificationError;

packages/std/src/errors/std_error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ pub enum CheckedFromRatioError {
544544
Overflow,
545545
}
546546

547+
#[derive(Error, Debug, PartialEq, Eq)]
548+
#[error("Round up operation failed because of overflow")]
549+
pub struct RoundUpOverflowError;
550+
547551
#[cfg(test)]
548552
mod tests {
549553
use super::*;

packages/std/src/math/decimal.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use thiserror::Error;
99

1010
use crate::errors::{
1111
CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
12-
OverflowOperation, StdError,
12+
OverflowOperation, RoundUpOverflowError, StdError,
1313
};
1414

1515
use super::Fraction;
@@ -184,6 +184,31 @@ impl Decimal {
184184
Self::DECIMAL_PLACES as u32
185185
}
186186

187+
/// Rounds value down after decimal places.
188+
pub fn floor(&self) -> Self {
189+
Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL)
190+
}
191+
192+
/// Rounds value up after decimal places. Panics on overflow.
193+
pub fn ceil(&self) -> Self {
194+
match self.checked_ceil() {
195+
Ok(value) => value,
196+
Err(_) => panic!("attempt to ceil with overflow"),
197+
}
198+
}
199+
200+
/// Rounds value up after decimal places. Returns OverflowError on overflow.
201+
pub fn checked_ceil(&self) -> Result<Self, RoundUpOverflowError> {
202+
let floor = self.floor();
203+
if &floor == self {
204+
Ok(floor)
205+
} else {
206+
floor
207+
.checked_add(Decimal::one())
208+
.map_err(|_| RoundUpOverflowError)
209+
}
210+
}
211+
187212
pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
188213
self.0
189214
.checked_add(other.0)
@@ -1872,4 +1897,37 @@ mod tests {
18721897
);
18731898
assert_eq!(Decimal::MAX.saturating_pow(2u32), Decimal::MAX);
18741899
}
1900+
1901+
#[test]
1902+
fn decimal_rounding() {
1903+
assert_eq!(Decimal::one().floor(), Decimal::one());
1904+
assert_eq!(Decimal::percent(150).floor(), Decimal::one());
1905+
assert_eq!(Decimal::percent(199).floor(), Decimal::one());
1906+
assert_eq!(Decimal::percent(200).floor(), Decimal::percent(200));
1907+
assert_eq!(Decimal::percent(99).floor(), Decimal::zero());
1908+
1909+
assert_eq!(Decimal::one().ceil(), Decimal::one());
1910+
assert_eq!(Decimal::percent(150).ceil(), Decimal::percent(200));
1911+
assert_eq!(Decimal::percent(199).ceil(), Decimal::percent(200));
1912+
assert_eq!(Decimal::percent(99).ceil(), Decimal::one());
1913+
assert_eq!(Decimal(Uint128::from(1u128)).ceil(), Decimal::one());
1914+
}
1915+
1916+
#[test]
1917+
#[should_panic(expected = "attempt to ceil with overflow")]
1918+
fn decimal_ceil_panics() {
1919+
let _ = Decimal::MAX.ceil();
1920+
}
1921+
1922+
#[test]
1923+
fn decimal_checked_ceil() {
1924+
assert_eq!(
1925+
Decimal::percent(199).checked_ceil(),
1926+
Ok(Decimal::percent(200))
1927+
);
1928+
assert!(matches!(
1929+
Decimal::MAX.checked_ceil(),
1930+
Err(RoundUpOverflowError { .. })
1931+
));
1932+
}
18751933
}

packages/std/src/math/decimal256.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use thiserror::Error;
99

1010
use crate::errors::{
1111
CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
12-
OverflowOperation, StdError,
12+
OverflowOperation, RoundUpOverflowError, StdError,
1313
};
1414
use crate::{Decimal, Uint512};
1515

@@ -197,6 +197,31 @@ impl Decimal256 {
197197
Self::DECIMAL_PLACES as u32
198198
}
199199

200+
/// Rounds value down after decimal places.
201+
pub fn floor(&self) -> Self {
202+
Self((self.0 / Self::DECIMAL_FRACTIONAL) * Self::DECIMAL_FRACTIONAL)
203+
}
204+
205+
/// Rounds value up after decimal places. Panics on overflow.
206+
pub fn ceil(&self) -> Self {
207+
match self.checked_ceil() {
208+
Ok(value) => value,
209+
Err(_) => panic!("attempt to ceil with overflow"),
210+
}
211+
}
212+
213+
/// Rounds value up after decimal places. Returns OverflowError on overflow.
214+
pub fn checked_ceil(&self) -> Result<Self, RoundUpOverflowError> {
215+
let floor = self.floor();
216+
if &floor == self {
217+
Ok(floor)
218+
} else {
219+
floor
220+
.checked_add(Decimal256::one())
221+
.map_err(|_| RoundUpOverflowError)
222+
}
223+
}
224+
200225
pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
201226
self.0
202227
.checked_add(other.0)
@@ -2022,4 +2047,34 @@ mod tests {
20222047
);
20232048
assert_eq!(Decimal256::MAX.saturating_pow(2u32), Decimal256::MAX);
20242049
}
2050+
2051+
#[test]
2052+
fn decimal256_rounding() {
2053+
assert_eq!(Decimal256::one().floor(), Decimal256::one());
2054+
assert_eq!(Decimal256::percent(150).floor(), Decimal256::one());
2055+
assert_eq!(Decimal256::percent(199).floor(), Decimal256::one());
2056+
assert_eq!(Decimal256::percent(200).floor(), Decimal256::percent(200));
2057+
assert_eq!(Decimal256::percent(99).floor(), Decimal256::zero());
2058+
2059+
assert_eq!(Decimal256::one().ceil(), Decimal256::one());
2060+
assert_eq!(Decimal256::percent(150).ceil(), Decimal256::percent(200));
2061+
assert_eq!(Decimal256::percent(199).ceil(), Decimal256::percent(200));
2062+
assert_eq!(Decimal256::percent(99).ceil(), Decimal256::one());
2063+
assert_eq!(Decimal256(Uint256::from(1u128)).ceil(), Decimal256::one());
2064+
}
2065+
2066+
#[test]
2067+
#[should_panic(expected = "attempt to ceil with overflow")]
2068+
fn decimal256_ceil_panics() {
2069+
let _ = Decimal256::MAX.ceil();
2070+
}
2071+
2072+
#[test]
2073+
fn decimal256_checked_ceil() {
2074+
assert_eq!(
2075+
Decimal256::percent(199).checked_ceil(),
2076+
Ok(Decimal256::percent(200))
2077+
);
2078+
assert_eq!(Decimal256::MAX.checked_ceil(), Err(RoundUpOverflowError));
2079+
}
20252080
}

0 commit comments

Comments
 (0)