Skip to content

Commit cb4cc5e

Browse files
committed
Support fraction multiply
1 parent 3cf3c67 commit cb4cc5e

File tree

8 files changed

+214
-13
lines changed

8 files changed

+214
-13
lines changed

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, CheckedMultiplyFractionalError, 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: 10 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,15 @@ impl DivideByZeroError {
556556
}
557557
}
558558

559+
#[derive(Error, Debug, PartialEq, Eq)]
560+
pub enum CheckedMultiplyFractionalError {
561+
#[error("{0}")]
562+
DivideByZero(#[from] DivideByZeroError),
563+
564+
#[error("{0}")]
565+
ConversionOverflow(#[from] ConversionOverflowError),
566+
}
567+
559568
#[derive(Error, Debug, PartialEq, Eq)]
560569
pub enum CheckedMultiplyRatioError {
561570
#[error("Denominator must not be zero")]

packages/std/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub use crate::ibc::{
4646
#[cfg(feature = "iterator")]
4747
pub use crate::iterator::{Order, Record};
4848
pub use crate::math::{
49-
Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128,
49+
Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fractional, Isqrt, Uint128,
5050
Uint256, Uint512, Uint64,
5151
};
5252
pub use crate::never::Never;

packages/std/src/math/decimal.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::errors::{
1212
OverflowOperation, RoundUpOverflowError, StdError,
1313
};
1414

15-
use super::Fraction;
15+
use super::Fractional;
1616
use super::Isqrt;
1717
use super::{Uint128, Uint256};
1818

@@ -359,7 +359,7 @@ impl Decimal {
359359
}
360360
}
361361

362-
impl Fraction<Uint128> for Decimal {
362+
impl Fractional<Uint128> for Decimal {
363363
#[inline]
364364
fn numerator(&self) -> Uint128 {
365365
self.0

packages/std/src/math/decimal256.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::errors::{
1313
};
1414
use crate::{Decimal, Uint512};
1515

16-
use super::Fraction;
16+
use super::Fractional;
1717
use super::Isqrt;
1818
use super::Uint256;
1919

@@ -376,7 +376,7 @@ impl Decimal256 {
376376
}
377377
}
378378

379-
impl Fraction<Uint256> for Decimal256 {
379+
impl Fractional<Uint256> for Decimal256 {
380380
#[inline]
381381
fn numerator(&self) -> Uint256 {
382382
self.0

packages/std/src/math/fraction.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use serde::{Deserialize, Serialize};
2+
13
/// A fraction `p`/`q` with integers `p` and `q`.
24
///
35
/// `p` is called the numerator and `q` is called the denominator.
4-
pub trait Fraction<T>: Sized {
6+
pub trait Fractional<T>: Sized {
57
/// Returns the numerator `p`
68
fn numerator(&self) -> T;
79
/// Returns the denominator `q`
@@ -12,3 +14,26 @@ pub trait Fraction<T>: Sized {
1214
/// If `p` is zero, None is returned.
1315
fn inv(&self) -> Option<Self>;
1416
}
17+
18+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
19+
pub struct Fraction<T>(T, T);
20+
21+
impl<T> Fraction<T> {
22+
pub fn new(numerator: T, denominator: T) -> Self {
23+
Self(numerator, denominator)
24+
}
25+
}
26+
27+
impl<T: Clone> Fractional<T> for Fraction<T> {
28+
fn numerator(&self) -> T {
29+
self.0.clone()
30+
}
31+
32+
fn denominator(&self) -> T {
33+
self.1.clone()
34+
}
35+
36+
fn inv(&self) -> Option<Self> {
37+
unimplemented!()
38+
}
39+
}

packages/std/src/math/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod uint64;
99

1010
pub use decimal::{Decimal, DecimalRangeExceeded};
1111
pub use decimal256::{Decimal256, Decimal256RangeExceeded};
12-
pub use fraction::Fraction;
12+
pub use fraction::Fractional;
1313
pub use isqrt::Isqrt;
1414
pub use uint128::Uint128;
1515
pub use uint256::Uint256;

packages/std/src/math/uint128.rs

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use std::ops::{
88
use std::str::FromStr;
99

1010
use crate::errors::{
11-
CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, StdError,
11+
CheckedMultiplyFractionalError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
12+
OverflowOperation, StdError,
1213
};
13-
use crate::{ConversionOverflowError, Uint256, Uint64};
14+
use crate::{ConversionOverflowError, Fractional, Uint256, Uint64};
1415

1516
/// A thin wrapper around u128 that is using strings for JSON encoding/decoding,
1617
/// such that the full u128 range can be used for clients that convert JSON numbers to floats,
@@ -136,6 +137,37 @@ impl Uint128 {
136137
.unwrap()
137138
}
138139

140+
pub fn mul_floored<F: Fractional<T>, T: Into<u128>>(self, rhs: F) -> Self {
141+
self.checked_mul_floored(rhs).unwrap()
142+
}
143+
144+
pub fn checked_mul_floored<F: Fractional<T>, T: Into<u128>>(
145+
self,
146+
rhs: F,
147+
) -> Result<Self, CheckedMultiplyFractionalError> {
148+
let res = self
149+
.full_mul(rhs.numerator())
150+
.checked_div(Uint256::from(rhs.denominator().into()))?;
151+
Ok(res.try_into()?)
152+
}
153+
154+
pub fn mul_ceil<F: Fractional<T> + Clone, T: Into<u128>>(self, rhs: F) -> Self {
155+
self.checked_mul_ceil(rhs).unwrap()
156+
}
157+
158+
pub fn checked_mul_ceil<F: Fractional<T> + Clone, T: Into<u128>>(
159+
self,
160+
rhs: F,
161+
) -> Result<Self, CheckedMultiplyFractionalError> {
162+
let mut result = self.checked_mul_floored(rhs.clone())?;
163+
let numerator = Uint256::from(rhs.numerator().into());
164+
let denominator = Uint256::from(rhs.denominator().into());
165+
if !numerator.checked_rem(denominator)?.is_zero() {
166+
result += Uint128::one();
167+
};
168+
Ok(result)
169+
}
170+
139171
pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
140172
self.0
141173
.checked_add(other.0)
@@ -538,7 +570,9 @@ impl PartialEq<Uint128> for &Uint128 {
538570
#[cfg(test)]
539571
mod tests {
540572
use super::*;
541-
use crate::{from_slice, to_vec};
573+
use crate::errors::CheckedMultiplyFractionalError::{ConversionOverflow, DivideByZero};
574+
use crate::math::fraction::Fraction;
575+
use crate::{from_slice, to_vec, Decimal};
542576

543577
#[test]
544578
fn size_of_works() {
@@ -1039,4 +1073,136 @@ mod tests {
10391073
assert_eq!(&lhs == &rhs, expected);
10401074
}
10411075
}
1076+
1077+
#[test]
1078+
fn mul_floored_works_with_zero() {
1079+
let fraction = Fraction::new(Uint128::zero(), Uint128::new(21));
1080+
let res = Uint128::new(123456).mul_floored(fraction);
1081+
assert_eq!(Uint128::zero(), res)
1082+
}
1083+
1084+
#[test]
1085+
fn mul_floored_does_nothing_with_one() {
1086+
let fraction = Fraction::new(Uint128::one(), Uint128::one());
1087+
let res = Uint128::new(123456).mul_floored(fraction);
1088+
assert_eq!(Uint128::new(123456), res)
1089+
}
1090+
1091+
#[test]
1092+
fn mul_floored_rounds_down_with_normal_case() {
1093+
let fraction = Fraction::new(8u128, 21u128);
1094+
let res = Uint128::new(123456).mul_floored(fraction); // 47030.8571
1095+
assert_eq!(Uint128::new(47030), res)
1096+
}
1097+
1098+
#[test]
1099+
fn mul_floored_works_with_decimal() {
1100+
let decimal = Decimal::from_ratio(8u128, 21u128);
1101+
let res = Uint128::new(123456).mul_floored(decimal); // 47030.8571
1102+
assert_eq!(Uint128::new(47030), res)
1103+
}
1104+
1105+
#[test]
1106+
#[should_panic(expected = "ConversionOverflowError")]
1107+
fn mul_floored_panics_on_overflow() {
1108+
let fraction = Fraction::new(21u128, 8u128);
1109+
Uint128::MAX.mul_floored(fraction);
1110+
}
1111+
1112+
#[test]
1113+
fn checked_mul_floored_does_not_panic_on_overflow() {
1114+
let fraction = Fraction::new(21u128, 8u128);
1115+
assert_eq!(
1116+
Uint128::MAX.checked_mul_floored(fraction),
1117+
Err(ConversionOverflow(ConversionOverflowError {
1118+
source_type: "Uint256",
1119+
target_type: "Uint128",
1120+
value: "893241213167463466591358344508391555069".to_string()
1121+
})),
1122+
);
1123+
}
1124+
1125+
#[test]
1126+
#[should_panic(expected = "DivideByZeroError")]
1127+
fn mul_floored_panics_on_zero_div() {
1128+
let fraction = Fraction::new(21u128, 0u128);
1129+
Uint128::new(123456).mul_floored(fraction);
1130+
}
1131+
1132+
#[test]
1133+
fn checked_mul_floored_does_not_panic_on_zero_div() {
1134+
let fraction = Fraction::new(21u128, 0u128);
1135+
assert_eq!(
1136+
Uint128::new(123456).checked_mul_floored(fraction),
1137+
Err(DivideByZero(DivideByZeroError {
1138+
operand: "2592576".to_string()
1139+
})),
1140+
);
1141+
}
1142+
1143+
#[test]
1144+
fn mul_ceil_works_with_zero() {
1145+
let fraction = Fraction::new(Uint128::zero(), Uint128::new(21));
1146+
let res = Uint128::new(123456).mul_ceil(fraction);
1147+
assert_eq!(Uint128::zero(), res)
1148+
}
1149+
1150+
#[test]
1151+
fn mul_ceil_does_nothing_with_one() {
1152+
let fraction = Fraction::new(Uint128::one(), Uint128::one());
1153+
let res = Uint128::new(123456).mul_ceil(fraction);
1154+
assert_eq!(Uint128::new(123456), res)
1155+
}
1156+
1157+
#[test]
1158+
fn mul_ceil_rounds_up_with_normal_case() {
1159+
let fraction = Fraction::new(8u128, 21u128);
1160+
let res = Uint128::new(123456).mul_ceil(fraction); // 47030.8571
1161+
assert_eq!(Uint128::new(47031), res)
1162+
}
1163+
1164+
#[test]
1165+
fn mul_ceil_works_with_decimal() {
1166+
let decimal = Decimal::from_ratio(8u128, 21u128);
1167+
let res = Uint128::new(123456).mul_ceil(decimal); // 47030.8571
1168+
assert_eq!(Uint128::new(47031), res)
1169+
}
1170+
1171+
#[test]
1172+
#[should_panic(expected = "ConversionOverflowError")]
1173+
fn mul_ceil_panics_on_overflow() {
1174+
let fraction = Fraction::new(21u128, 8u128);
1175+
Uint128::MAX.mul_ceil(fraction);
1176+
}
1177+
1178+
#[test]
1179+
fn checked_mul_ceil_does_not_panic_on_overflow() {
1180+
let fraction = Fraction::new(21u128, 8u128);
1181+
assert_eq!(
1182+
Uint128::MAX.checked_mul_ceil(fraction),
1183+
Err(ConversionOverflow(ConversionOverflowError {
1184+
source_type: "Uint256",
1185+
target_type: "Uint128",
1186+
value: "893241213167463466591358344508391555069".to_string()
1187+
})),
1188+
);
1189+
}
1190+
1191+
#[test]
1192+
#[should_panic(expected = "DivideByZeroError")]
1193+
fn mul_ceil_panics_on_zero_div() {
1194+
let fraction = Fraction::new(21u128, 0u128);
1195+
Uint128::new(123456).mul_ceil(fraction);
1196+
}
1197+
1198+
#[test]
1199+
fn checked_mul_ceil_does_not_panic_on_zero_div() {
1200+
let fraction = Fraction::new(21u128, 0u128);
1201+
assert_eq!(
1202+
Uint128::new(123456).checked_mul_ceil(fraction),
1203+
Err(DivideByZero(DivideByZeroError {
1204+
operand: "2592576".to_string()
1205+
})),
1206+
);
1207+
}
10421208
}

0 commit comments

Comments
 (0)