Skip to content

Commit 7e13d25

Browse files
authored
Merge pull request #1603 from CosmWasm/convert-decimal-to-int
Add Decimal{,256}::to_uint_floor and ::to_uint_ceil
2 parents 35a2bd5 + 6ca600b commit 7e13d25

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

packages/std/src/math/decimal.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,58 @@ impl Decimal {
357357
Err(_) => Self::MAX,
358358
}
359359
}
360+
361+
/// Converts this decimal to an unsigned integer by truncating
362+
/// the fractional part, e.g. 22.5 becomes 22.
363+
///
364+
/// ## Examples
365+
///
366+
/// ```
367+
/// use std::str::FromStr;
368+
/// use cosmwasm_std::{Decimal, Uint128};
369+
///
370+
/// let d = Decimal::from_str("12.345").unwrap();
371+
/// assert_eq!(d.to_uint_floor(), Uint128::new(12));
372+
///
373+
/// let d = Decimal::from_str("12.999").unwrap();
374+
/// assert_eq!(d.to_uint_floor(), Uint128::new(12));
375+
///
376+
/// let d = Decimal::from_str("75.0").unwrap();
377+
/// assert_eq!(d.to_uint_floor(), Uint128::new(75));
378+
/// ```
379+
pub fn to_uint_floor(self) -> Uint128 {
380+
self.0 / Self::DECIMAL_FRACTIONAL
381+
}
382+
383+
/// Converts this decimal to an unsigned integer by rounting up
384+
/// to the next integer, e.g. 22.3 becomes 23.
385+
///
386+
/// ## Examples
387+
///
388+
/// ```
389+
/// use std::str::FromStr;
390+
/// use cosmwasm_std::{Decimal, Uint128};
391+
///
392+
/// let d = Decimal::from_str("12.345").unwrap();
393+
/// assert_eq!(d.to_uint_ceil(), Uint128::new(13));
394+
///
395+
/// let d = Decimal::from_str("12.999").unwrap();
396+
/// assert_eq!(d.to_uint_ceil(), Uint128::new(13));
397+
///
398+
/// let d = Decimal::from_str("75.0").unwrap();
399+
/// assert_eq!(d.to_uint_ceil(), Uint128::new(75));
400+
/// ```
401+
pub fn to_uint_ceil(self) -> Uint128 {
402+
// Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q
403+
// from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow.
404+
let x = self.0;
405+
let y = Self::DECIMAL_FRACTIONAL;
406+
if x.is_zero() {
407+
Uint128::zero()
408+
} else {
409+
Uint128::one() + ((x - Uint128::one()) / y)
410+
}
411+
}
360412
}
361413

362414
impl Fraction<Uint128> for Decimal {
@@ -2002,6 +2054,57 @@ mod tests {
20022054
));
20032055
}
20042056

2057+
#[test]
2058+
fn decimal_to_uint_floor_works() {
2059+
let d = Decimal::from_str("12.000000000000000001").unwrap();
2060+
assert_eq!(d.to_uint_floor(), Uint128::new(12));
2061+
let d = Decimal::from_str("12.345").unwrap();
2062+
assert_eq!(d.to_uint_floor(), Uint128::new(12));
2063+
let d = Decimal::from_str("12.999").unwrap();
2064+
assert_eq!(d.to_uint_floor(), Uint128::new(12));
2065+
let d = Decimal::from_str("0.98451384").unwrap();
2066+
assert_eq!(d.to_uint_floor(), Uint128::new(0));
2067+
2068+
let d = Decimal::from_str("75.0").unwrap();
2069+
assert_eq!(d.to_uint_floor(), Uint128::new(75));
2070+
let d = Decimal::from_str("0.0").unwrap();
2071+
assert_eq!(d.to_uint_floor(), Uint128::new(0));
2072+
2073+
let d = Decimal::MAX;
2074+
assert_eq!(d.to_uint_floor(), Uint128::new(340282366920938463463));
2075+
2076+
// Does the same as the old workaround `Uint128::one() * my_decimal`.
2077+
// This block can be deleted as part of https://github.com/CosmWasm/cosmwasm/issues/1485.
2078+
let tests = vec![
2079+
Decimal::from_str("12.345").unwrap(),
2080+
Decimal::from_str("0.98451384").unwrap(),
2081+
Decimal::from_str("178.0").unwrap(),
2082+
Decimal::MIN,
2083+
Decimal::MAX,
2084+
];
2085+
for my_decimal in tests.into_iter() {
2086+
assert_eq!(my_decimal.to_uint_floor(), Uint128::one() * my_decimal);
2087+
}
2088+
}
2089+
2090+
#[test]
2091+
fn decimal_to_uint_ceil_works() {
2092+
let d = Decimal::from_str("12.000000000000000001").unwrap();
2093+
assert_eq!(d.to_uint_ceil(), Uint128::new(13));
2094+
let d = Decimal::from_str("12.345").unwrap();
2095+
assert_eq!(d.to_uint_ceil(), Uint128::new(13));
2096+
let d = Decimal::from_str("12.999").unwrap();
2097+
assert_eq!(d.to_uint_ceil(), Uint128::new(13));
2098+
2099+
let d = Decimal::from_str("75.0").unwrap();
2100+
assert_eq!(d.to_uint_ceil(), Uint128::new(75));
2101+
let d = Decimal::from_str("0.0").unwrap();
2102+
assert_eq!(d.to_uint_ceil(), Uint128::new(0));
2103+
2104+
let d = Decimal::MAX;
2105+
assert_eq!(d.to_uint_ceil(), Uint128::new(340282366920938463464));
2106+
}
2107+
20052108
#[test]
20062109
fn decimal_partial_eq() {
20072110
let test_cases = [

packages/std/src/math/decimal256.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,58 @@ impl Decimal256 {
374374
Err(_) => Self::MAX,
375375
}
376376
}
377+
378+
/// Converts this decimal to an unsigned integer by truncating
379+
/// the fractional part, e.g. 22.5 becomes 22.
380+
///
381+
/// ## Examples
382+
///
383+
/// ```
384+
/// use std::str::FromStr;
385+
/// use cosmwasm_std::{Decimal256, Uint256};
386+
///
387+
/// let d = Decimal256::from_str("12.345").unwrap();
388+
/// assert_eq!(d.to_uint_floor(), Uint256::from(12u64));
389+
///
390+
/// let d = Decimal256::from_str("12.999").unwrap();
391+
/// assert_eq!(d.to_uint_floor(), Uint256::from(12u64));
392+
///
393+
/// let d = Decimal256::from_str("75.0").unwrap();
394+
/// assert_eq!(d.to_uint_floor(), Uint256::from(75u64));
395+
/// ```
396+
pub fn to_uint_floor(self) -> Uint256 {
397+
self.0 / Self::DECIMAL_FRACTIONAL
398+
}
399+
400+
/// Converts this decimal to an unsigned integer by rounting up
401+
/// to the next integer, e.g. 22.3 becomes 23.
402+
///
403+
/// ## Examples
404+
///
405+
/// ```
406+
/// use std::str::FromStr;
407+
/// use cosmwasm_std::{Decimal256, Uint256};
408+
///
409+
/// let d = Decimal256::from_str("12.345").unwrap();
410+
/// assert_eq!(d.to_uint_ceil(), Uint256::from(13u64));
411+
///
412+
/// let d = Decimal256::from_str("12.999").unwrap();
413+
/// assert_eq!(d.to_uint_ceil(), Uint256::from(13u64));
414+
///
415+
/// let d = Decimal256::from_str("75.0").unwrap();
416+
/// assert_eq!(d.to_uint_ceil(), Uint256::from(75u64));
417+
/// ```
418+
pub fn to_uint_ceil(self) -> Uint256 {
419+
// Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q
420+
// from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow.
421+
let x = self.0;
422+
let y = Self::DECIMAL_FRACTIONAL;
423+
if x.is_zero() {
424+
Uint256::zero()
425+
} else {
426+
Uint256::one() + ((x - Uint256::one()) / y)
427+
}
428+
}
377429
}
378430

