Skip to content

Commit

Permalink
Merge pull request #1341 from CosmWasm/1186-checked-methods-for-decimal
Browse files Browse the repository at this point in the history
`Decimal`/`Decimal256` - implement missing checked methods
  • Loading branch information
ueco-jb authored Jul 11, 2022
2 parents 5ea5912 + 681043a commit 08efa35
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to
`Decimal{,256}::abs_diff` ([#1334]).
- cosmwasm-std: Implement `From<Decimal> for Decimal256`.
- cosmwasm-std: Implement `Rem`/`RemAssign` for `Decimal`/`Decimal256`.
- cosmwasm-std: Implement `checked_add`/`_sub`/`_div`/`_rem` for
`Decimal`/`Decimal256`.

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

Expand Down
98 changes: 96 additions & 2 deletions packages/std/src/math/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, S
use std::str::FromStr;
use thiserror::Error;

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

use super::Fraction;
use super::Isqrt;
Expand Down Expand Up @@ -182,6 +184,20 @@ impl Decimal {
Self::DECIMAL_PLACES as u32
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
.map(Self)
.map_err(|_| OverflowError::new(OverflowOperation::Add, self, other))
}

pub fn checked_sub(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_sub(other.0)
.map(Self)
.map_err(|_| OverflowError::new(OverflowOperation::Sub, self, other))
}

/// Multiplies one `Decimal` by another, returning an `OverflowError` if an overflow occurred.
pub fn checked_mul(self, other: Self) -> Result<Self, OverflowError> {
let result_as_uint256 = self.numerator().full_mul(other.numerator())
Expand Down Expand Up @@ -229,6 +245,17 @@ impl Decimal {
})
}

pub fn checked_div(self, other: Self) -> Result<Self, CheckedFromRatioError> {
Decimal::checked_from_ratio(self.numerator(), other.numerator())
}

pub fn checked_rem(self, other: Self) -> Result<Self, DivideByZeroError> {
self.0
.checked_rem(other.0)
.map(Self)
.map_err(|_| DivideByZeroError::new(self))
}

/// Returns the approximate square root as a Decimal.
///
/// This should not overflow or panic.
Expand Down Expand Up @@ -1741,4 +1768,71 @@ mod tests {
fn decimal_rem_panics_for_zero() {
let _ = Decimal::percent(777) % Decimal::zero();
}

#[test]
fn decimal_checked_methods() {
// checked add
assert_eq!(
Decimal::percent(402)
.checked_add(Decimal::percent(111))
.unwrap(),
Decimal::percent(513)
);
assert!(matches!(
Decimal::MAX.checked_add(Decimal::percent(1)),
Err(OverflowError { .. })
));

// checked sub
assert_eq!(
Decimal::percent(1111)
.checked_sub(Decimal::percent(111))
.unwrap(),
Decimal::percent(1000)
);
assert!(matches!(
Decimal::zero().checked_sub(Decimal::percent(1)),
Err(OverflowError { .. })
));

// checked div
assert_eq!(
Decimal::percent(30)
.checked_div(Decimal::percent(200))
.unwrap(),
Decimal::percent(15)
);
assert_eq!(
Decimal::percent(88)
.checked_div(Decimal::percent(20))
.unwrap(),
Decimal::percent(440)
);
assert!(matches!(
Decimal::MAX.checked_div(Decimal::zero()),
Err(CheckedFromRatioError::DivideByZero { .. })
));
assert!(matches!(
Decimal::MAX.checked_div(Decimal::percent(1)),
Err(CheckedFromRatioError::Overflow { .. })
));

// checked rem
assert_eq!(
Decimal::percent(402)
.checked_rem(Decimal::percent(111))
.unwrap(),
Decimal::percent(69)
);
assert_eq!(
Decimal::percent(1525)
.checked_rem(Decimal::percent(400))
.unwrap(),
Decimal::percent(325)
);
assert!(matches!(
Decimal::MAX.checked_rem(Decimal::zero()),
Err(DivideByZeroError { .. })
));
}
}
99 changes: 97 additions & 2 deletions packages/std/src/math/decimal256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, S
use std::str::FromStr;
use thiserror::Error;

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

use super::Fraction;
use super::Isqrt;
Expand Down Expand Up @@ -194,6 +197,20 @@ impl Decimal256 {
Self::DECIMAL_PLACES as u32
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
.map(Self)
.map_err(|_| OverflowError::new(OverflowOperation::Add, self, other))
}

pub fn checked_sub(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_sub(other.0)
.map(Self)
.map_err(|_| OverflowError::new(OverflowOperation::Sub, self, other))
}

/// Multiplies one `Decimal256` by another, returning an `OverflowError` if an overflow occurred.
pub fn checked_mul(self, other: Self) -> Result<Self, OverflowError> {
let result_as_uint512 = self.numerator().full_mul(other.numerator())
Expand Down Expand Up @@ -241,6 +258,17 @@ impl Decimal256 {
})
}

pub fn checked_div(self, other: Self) -> Result<Self, CheckedFromRatioError> {
Decimal256::checked_from_ratio(self.numerator(), other.numerator())
}

pub fn checked_rem(self, other: Self) -> Result<Self, DivideByZeroError> {
self.0
.checked_rem(other.0)
.map(Self)
.map_err(|_| DivideByZeroError::new(self))
}

/// Returns the approximate square root as a Decimal256.
///
/// This should not overflow or panic.
Expand Down Expand Up @@ -1887,4 +1915,71 @@ mod tests {
fn decimal256_rem_panics_for_zero() {
let _ = Decimal256::percent(777) % Decimal256::zero();
}

#[test]
fn decimal256_checked_methods() {
// checked add
assert_eq!(
Decimal256::percent(402)
.checked_add(Decimal256::percent(111))
.unwrap(),
Decimal256::percent(513)
);
assert!(matches!(
Decimal256::MAX.checked_add(Decimal256::percent(1)),
Err(OverflowError { .. })
));

// checked sub
assert_eq!(
Decimal256::percent(1111)
.checked_sub(Decimal256::percent(111))
.unwrap(),
Decimal256::percent(1000)
);
assert!(matches!(
Decimal256::zero().checked_sub(Decimal256::percent(1)),
Err(OverflowError { .. })
));

// checked div
assert_eq!(
Decimal256::percent(30)
.checked_div(Decimal256::percent(200))
.unwrap(),
Decimal256::percent(15)
);
assert_eq!(
Decimal256::percent(88)
.checked_div(Decimal256::percent(20))
.unwrap(),
Decimal256::percent(440)
);
assert!(matches!(
Decimal256::MAX.checked_div(Decimal256::zero()),
Err(CheckedFromRatioError::DivideByZero { .. })
));
assert!(matches!(
Decimal256::MAX.checked_div(Decimal256::percent(1)),
Err(CheckedFromRatioError::Overflow { .. })
));

// checked rem
assert_eq!(
Decimal256::percent(402)
.checked_rem(Decimal256::percent(111))
.unwrap(),
Decimal256::percent(69)
);
assert_eq!(
Decimal256::percent(1525)
.checked_rem(Decimal256::percent(400))
.unwrap(),
Decimal256::percent(325)
);
assert!(matches!(
Decimal256::MAX.checked_rem(Decimal256::zero()),
Err(DivideByZeroError { .. })
));
}
}

0 comments on commit 08efa35

Please sign in to comment.