Skip to content

Commit e79ab95

Browse files
committed
Add fraction division support
1 parent 8b63fcd commit e79ab95

File tree

4 files changed

+451
-0
lines changed

4 files changed

+451
-0
lines changed

packages/std/src/math/fraction.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,53 @@ macro_rules! impl_mul_fraction {
6969
pub fn mul_ceil<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self {
7070
self.checked_mul_ceil(rhs).unwrap()
7171
}
72+
73+
pub fn div_floor<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self
74+
where
75+
Self: Sized,
76+
{
77+
self.checked_div_floor(rhs).unwrap()
78+
}
79+
80+
pub fn checked_div_floor<F: Fraction<T>, T: Into<$Uint>>(
81+
self,
82+
rhs: F,
83+
) -> Result<Self, CheckedMultiplyFractionError>
84+
where
85+
Self: Sized,
86+
{
87+
let divisor = rhs.numerator().into();
88+
let res = self
89+
.full_mul(rhs.denominator().into())
90+
.checked_div(divisor.into())?;
91+
Ok(res.try_into()?)
92+
}
93+
94+
pub fn div_ceil<F: Fraction<T>, T: Into<$Uint>>(self, rhs: F) -> Self
95+
where
96+
Self: Sized,
97+
{
98+
self.checked_div_ceil(rhs).unwrap()
99+
}
100+
101+
pub fn checked_div_ceil<F: Fraction<T>, T: Into<$Uint>>(
102+
self,
103+
rhs: F,
104+
) -> Result<Self, CheckedMultiplyFractionError>
105+
where
106+
Self: Sized,
107+
{
108+
let divisor = rhs.numerator().into();
109+
let remainder = self
110+
.full_mul(rhs.denominator().into())
111+
.checked_rem(divisor.into())?;
112+
let floor_result = self.checked_div_floor(rhs)?;
113+
if !remainder.is_zero() {
114+
Ok($Uint::one().checked_add(floor_result)?)
115+
} else {
116+
Ok(floor_result)
117+
}
118+
}
72119
}
73120
};
74121
}