379431
impl Fraction<Uint256> for Decimal256 {
@@ -2149,6 +2201,65 @@ mod tests {
21492201
assert_eq!(Decimal256::MAX.checked_ceil(), Err(RoundUpOverflowError));
21502202
}
21512203

2204+
#[test]
2205+
fn decimal256_to_uint_floor_works() {
2206+
let d = Decimal256::from_str("12.000000000000000001").unwrap();
2207+
assert_eq!(d.to_uint_floor(), Uint256::from_u128(12));
2208+
let d = Decimal256::from_str("12.345").unwrap();
2209+
assert_eq!(d.to_uint_floor(), Uint256::from_u128(12));
2210+
let d = Decimal256::from_str("12.999").unwrap();
2211+
assert_eq!(d.to_uint_floor(), Uint256::from_u128(12));
2212+
let d = Decimal256::from_str("0.98451384").unwrap();
2213+
assert_eq!(d.to_uint_floor(), Uint256::from_u128(0));
2214+
2215+
let d = Decimal256::from_str("75.0").unwrap();
2216+
assert_eq!(d.to_uint_floor(), Uint256::from_u128(75));
2217+
let d = Decimal256::from_str("0.0").unwrap();
2218+
assert_eq!(d.to_uint_floor(), Uint256::from_u128(0));
2219+
2220+
let d = Decimal256::MAX;
2221+
assert_eq!(
2222+
d.to_uint_floor(),
2223+
Uint256::from_str("115792089237316195423570985008687907853269984665640564039457")
2224+
.unwrap()
2225+
);
2226+
2227+
// Does the same as the old workaround `Uint256::one() * my_decimal`.
2228+
// This block can be deleted as part of https://github.com/CosmWasm/cosmwasm/issues/1485.
2229+
let tests = vec![
2230+
Decimal256::from_str("12.345").unwrap(),
2231+
Decimal256::from_str("0.98451384").unwrap(),
2232+
Decimal256::from_str("178.0").unwrap(),
2233+
Decimal256::MIN,
2234+
Decimal256::MAX,
2235+
];
2236+
for my_decimal in tests.into_iter() {
2237+
assert_eq!(my_decimal.to_uint_floor(), Uint256::one() * my_decimal);
2238+
}
2239+
}
2240+
2241+
#[test]
2242+
fn decimal256_to_uint_ceil_works() {
2243+
let d = Decimal256::from_str("12.000000000000000001").unwrap();
2244+
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(13));
2245+
let d = Decimal256::from_str("12.345").unwrap();
2246+
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(13));
2247+
let d = Decimal256::from_str("12.999").unwrap();
2248+
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(13));
2249+
2250+
let d = Decimal256::from_str("75.0").unwrap();
2251+
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(75));
2252+
let d = Decimal256::from_str("0.0").unwrap();
2253+
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(0));
2254+
2255+
let d = Decimal256::MAX;
2256+
assert_eq!(
2257+
d.to_uint_ceil(),
2258+
Uint256::from_str("115792089237316195423570985008687907853269984665640564039458")
2259+
.unwrap()
2260+
);
2261+
}
2262+
21522263
#[test]
21532264
fn decimal256_partial_eq() {
21542265
let test_cases = [

0 commit comments

Comments
 (0)