Skip to content

Commit

Permalink
Merge pull request #1566 from grod220/fractional-round
Browse files Browse the repository at this point in the history
Support fraction multiply floor/ceil
  • Loading branch information
webmaster128 committed Jan 23, 2023
2 parents 9f0fb71 + d8c52e5 commit 25c29e4
Show file tree
Hide file tree
Showing 7 changed files with 1,126 additions and 17 deletions.
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))
}
}
}

#[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)
}
}

/// 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

0 comments on commit 25c29e4

Please sign in to comment.