packages/std/src/math/uint128.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,4 +1211,146 @@ mod tests {
12111211
})),
12121212
);
12131213
}
1214+
1215+
#[test]
1216+
#[should_panic(expected = "DivideByZeroError")]
1217+
fn div_floor_raises_with_zero() {
1218+
let fraction = (Uint128::zero(), Uint128::new(21));
1219+
Uint128::new(123456).div_floor(fraction);
1220+
}
1221+
1222+
#[test]
1223+
fn div_floor_does_nothing_with_one() {
1224+
let fraction = (Uint128::one(), Uint128::one());
1225+
let res = Uint128::new(123456).div_floor(fraction);
1226+
assert_eq!(Uint128::new(123456), res)
1227+
}
1228+
1229+
#[test]
1230+
fn div_floor_rounds_down_with_normal_case() {
1231+
let fraction = (5u128, 21u128);
1232+
let res = Uint128::new(123456).div_floor(fraction); // 518515.2
1233+
assert_eq!(Uint128::new(518515), res)
1234+
}
1235+
1236+
#[test]
1237+
fn div_floor_does_not_round_on_even_divide() {
1238+
let fraction = (5u128, 2u128);
1239+
let res = Uint128::new(25).div_floor(fraction);
1240+
assert_eq!(Uint128::new(10), res)
1241+
}
1242+
1243+
#[test]
1244+
fn div_floor_works_when_operation_temporarily_takes_above_max() {
1245+
let fraction = (21u128, 8u128);
1246+
let res = Uint128::MAX.div_floor(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428
1247+
assert_eq!(
1248+
Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697),
1249+
res
1250+
)
1251+
}
1252+
1253+
#[test]
1254+
fn div_floor_works_with_decimal() {
1255+
let decimal = Decimal::from_ratio(21u128, 8u128);
1256+
let res = Uint128::new(123456).div_floor(decimal); // 47030.8571
1257+
assert_eq!(Uint128::new(47030), res)
1258+
}
1259+
1260+
#[test]
1261+
fn div_floor_works_with_decimal_evenly() {
1262+
let res = Uint128::new(60).div_floor(Decimal::from_atomics(6u128, 0).unwrap());
1263+
assert_eq!(res, Uint128::new(10));
1264+
}
1265+
1266+
#[test]
1267+
#[should_panic(expected = "ConversionOverflowError")]
1268+
fn div_floor_panics_on_overflow() {
1269+
let fraction = (8u128, 21u128);
1270+
Uint128::MAX.div_floor(fraction);
1271+
}
1272+
1273+
#[test]
1274+
fn div_floor_does_not_panic_on_overflow() {
1275+
let fraction = (8u128, 21u128);
1276+
assert_eq!(
1277+
Uint128::MAX.checked_div_floor(fraction),
1278+
Err(ConversionOverflow(ConversionOverflowError {
1279+
source_type: "Uint256",
1280+
target_type: "Uint128",
1281+
value: "893241213167463466591358344508391555069".to_string()
1282+
})),
1283+
);
1284+
}
1285+
1286+
#[test]
1287+
#[should_panic(expected = "DivideByZeroError")]
1288+
fn div_ceil_raises_with_zero() {
1289+
let fraction = (Uint128::zero(), Uint128::new(21));
1290+
Uint128::new(123456).div_ceil(fraction);
1291+
}
1292+
1293+
#[test]
1294+
fn div_ceil_does_nothing_with_one() {
1295+
let fraction = (Uint128::one(), Uint128::one());
1296+
let res = Uint128::new(123456).div_ceil(fraction);
1297+
assert_eq!(Uint128::new(123456), res)
1298+
}
1299+
1300+
#[test]
1301+
fn div_ceil_rounds_up_with_normal_case() {
1302+
let fraction = (5u128, 21u128);
1303+
let res = Uint128::new(123456).div_ceil(fraction); // 518515.2
1304+
assert_eq!(Uint128::new(518516), res)
1305+
}
1306+
1307+
#[test]
1308+
fn div_ceil_does_not_round_on_even_divide() {
1309+
let fraction = (5u128, 2u128);
1310+
let res = Uint128::new(25).div_ceil(fraction);
1311+
assert_eq!(Uint128::new(10), res)
1312+
}
1313+
1314+
#[test]
1315+
fn div_ceil_works_when_operation_temporarily_takes_above_max() {
1316+
let fraction = (21u128, 8u128);
1317+
let res = Uint128::MAX.div_ceil(fraction); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428
1318+
assert_eq!(
1319+
Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698),
1320+
res
1321+
)
1322+
}
1323+
1324+
#[test]
1325+
fn div_ceil_works_with_decimal() {
1326+
let decimal = Decimal::from_ratio(21u128, 8u128);
1327+
let res = Uint128::new(123456).div_ceil(decimal); // 47030.8571
1328+
assert_eq!(Uint128::new(47031), res)
1329+
}
1330+
1331+
#[test]
1332+
fn div_ceil_works_with_decimal_evenly() {
1333+
let res = Uint128::new(60).div_ceil(Decimal::from_atomics(6u128, 0).unwrap());
1334+
assert_eq!(res, Uint128::new(10));
1335+
}
1336+
1337+
#[test]
1338+
#[should_panic(expected = "ConversionOverflowError")]
1339+
fn div_ceil_panics_on_overflow() {
1340+
let fraction = (8u128, 21u128);
1341+
Uint128::MAX.div_ceil(fraction);
1342+
}
1343+
1344+
#[test]
1345+
fn div_ceil_does_not_panic_on_overflow() {
1346+
let fraction = (8u128, 21u128);
1347+
assert_eq!(
1348+
Uint128::MAX.checked_div_ceil(fraction),
1349+
Err(ConversionOverflow(ConversionOverflowError {
1350+
source_type: "Uint256",
1351+
target_type: "Uint128",
1352+
value: "893241213167463466591358344508391555069".to_string()
1353+
})),
1354+
);
1355+
}
12141356
}

