Skip to content

Commit

Permalink
Merge pull request #1807 from CosmWasm/1711-signed-decimals
Browse files Browse the repository at this point in the history
SignedDecimal implementation
  • Loading branch information
chipshort committed Sep 25, 2023
2 parents 09555ac + 9989ab9 commit 1e1eb5e
Show file tree
Hide file tree
Showing 9 changed files with 6,483 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ and this project adheres to
- cosmwasm-std: Add `to_json_{vec,binary,string}` and `from_json` and deprecate
`to_{vec,binary}` in favor of `to_json_{vec,binary}` and `from_{slice,binary}`
in favor of `from_json`. ([#1886])
- cosmwasm-std: Add `SignedDecimal` and `SignedDecimal256` ([#1807]).

[#1854]: https://github.com/CosmWasm/cosmwasm/pull/1854
[#1861]: https://github.com/CosmWasm/cosmwasm/pull/1861
[#1866]: https://github.com/CosmWasm/cosmwasm/pull/1866
[#1867]: https://github.com/CosmWasm/cosmwasm/pull/1867
[#1870]: https://github.com/CosmWasm/cosmwasm/pull/1870
[#1886]: https://github.com/CosmWasm/cosmwasm/pull/1886
[#1807]: https://github.com/CosmWasm/cosmwasm/pull/1807

## [1.4.0] - 2023-09-04

Expand Down
3 changes: 2 additions & 1 deletion packages/std/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ pub use recover_pubkey_error::RecoverPubkeyError;
pub use std_error::{
CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
CoinFromStrError, CoinsError, ConversionOverflowError, DivideByZeroError, DivisionError,
OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult,
OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError,
StdResult,
};
pub use system_error::SystemError;
pub use verification_error::VerificationError;
4 changes: 4 additions & 0 deletions packages/std/src/errors/std_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,10 @@ pub enum CheckedFromRatioError {
#[error("Round up operation failed because of overflow")]
pub struct RoundUpOverflowError;

#[derive(Error, Debug, PartialEq, Eq)]
#[error("Round down operation failed because of overflow")]
pub struct RoundDownOverflowError;

#[derive(Error, Debug, PartialEq, Eq)]
pub enum CoinsError {
#[error("Duplicate denom")]
Expand Down
3 changes: 2 additions & 1 deletion packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ pub use crate::ibc::{
pub use crate::iterator::{Order, Record};
pub use crate::math::{
Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Int128, Int256,
Int512, Int64, Isqrt, Uint128, Uint256, Uint512, Uint64,
Int512, Int64, Isqrt, SignedDecimal, SignedDecimal256, SignedDecimal256RangeExceeded,
SignedDecimalRangeExceeded, Uint128, Uint256, Uint512, Uint64,
};
pub use crate::metadata::{DenomMetadata, DenomUnit};
pub use crate::never::Never;
Expand Down
70 changes: 61 additions & 9 deletions packages/std/src/math/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::errors::{
CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
OverflowOperation, RoundUpOverflowError, StdError,
};
use crate::{forward_ref_partial_eq, Decimal256};
use crate::{forward_ref_partial_eq, Decimal256, SignedDecimal, SignedDecimal256};

use super::Fraction;
use super::Isqrt;
Expand Down Expand Up @@ -289,7 +289,7 @@ impl Decimal {
.try_into()
.map(Self)
.map_err(|_| OverflowError {
operation: crate::OverflowOperation::Mul,
operation: OverflowOperation::Mul,
operand1: self.to_string(),
operand2: other.to_string(),
})
Expand Down Expand Up @@ -331,7 +331,7 @@ impl Decimal {
}

inner(self, exp).map_err(|_| OverflowError {
operation: crate::OverflowOperation::Pow,
operation: OverflowOperation::Pow,
operand1: self.to_string(),
operand2: exp.to_string(),
})
Expand Down Expand Up @@ -435,7 +435,7 @@ impl Decimal {
/// let d = Decimal::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint128::new(75));
/// ```
#[must_use]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn to_uint_floor(self) -> Uint128 {
self.0 / Self::DECIMAL_FRACTIONAL
}
Expand All @@ -458,7 +458,7 @@ impl Decimal {
/// let d = Decimal::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint128::new(75));
/// ```
#[must_use]
#[must_use = "this returns the result of the operation, without modifying the original"]
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.
Expand Down Expand Up @@ -510,6 +510,30 @@ impl TryFrom<Decimal256> for Decimal {
}
}

impl TryFrom<SignedDecimal> for Decimal {
type Error = DecimalRangeExceeded;

fn try_from(value: SignedDecimal) -> Result<Self, Self::Error> {
value
.atomics()
.try_into()
.map(Decimal)
.map_err(|_| DecimalRangeExceeded)
}
}

impl TryFrom<SignedDecimal256> for Decimal {
type Error = DecimalRangeExceeded;

fn try_from(value: SignedDecimal256) -> Result<Self, Self::Error> {
value
.atomics()
.try_into()
.map(Decimal)
.map_err(|_| DecimalRangeExceeded)
}
}

impl FromStr for Decimal {
type Err = StdError;

Expand Down Expand Up @@ -845,6 +869,34 @@ mod tests {
);
}

#[test]
fn decimal_try_from_signed_works() {
assert_eq!(
Decimal::try_from(SignedDecimal::MAX).unwrap(),
Decimal::raw(SignedDecimal::MAX.atomics().i128() as u128)
);
assert_eq!(
Decimal::try_from(SignedDecimal::zero()).unwrap(),
Decimal::zero()
);
assert_eq!(
Decimal::try_from(SignedDecimal::one()).unwrap(),
Decimal::one()
);
assert_eq!(
Decimal::try_from(SignedDecimal::percent(50)).unwrap(),
Decimal::percent(50)
);
assert_eq!(
Decimal::try_from(SignedDecimal::negative_one()),
Err(DecimalRangeExceeded)
);
assert_eq!(
Decimal::try_from(SignedDecimal::MIN),
Err(DecimalRangeExceeded)
);
}

#[test]
fn decimal_from_atomics_works() {
let one = Decimal::one();
Expand Down Expand Up @@ -1069,7 +1121,7 @@ mod tests {
}

#[test]
fn decimal_from_str_errors_for_broken_fractinal_part() {
fn decimal_from_str_errors_for_broken_fractional_part() {
match Decimal::from_str("1.").unwrap_err() {
StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"),
e => panic!("Unexpected error: {e:?}"),
Expand Down Expand Up @@ -1485,7 +1537,7 @@ mod tests {
assert_eq!(
Decimal::MAX.checked_mul(Decimal::percent(200)),
Err(OverflowError {
operation: crate::OverflowOperation::Mul,
operation: OverflowOperation::Mul,
operand1: Decimal::MAX.to_string(),
operand2: Decimal::percent(200).to_string(),
})
Expand Down Expand Up @@ -1727,7 +1779,7 @@ mod tests {
assert_eq!(Decimal::one().checked_pow(exp).unwrap(), Decimal::one());
}

// This case is mathematically undefined but we ensure consistency with Rust stdandard types
// This case is mathematically undefined but we ensure consistency with Rust standard types
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494
assert_eq!(Decimal::zero().checked_pow(0).unwrap(), Decimal::one());

Expand Down Expand Up @@ -1799,7 +1851,7 @@ mod tests {
assert_eq!(
Decimal::MAX.checked_pow(2),
Err(OverflowError {
operation: crate::OverflowOperation::Pow,
operation: OverflowOperation::Pow,
operand1: Decimal::MAX.to_string(),
operand2: "2".to_string(),
})
Expand Down
52 changes: 35 additions & 17 deletions packages/std/src/math/decimal256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::errors::{
CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError,
OverflowOperation, RoundUpOverflowError, StdError,
};
use crate::{forward_ref_partial_eq, Decimal, Uint512};
use crate::{forward_ref_partial_eq, Decimal, SignedDecimal, SignedDecimal256, Uint512};

use super::Fraction;
use super::Isqrt;
Expand All @@ -33,15 +33,9 @@ pub struct Decimal256RangeExceeded;

impl Decimal256 {
const DECIMAL_FRACTIONAL: Uint256 = // 1*10**18
Uint256::from_be_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182,
179, 167, 100, 0, 0,
]);
Uint256::from_u128(1_000_000_000_000_000_000);
const DECIMAL_FRACTIONAL_SQUARED: Uint256 = // 1*10**36
Uint256::from_be_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 151, 206, 123, 201, 7, 21, 179,
75, 159, 16, 0, 0, 0, 0,
]);
Uint256::from_u128(1_000_000_000_000_000_000_000_000_000_000_000_000);

/// The number of decimal places. Since decimal types are fixed-point rather than
/// floating-point, this is a constant.
Expand Down Expand Up @@ -304,7 +298,7 @@ impl Decimal256 {
.try_into()
.map(Self)
.map_err(|_| OverflowError {
operation: crate::OverflowOperation::Mul,
operation: OverflowOperation::Mul,
operand1: self.to_string(),
operand2: other.to_string(),
})
Expand Down Expand Up @@ -346,7 +340,7 @@ impl Decimal256 {
}

inner(self, exp).map_err(|_| OverflowError {
operation: crate::OverflowOperation::Pow,
operation: OverflowOperation::Pow,
operand1: self.to_string(),
operand2: exp.to_string(),
})
Expand Down Expand Up @@ -454,7 +448,7 @@ impl Decimal256 {
/// let d = Decimal256::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_floor(), Uint256::from(75u64));
/// ```
#[must_use]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn to_uint_floor(self) -> Uint256 {
self.0 / Self::DECIMAL_FRACTIONAL
}
Expand All @@ -477,7 +471,7 @@ impl Decimal256 {
/// let d = Decimal256::from_str("75.0").unwrap();
/// assert_eq!(d.to_uint_ceil(), Uint256::from(75u64));
/// ```
#[must_use]
#[must_use = "this returns the result of the operation, without modifying the original"]
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.
Expand Down Expand Up @@ -525,6 +519,30 @@ impl From<Decimal> for Decimal256 {
}
}

impl TryFrom<SignedDecimal> for Decimal256 {
type Error = Decimal256RangeExceeded;

fn try_from(value: SignedDecimal) -> Result<Self, Self::Error> {
value
.atomics()
.try_into()
.map(Decimal256)
.map_err(|_| Decimal256RangeExceeded)
}
}

impl TryFrom<SignedDecimal256> for Decimal256 {
type Error = Decimal256RangeExceeded;

fn try_from(value: SignedDecimal256) -> Result<Self, Self::Error> {
value
.atomics()
.try_into()
.map(Decimal256)
.map_err(|_| Decimal256RangeExceeded)
}
}

impl FromStr for Decimal256 {
type Err = StdError;

Expand Down Expand Up @@ -1145,7 +1163,7 @@ mod tests {
}

#[test]
fn decimal256_from_str_errors_for_broken_fractinal_part() {
fn decimal256_from_str_errors_for_broken_fractional_part() {
match Decimal256::from_str("1.").unwrap_err() {
StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"),
e => panic!("Unexpected error: {e:?}"),
Expand Down Expand Up @@ -1581,7 +1599,7 @@ mod tests {
assert_eq!(
Decimal256::MAX.checked_mul(Decimal256::percent(200)),
Err(OverflowError {
operation: crate::OverflowOperation::Mul,
operation: OverflowOperation::Mul,
operand1: Decimal256::MAX.to_string(),
operand2: Decimal256::percent(200).to_string(),
})
Expand Down Expand Up @@ -1830,7 +1848,7 @@ mod tests {
);
}

// This case is mathematically undefined but we ensure consistency with Rust stdandard types
// This case is mathematically undefined but we ensure consistency with Rust standard types
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=20df6716048e77087acd40194b233494
assert_eq!(
Decimal256::zero().checked_pow(0).unwrap(),
Expand Down Expand Up @@ -1908,7 +1926,7 @@ mod tests {
assert_eq!(
Decimal256::MAX.checked_pow(2),
Err(OverflowError {
operation: crate::OverflowOperation::Pow,
operation: OverflowOperation::Pow,
operand1: Decimal256::MAX.to_string(),
operand2: "2".to_string(),
})
Expand Down
4 changes: 4 additions & 0 deletions packages/std/src/math/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ mod int512;
mod int64;
mod isqrt;
mod num_consts;
mod signed_decimal;
mod signed_decimal_256;
mod uint128;
mod uint256;
mod uint512;
Expand All @@ -21,6 +23,8 @@ pub use int256::Int256;
pub use int512::Int512;
pub use int64::Int64;
pub use isqrt::Isqrt;
pub use signed_decimal::{SignedDecimal, SignedDecimalRangeExceeded};
pub use signed_decimal_256::{SignedDecimal256, SignedDecimal256RangeExceeded};
pub use uint128::Uint128;
pub use uint256::Uint256;
pub use uint512::Uint512;
Expand Down
Loading

0 comments on commit 1e1eb5e

Please sign in to comment.