Skip to content

Commit

Permalink
Merge pull request #1603 from CosmWasm/convert-decimal-to-int
Browse files Browse the repository at this point in the history
Add Decimal{,256}::to_uint_floor and ::to_uint_ceil
  • Loading branch information
webmaster128 authored Jan 30, 2023
2 parents 35a2bd5 + 6ca600b commit 7e13d25
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 0 deletions.
103 changes: 103 additions & 0 deletions packages/std/src/math/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,58 @@ impl Decimal {
Err(_) => Self::MAX,
}
}

/// Converts this decimal to an unsigned integer by truncating
/// the fractional part, e.g. 22.5 becomes 22.
///
/// ## Examples
///
/// ```
/// use std::str::FromStr;
/// use cosmwasm_std::{Decimal, Uint128};
///
/// let d = Decimal::from_str("12.345").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint128::new(12));
///
/// let d = Decimal::from_str("12.999").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint128::new(12));
///
/// let d = Decimal::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint128::new(75));
/// ```
pub fn to_uint_floor(self) -> Uint128 {
self.0 / Self::DECIMAL_FRACTIONAL
}

/// Converts this decimal to an unsigned integer by rounting up
/// to the next integer, e.g. 22.3 becomes 23.
///
/// ## Examples
///
/// ```
/// use std::str::FromStr;
/// use cosmwasm_std::{Decimal, Uint128};
///
/// let d = Decimal::from_str("12.345").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint128::new(13));
///
/// let d = Decimal::from_str("12.999").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint128::new(13));
///
/// let d = Decimal::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint128::new(75));
/// ```
pub fn to_uint_ceil(self) -> Uint128 {
// Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q
// from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow.
let x = self.0;
let y = Self::DECIMAL_FRACTIONAL;
if x.is_zero() {
Uint128::zero()
} else {
Uint128::one() + ((x - Uint128::one()) / y)
}
}
}

impl Fraction<Uint128> for Decimal {
Expand Down Expand Up @@ -2002,6 +2054,57 @@ mod tests {
));
}

#[test]
fn decimal_to_uint_floor_works() {
let d = Decimal::from_str("12.000000000000000001").unwrap();
assert_eq!(d.to_uint_floor(), Uint128::new(12));
let d = Decimal::from_str("12.345").unwrap();
assert_eq!(d.to_uint_floor(), Uint128::new(12));
let d = Decimal::from_str("12.999").unwrap();
assert_eq!(d.to_uint_floor(), Uint128::new(12));
let d = Decimal::from_str("0.98451384").unwrap();
assert_eq!(d.to_uint_floor(), Uint128::new(0));

let d = Decimal::from_str("75.0").unwrap();
assert_eq!(d.to_uint_floor(), Uint128::new(75));
let d = Decimal::from_str("0.0").unwrap();
assert_eq!(d.to_uint_floor(), Uint128::new(0));

let d = Decimal::MAX;
assert_eq!(d.to_uint_floor(), Uint128::new(340282366920938463463));

// Does the same as the old workaround `Uint128::one() * my_decimal`.
// This block can be deleted as part of https://github.com/CosmWasm/cosmwasm/issues/1485.
let tests = vec![
Decimal::from_str("12.345").unwrap(),
Decimal::from_str("0.98451384").unwrap(),
Decimal::from_str("178.0").unwrap(),
Decimal::MIN,
Decimal::MAX,
];
for my_decimal in tests.into_iter() {
assert_eq!(my_decimal.to_uint_floor(), Uint128::one() * my_decimal);
}
}

#[test]
fn decimal_to_uint_ceil_works() {
let d = Decimal::from_str("12.000000000000000001").unwrap();
assert_eq!(d.to_uint_ceil(), Uint128::new(13));
let d = Decimal::from_str("12.345").unwrap();
assert_eq!(d.to_uint_ceil(), Uint128::new(13));
let d = Decimal::from_str("12.999").unwrap();
assert_eq!(d.to_uint_ceil(), Uint128::new(13));

let d = Decimal::from_str("75.0").unwrap();
assert_eq!(d.to_uint_ceil(), Uint128::new(75));
let d = Decimal::from_str("0.0").unwrap();
assert_eq!(d.to_uint_ceil(), Uint128::new(0));

let d = Decimal::MAX;
assert_eq!(d.to_uint_ceil(), Uint128::new(340282366920938463464));
}