packages/std/src/math/uint256.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,4 +1857,156 @@ mod tests {
18571857
})),
18581858
);
18591859
}
1860+
1861+
#[test]
1862+
#[should_panic(expected = "DivideByZeroError")]
1863+
fn div_floor_raises_with_zero() {
1864+
let fraction = (Uint256::zero(), Uint256::from(21u32));
1865+
Uint256::from(123456u128).div_floor(fraction);
1866+
}
1867+
1868+
#[test]
1869+
fn div_floor_does_nothing_with_one() {
1870+
let fraction = (Uint256::one(), Uint256::one());
1871+
let res = Uint256::from(123456u128).div_floor(fraction);
1872+
assert_eq!(Uint256::from(123456u128), res)
1873+
}
1874+
1875+
#[test]
1876+
fn div_floor_rounds_down_with_normal_case() {
1877+
let fraction = (5u128, 21u128);
1878+
let res = Uint256::from(123456u128).div_floor(fraction); // 518515.2
1879+
assert_eq!(Uint256::from(518515u128), res)
1880+
}
1881+
1882+
#[test]
1883+
fn div_floor_does_not_round_on_even_divide() {
1884+
let fraction = (5u128, 2u128);
1885+
let res = Uint256::from(25u128).div_floor(fraction);
1886+
assert_eq!(Uint256::from(10u128), res)
1887+
}
1888+
1889+
#[test]
1890+
fn div_floor_works_when_operation_temporarily_takes_above_max() {
1891+
let fraction = (21u128, 8u128);
1892+
let res = Uint256::MAX.div_floor(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571
1893+
assert_eq!(
1894+
Uint256::from_str(
1895+
"44111272090406169685169899050928726801245708444053548205507651050633573196165"
1896+
)
1897+
.unwrap(),
1898+
res
1899+
)
1900+
}
1901+
1902+
#[test]
1903+
fn div_floor_works_with_decimal() {
1904+
let decimal = Decimal::from_ratio(21u128, 8u128);
1905+
let res = Uint256::from(123456u128).div_floor(decimal); // 47030.8571
1906+
assert_eq!(Uint256::from(47030u128), res)
1907+
}
1908+
1909+
#[test]
1910+
fn div_floor_works_with_decimal_evenly() {
1911+
let res = Uint256::from(60u128).div_floor(Decimal::from_atomics(6u128, 0).unwrap());
1912+
assert_eq!(res, Uint256::from(10u128));
1913+
}
1914+
1915+
#[test]
1916+
#[should_panic(expected = "ConversionOverflowError")]
1917+
fn div_floor_panics_on_overflow() {
1918+
let fraction = (8u128, 21u128);
1919+
Uint256::MAX.div_floor(fraction);
1920+
}
1921+
1922+
#[test]
1923+
fn div_floor_does_not_panic_on_overflow() {
1924+
let fraction = (8u128, 21u128);
1925+
assert_eq!(
1926+
Uint256::MAX.checked_div_floor(fraction),
1927+
Err(ConversionOverflow(ConversionOverflowError {
1928+
source_type: "Uint512",
1929+
target_type: "Uint256",
1930+
value:
1931+
"303954234247955012986873835647805758114833709747306480603576158020771965304829"
1932+
.to_string()
1933+
})),
1934+
);
1935+
}
1936+
1937+
#[test]
1938+
#[should_panic(expected = "DivideByZeroError")]
1939+
fn div_ceil_raises_with_zero() {
1940+
let fraction = (Uint256::zero(), Uint256::from(21u128));
1941+
Uint256::from(123456u128).div_ceil(fraction);
1942+
}
1943+
1944+
#[test]
1945+
fn div_ceil_does_nothing_with_one() {
1946+
let fraction = (Uint256::one(), Uint256::one());
1947+
let res = Uint256::from(123456u128).div_ceil(fraction);
1948+
assert_eq!(Uint256::from(123456u128), res)
1949+
}
1950+
1951+
#[test]
1952+
fn div_ceil_rounds_up_with_normal_case() {
1953+
let fraction = (5u128, 21u128);
1954+
let res = Uint256::from(123456u128).div_ceil(fraction); // 518515.2
1955+
assert_eq!(Uint256::from(518516u128), res)
1956+
}
1957+
1958+
#[test]
1959+
fn div_ceil_does_not_round_on_even_divide() {
1960+
let fraction = (5u128, 2u128);
1961+
let res = Uint256::from(25u128).div_ceil(fraction);
1962+
assert_eq!(Uint256::from(10u128), res)
1963+
}
1964+
1965+
#[test]
1966+
fn div_ceil_works_when_operation_temporarily_takes_above_max() {
1967+
let fraction = (21u128, 8u128);
1968+
let res = Uint256::MAX.div_ceil(fraction); // 44_111_272_090_406_169_685_169_899_050_928_726_801_245_708_444_053_548_205_507_651_050_633_573_196_165.71428571
1969+
assert_eq!(
1970+
Uint256::from_str(
1971+
"44111272090406169685169899050928726801245708444053548205507651050633573196166"
1972+
)
1973+
.unwrap(),
1974+
res
1975+
)
1976+
}
1977+
1978+
#[test]
1979+
fn div_ceil_works_with_decimal() {
1980+
let decimal = Decimal::from_ratio(21u128, 8u128);
1981+
let res = Uint256::from(123456u128).div_ceil(decimal); // 47030.8571
1982+
assert_eq!(Uint256::from(47031u128), res)
1983+
}
1984+
1985+
#[test]
1986+
fn div_ceil_works_with_decimal_evenly() {
1987+
let res = Uint256::from(60u128).div_ceil(Decimal::from_atomics(6u128, 0).unwrap());
1988+
assert_eq!(res, Uint256::from(10u128));
1989+
}
1990+
1991+
#[test]
1992+
#[should_panic(expected = "ConversionOverflowError")]
1993+
fn div_ceil_panics_on_overflow() {
1994+
let fraction = (8u128, 21u128);
1995+
Uint256::MAX.div_ceil(fraction);
1996+
}
1997+
1998+
#[test]
1999+
fn div_ceil_does_not_panic_on_overflow() {
2000+
let fraction = (8u128, 21u128);
2001+
assert_eq!(
2002+
Uint256::MAX.checked_div_ceil(fraction),
2003+
Err(ConversionOverflow(ConversionOverflowError {
2004+
source_type: "Uint512",
2005+
target_type: "Uint256",
2006+
value:
2007+
"303954234247955012986873835647805758114833709747306480603576158020771965304829"
2008+
.to_string() // raises prior to rounding up
2009+
})),
2010+
);
2011+
}
18602012
}

0 commit comments

Comments
 (0)