Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support fraction multiply floor/ceil #1566

Merged
merged 13 commits into from
Jan 23, 2023
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,23 @@ and this project adheres to
([#1561]).
- cosmwasm-vm: Add `Cache::remove_wasm` to remove obsolete Wasm blobs and their
compiled modules.
- cosmwasm-std: Implement fraction multiplication and division. Assists with
Uint & Decimal arithmetic and exposes methods for flooring/ceiling result
([#1485], [#1566]).

[#1436]: https://github.com/CosmWasm/cosmwasm/issues/1436
[#1437]: https://github.com/CosmWasm/cosmwasm/issues/1437
[#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481
[#1478]: https://github.com/CosmWasm/cosmwasm/pull/1478
[#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481
[#1485]: https://github.com/CosmWasm/cosmwasm/issues/1485
[#1513]: https://github.com/CosmWasm/cosmwasm/pull/1513
[#1533]: https://github.com/CosmWasm/cosmwasm/pull/1533
[#1550]: https://github.com/CosmWasm/cosmwasm/issues/1550
[#1552]: https://github.com/CosmWasm/cosmwasm/pull/1552
[#1554]: https://github.com/CosmWasm/cosmwasm/pull/1554
[#1560]: https://github.com/CosmWasm/cosmwasm/pull/1560
[#1561]: https://github.com/CosmWasm/cosmwasm/pull/1561
[#1566]: https://github.com/CosmWasm/cosmwasm/pull/1566

### Changed

Expand Down
5 changes: 3 additions & 2 deletions packages/std/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ mod verification_error;

pub use recover_pubkey_error::RecoverPubkeyError;
pub use std_error::{
CheckedFromRatioError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError,
OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult,
CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation,
RoundUpOverflowError, StdError, StdResult,
};
pub use system_error::SystemError;
pub use verification_error::VerificationError;
14 changes: 13 additions & 1 deletion packages/std/src/errors/std_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ impl ConversionOverflowError {
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("Cannot devide {operand} by zero")]
#[error("Cannot divide {operand} by zero")]
pub struct DivideByZeroError {
pub operand: String,
}
Expand All @@ -556,6 +556,18 @@ impl DivideByZeroError {
}
}

#[derive(Error, Debug, PartialEq, Eq)]
pub enum CheckedMultiplyFractionError {
#[error("{0}")]
DivideByZero(#[from] DivideByZeroError),

#[error("{0}")]
ConversionOverflow(#[from] ConversionOverflowError),

#[error("{0}")]
Overflow(#[from] OverflowError),
}

#[derive(Error, Debug, PartialEq, Eq)]
pub enum CheckedMultiplyRatioError {
#[error("Denominator must not be zero")]
Expand Down
172 changes: 172 additions & 0 deletions packages/std/src/math/fraction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,175 @@ pub trait Fraction<T>: Sized {
/// If `p` is zero, None is returned.
fn inv(&self) -> Option<Self>;
}

impl<T: Copy + From<u8> + PartialEq> Fraction<T> for (T, T) {
fn numerator(&self) -> T {
self.0
}

fn denominator(&self) -> T {
self.1
}

fn inv(&self) -> Option<Self> {
if self.numerator() == 0u8.into() {
None
} else {
Some((self.1, self.0))
}
}
}
grod220 marked this conversation as resolved.
Show resolved Hide resolved

#[macro_export]
macro_rules! impl_mul_fraction {
($Uint:ident) => {
impl $Uint {
/// Multiply `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
/// Result is rounded down.
///
/// ## Examples
///
/// ```
/// use cosmwasm_std::Uint128;
/// let fraction = (8u128, 21u128);
/// let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap();
/// assert_eq!(Uint128::new(47030), res); // 47030.8571 rounds down
/// ```
pub fn checked_mul_floor<F: Fraction<T>, T: Into<$Uint>>(
self,
rhs: F,
) -> Result<Self, CheckedMultiplyFractionError> {
let divisor = rhs.denominator().into();
let res = self
.full_mul(rhs.numerator().into())
.checked_div(divisor.into())?;
Ok(res.try_into()?)
}

/// Same operation as `checked_mul_floor` except unwrapped
pub fn mul_floor<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self {
self.checked_mul_floor(rhs).unwrap()
}

/// Multiply `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
/// Result is rounded up.
///
/// ## Examples
///
/// ```
/// use cosmwasm_std::Uint128;
/// let fraction = (8u128, 21u128);
/// let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap();
/// assert_eq!(Uint128::new(47031), res); // 47030.8571 rounds up
/// ```
pub fn checked_mul_ceil<F: Fraction<T>, T: Into<$Uint>>(
self,
rhs: F,
) -> Result<Self, CheckedMultiplyFractionError> {
let dividend = self.full_mul(rhs.numerator().into());
let divisor = rhs.denominator().into().into();
let floor_result = dividend.checked_div(divisor)?.try_into()?;
let remainder = dividend.checked_rem(divisor)?;
if !remainder.is_zero() {
Ok($Uint::one().checked_add(floor_result)?)
} else {
Ok(floor_result)
}
}

/// Same operation as `checked_mul_ceil` except unwrapped
pub fn mul_ceil<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self {
self.checked_mul_ceil(rhs).unwrap()
}

/// Divide `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
/// Result is rounded down.
///
/// ## Examples
///
/// ```
/// use cosmwasm_std::Uint128;
/// let fraction = (4u128, 5u128);
/// let res = Uint128::new(789).checked_div_floor(fraction).unwrap();
/// assert_eq!(Uint128::new(986), res); // 986.25 rounds down
/// ```
pub fn checked_div_floor<F: Fraction<T>, T: Into<$Uint>>(
self,
rhs: F,
) -> Result<Self, CheckedMultiplyFractionError>
where
Self: Sized,
{
let divisor = rhs.numerator().into();
let res = self
.full_mul(rhs.denominator().into())
.checked_div(divisor.into())?;
Ok(res.try_into()?)
}

/// Same operation as `checked_div_floor` except unwrapped
pub fn div_floor<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self
where
Self: Sized,
{
self.checked_div_floor(rhs).unwrap()
}

/// Divide `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
/// Result is rounded up.
///
/// ## Examples
///
/// ```
/// use cosmwasm_std::Uint128;
/// let fraction = (4u128, 5u128);
/// let res = Uint128::new(789).checked_div_ceil(fraction).unwrap();
/// assert_eq!(Uint128::new(987), res); // 986.25 rounds up
/// ```
pub fn checked_div_ceil<F: Fraction<T>, T: Into<$Uint>>(
self,
rhs: F,
) -> Result<Self, CheckedMultiplyFractionError>
where
Self: Sized,
{
let dividend = self.full_mul(rhs.denominator().into());
let divisor = rhs.numerator().into().into();
let floor_result = dividend.checked_div(divisor)?.try_into()?;
let remainder = dividend.checked_rem(divisor)?;
if !remainder.is_zero() {
Ok($Uint::one().checked_add(floor_result)?)
} else {
Ok(floor_result)
}
grod220 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Same operation as `checked_div_ceil` except unwrapped
pub fn div_ceil<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self
where
Self: Sized,
{
self.checked_div_ceil(rhs).unwrap()
}
}
};
}

#[cfg(test)]
mod tests {
use crate::{Fraction, Uint128, Uint64};

#[test]
fn fraction_tuple_methods() {
let fraction = (Uint64::one(), Uint64::new(2));
assert_eq!(Uint64::one(), fraction.numerator());
assert_eq!(Uint64::new(2), fraction.denominator());
assert_eq!(Some((Uint64::new(2), Uint64::one())), fraction.inv());
}

#[test]
fn inverse_with_zero_denominator() {
let fraction = (Uint128::zero(), Uint128::one());
assert_eq!(None, fraction.inv());
}
}
Loading