#[test]
fn decimal_partial_eq() {
let test_cases = [
Expand Down
111 changes: 111 additions & 0 deletions packages/std/src/math/decimal256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,58 @@ impl Decimal256 {
Err(_) => Self::MAX,
}
}

/// Converts this decimal to an unsigned integer by truncating
/// the fractional part, e.g. 22.5 becomes 22.
///
/// ## Examples
///
/// ```
/// use std::str::FromStr;
/// use cosmwasm_std::{Decimal256, Uint256};
///
/// let d = Decimal256::from_str("12.345").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint256::from(12u64));
///
/// let d = Decimal256::from_str("12.999").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint256::from(12u64));
///
/// let d = Decimal256::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint256::from(75u64));
/// ```
pub fn to_uint_floor(self) -> Uint256 {
self.0 / Self::DECIMAL_FRACTIONAL
}

/// Converts this decimal to an unsigned integer by rounting up
/// to the next integer, e.g. 22.3 becomes 23.
///
/// ## Examples
///
/// ```
/// use std::str::FromStr;
/// use cosmwasm_std::{Decimal256, Uint256};
///
/// let d = Decimal256::from_str("12.345").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint256::from(13u64));
///
/// let d = Decimal256::from_str("12.999").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint256::from(13u64));
///
/// let d = Decimal256::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint256::from(75u64));
/// ```
pub fn to_uint_ceil(self) -> Uint256 {
// Using `q = 1 + ((x - 1) / y); // if x != 0` with unsigned integers x, y, q
// from https://stackoverflow.com/a/2745086/2013738. We know `x + y` CAN overflow.
let x = self.0;
let y = Self::DECIMAL_FRACTIONAL;
if x.is_zero() {
Uint256::zero()
} else {
Uint256::one() + ((x - Uint256::one()) / y)
}
}
}

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

#[test]
fn decimal256_to_uint_floor_works() {
let d = Decimal256::from_str("12.000000000000000001").unwrap();
assert_eq!(d.to_uint_floor(), Uint256::from_u128(12));
let d = Decimal256::from_str("12.345").unwrap();
assert_eq!(d.to_uint_floor(), Uint256::from_u128(12));
let d = Decimal256::from_str("12.999").unwrap();
assert_eq!(d.to_uint_floor(), Uint256::from_u128(12));
let d = Decimal256::from_str("0.98451384").unwrap();
assert_eq!(d.to_uint_floor(), Uint256::from_u128(0));

let d = Decimal256::from_str("75.0").unwrap();
assert_eq!(d.to_uint_floor(), Uint256::from_u128(75));
let d = Decimal256::from_str("0.0").unwrap();
assert_eq!(d.to_uint_floor(), Uint256::from_u128(0));

let d = Decimal256::MAX;
assert_eq!(
d.to_uint_floor(),
Uint256::from_str("115792089237316195423570985008687907853269984665640564039457")
.unwrap()
);

// Does the same as the old workaround `Uint256::one() * my_decimal`.
// This block can be deleted as part of https://github.com/CosmWasm/cosmwasm/issues/1485.
let tests = vec![
Decimal256::from_str("12.345").unwrap(),
Decimal256::from_str("0.98451384").unwrap(),
Decimal256::from_str("178.0").unwrap(),
Decimal256::MIN,
Decimal256::MAX,
];
for my_decimal in tests.into_iter() {
assert_eq!(my_decimal.to_uint_floor(), Uint256::one() * my_decimal);
}
}

#[test]
fn decimal256_to_uint_ceil_works() {
let d = Decimal256::from_str("12.000000000000000001").unwrap();
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(13));
let d = Decimal256::from_str("12.345").unwrap();
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(13));
let d = Decimal256::from_str("12.999").unwrap();
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(13));

let d = Decimal256::from_str("75.0").unwrap();
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(75));
let d = Decimal256::from_str("0.0").unwrap();
assert_eq!(d.to_uint_ceil(), Uint256::from_u128(0));

let d = Decimal256::MAX;
assert_eq!(
d.to_uint_ceil(),
Uint256::from_str("115792089237316195423570985008687907853269984665640564039458")
.unwrap()
);
}

#[test]
fn decimal256_partial_eq() {
let test_cases = [
Expand Down

0 comments on commit 7e13d25

Please sign in to comment.