Skip to content

Commit 25c29e4

Browse files
authored
Merge pull request #1566 from grod220/fractional-round
Support fraction multiply floor/ceil
2 parents 9f0fb71 + d8c52e5 commit 25c29e4

File tree

7 files changed

+1126
-17
lines changed

7 files changed

+1126
-17
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,23 @@ and this project adheres to
3333
([#1561]).
3434
- cosmwasm-vm: Add `Cache::remove_wasm` to remove obsolete Wasm blobs and their
3535
compiled modules.
36+
- cosmwasm-std: Implement fraction multiplication and division. Assists with
37+
Uint & Decimal arithmetic and exposes methods for flooring/ceiling result
38+
([#1485], [#1566]).
3639

3740
[#1436]: https://github.com/CosmWasm/cosmwasm/issues/1436
3841
[#1437]: https://github.com/CosmWasm/cosmwasm/issues/1437
39-
[#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481
4042
[#1478]: https://github.com/CosmWasm/cosmwasm/pull/1478
43+
[#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481
44+
[#1485]: https://github.com/CosmWasm/cosmwasm/issues/1485
4145
[#1513]: https://github.com/CosmWasm/cosmwasm/pull/1513
4246
[#1533]: https://github.com/CosmWasm/cosmwasm/pull/1533
4347
[#1550]: https://github.com/CosmWasm/cosmwasm/issues/1550
4448
[#1552]: https://github.com/CosmWasm/cosmwasm/pull/1552
4549
[#1554]: https://github.com/CosmWasm/cosmwasm/pull/1554
4650
[#1560]: https://github.com/CosmWasm/cosmwasm/pull/1560
4751
[#1561]: https://github.com/CosmWasm/cosmwasm/pull/1561
52+
[#1566]: https://github.com/CosmWasm/cosmwasm/pull/1566
4853

4954
### Changed
5055

packages/std/src/errors/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ mod verification_error;
55

66
pub use recover_pubkey_error::RecoverPubkeyError;
77
pub use std_error::{
8-
CheckedFromRatioError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError,
9-
OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult,
8+
CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
9+
ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation,
10+
RoundUpOverflowError, StdError, StdResult,
1011
};
1112
pub use system_error::SystemError;
1213
pub use verification_error::VerificationError;

packages/std/src/errors/std_error.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ impl ConversionOverflowError {
543543
}
544544

545545
#[derive(Error, Debug, PartialEq, Eq)]
546-
#[error("Cannot devide {operand} by zero")]
546+
#[error("Cannot divide {operand} by zero")]
547547
pub struct DivideByZeroError {
548548
pub operand: String,
549549
}
@@ -556,6 +556,18 @@ impl DivideByZeroError {
556556
}
557557
}
558558

559+
#[derive(Error, Debug, PartialEq, Eq)]
560+
pub enum CheckedMultiplyFractionError {
561+
#[error("{0}")]
562+
DivideByZero(#[from] DivideByZeroError),
563+
564+
#[error("{0}")]
565+
ConversionOverflow(#[from] ConversionOverflowError),
566+
567+
#[error("{0}")]
568+
Overflow(#[from] OverflowError),
569+
}
570+
559571
#[derive(Error, Debug, PartialEq, Eq)]
560572
pub enum CheckedMultiplyRatioError {
561573
#[error("Denominator must not be zero")]

packages/std/src/math/fraction.rs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,175 @@ pub trait Fraction<T>: Sized {
1212
/// If `p` is zero, None is returned.
1313
fn inv(&self) -> Option<Self>;
1414
}
15+
16+
impl<T: Copy + From<u8> + PartialEq> Fraction<T> for (T, T) {
17+
fn numerator(&self) -> T {
18+
self.0
19+
}
20+
21+
fn denominator(&self) -> T {
22+
self.1
23+
}
24+
25+
fn inv(&self) -> Option<Self> {
26+
if self.numerator() == 0u8.into() {
27+
None
28+
} else {
29+
Some((self.1, self.0))
30+
}
31+
}
32+
}
33+
34+
#[macro_export]
35+
macro_rules! impl_mul_fraction {
36+
($Uint:ident) => {
37+
impl $Uint {
38+
/// Multiply `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
39+
/// Result is rounded down.
40+
///
41+
/// ## Examples
42+
///
43+
/// ```
44+
/// use cosmwasm_std::Uint128;
45+
/// let fraction = (8u128, 21u128);
46+
/// let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap();
47+
/// assert_eq!(Uint128::new(47030), res); // 47030.8571 rounds down
48+
/// ```
49+
pub fn checked_mul_floor<F: Fraction<T>, T: Into<$Uint>>(
50+
self,
51+
rhs: F,
52+
) -> Result<Self, CheckedMultiplyFractionError> {
53+
let divisor = rhs.denominator().into();
54+
let res = self
55+
.full_mul(rhs.numerator().into())
56+
.checked_div(divisor.into())?;
57+
Ok(res.try_into()?)
58+
}
59+
60+
/// Same operation as `checked_mul_floor` except unwrapped
61+
pub fn mul_floor<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self {
62+
self.checked_mul_floor(rhs).unwrap()
63+
}
64+
65+
/// Multiply `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
66+
/// Result is rounded up.
67+
///
68+
/// ## Examples
69+
///
70+
/// ```
71+
/// use cosmwasm_std::Uint128;
72+
/// let fraction = (8u128, 21u128);
73+
/// let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap();
74+
/// assert_eq!(Uint128::new(47031), res); // 47030.8571 rounds up
75+
/// ```
76+
pub fn checked_mul_ceil<F: Fraction<T>, T: Into<$Uint>>(
77+
self,
78+
rhs: F,
79+
) -> Result<Self, CheckedMultiplyFractionError> {
80+
let dividend = self.full_mul(rhs.numerator().into());
81+
let divisor = rhs.denominator().into().into();
82+
let floor_result = dividend.checked_div(divisor)?.try_into()?;
83+
let remainder = dividend.checked_rem(divisor)?;
84+
if !remainder.is_zero() {
85+
Ok($Uint::one().checked_add(floor_result)?)
86+
} else {
87+
Ok(floor_result)
88+
}
89+
}
90+
91+
/// Same operation as `checked_mul_ceil` except unwrapped
92+
pub fn mul_ceil<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self {
93+
self.checked_mul_ceil(rhs).unwrap()
94+
}
95+
96+
/// Divide `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
97+
/// Result is rounded down.
98+
///
99+
/// ## Examples
100+
///
101+
/// ```
102+
/// use cosmwasm_std::Uint128;
103+
/// let fraction = (4u128, 5u128);
104+
/// let res = Uint128::new(789).checked_div_floor(fraction).unwrap();
105+
/// assert_eq!(Uint128::new(986), res); // 986.25 rounds down
106+
/// ```
107+
pub fn checked_div_floor<F: Fraction<T>, T: Into<$Uint>>(
108+
self,
109+
rhs: F,
110+
) -> Result<Self, CheckedMultiplyFractionError>
111+
where
112+
Self: Sized,
113+
{
114+
let divisor = rhs.numerator().into();
115+
let res = self
116+
.full_mul(rhs.denominator().into())
117+
.checked_div(divisor.into())?;
118+
Ok(res.try_into()?)
119+
}
120+
121+
/// Same operation as `checked_div_floor` except unwrapped
122+
pub fn div_floor<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self
123+
where
124+
Self: Sized,
125+
{
126+
self.checked_div_floor(rhs).unwrap()
127+
}
128+
129+
/// Divide `self` with a struct implementing [`Fraction`] (e.g. [`crate::Decimal`]).
130+
/// Result is rounded up.
131+
///
132+
/// ## Examples
133+
///
134+
/// ```
135+
/// use cosmwasm_std::Uint128;
136+
/// let fraction = (4u128, 5u128);
137+
/// let res = Uint128::new(789).checked_div_ceil(fraction).unwrap();
138+
/// assert_eq!(Uint128::new(987), res); // 986.25 rounds up
139+
/// ```
140+
pub fn checked_div_ceil<F: Fraction<T>, T: Into<$Uint>>(
141+
self,
142+
rhs: F,
143+
) -> Result<Self, CheckedMultiplyFractionError>
144+
where
145+
Self: Sized,
146+
{
147+
let dividend = self.full_mul(rhs.denominator().into());
148+
let divisor = rhs.numerator().into().into();
149+
let floor_result = dividend.checked_div(divisor)?.try_into()?;
150+
let remainder = dividend.checked_rem(divisor)?;
151+
if !remainder.is_zero() {
152+
Ok($Uint::one().checked_add(floor_result)?)
153+
} else {
154+
Ok(floor_result)
155+
}
156+
}
157+
158+
/// Same operation as `checked_div_ceil` except unwrapped
159+
pub fn div_ceil<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self
160+
where
161+
Self: Sized,
162+
{
163+
self.checked_div_ceil(rhs).unwrap()
164+
}
165+
}
166+
};
167+
}
168+
169+
#[cfg(test)]
170+
mod tests {
171+
use crate::{Fraction, Uint128, Uint64};
172+
173+
#[test]
174+
fn fraction_tuple_methods() {
175+
let fraction = (Uint64::one(), Uint64::new(2));
176+
assert_eq!(Uint64::one(), fraction.numerator());
177+
assert_eq!(Uint64::new(2), fraction.denominator());
178+
assert_eq!(Some((Uint64::new(2), Uint64::one())), fraction.inv());
179+
}
180+
181+
#[test]
182+
fn inverse_with_zero_denominator() {
183+
let fraction = (Uint128::zero(), Uint128::one());
184+
assert_eq!(None, fraction.inv());
185+
}
186+
}

0 commit comments

Comments
 (0)