Skip to content

Commit

Permalink
Merge pull request #1866 from CosmWasm/checked-multiply-ratio
Browse files Browse the repository at this point in the history
Add missing checked_multiply_ratio implementations
  • Loading branch information
chipshort committed Sep 7, 2023
2 parents 4e5b2bf + 06fa3d4 commit 7b2e753
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ and this project adheres to
`TryFrom<Int{128,256,512}>` for `Int64`, `TryFrom<Int{256,512}>` for `Int128`,
`TryFrom<Int512>` for `Int256` and `Int256::from_i128` for const contexts
([#1861]).
- cosmwasm-std: Add `Int{64,128,256}::{checked_multiply_ratio, full_mul}`
([#1866])
- cosmwasm-std: Add `is_negative` for `Int{64,128,256,512}` ([#1867]).

[#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

## [1.4.0] - 2023-09-04
Expand Down
101 changes: 100 additions & 1 deletion packages/std/src/math/int128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize};

use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
use crate::{
forward_ref_partial_eq, ConversionOverflowError, Int256, Int512, Int64, Uint128, Uint64,
forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int256, Int512,
Int64, Uint128, Uint64,
};

use super::conversion::shrink_be_int;
Expand Down Expand Up @@ -99,6 +100,45 @@ impl Int128 {
Self(self.0.pow(exp))
}

/// Returns `self * numerator / denominator`.
///
/// Due to the nature of the integer division involved, the result is always floored.
/// E.g. 5 * 99/100 = 4.
pub fn checked_multiply_ratio<A: Into<Self>, B: Into<Self>>(
&self,
numerator: A,
denominator: B,
) -> Result<Self, CheckedMultiplyRatioError> {
let numerator = numerator.into();
let denominator = denominator.into();
if denominator.is_zero() {
return Err(CheckedMultiplyRatioError::DivideByZero);
}
match (self.full_mul(numerator) / Int256::from(denominator)).try_into() {
Ok(ratio) => Ok(ratio),
Err(_) => Err(CheckedMultiplyRatioError::Overflow),
}
}

/// Multiplies two [`Int128`] values without overflow, producing an
/// [`Int256`].
///
/// # Examples
///
/// ```
/// use cosmwasm_std::Int128;
///
/// let a = Int128::MAX;
/// let result = a.full_mul(2i32);
/// assert_eq!(result.to_string(), "340282366920938463463374607431768211454");
/// ```
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn full_mul(self, rhs: impl Into<Self>) -> Int256 {
Int256::from(self.i128())
.checked_mul(Int256::from(rhs.into()))
.unwrap()
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
Expand Down Expand Up @@ -912,6 +952,65 @@ mod tests {
_ = Int128::MAX.pow(2u32);
}

#[test]
fn int128_checked_multiply_ratio_works() {
let base = Int128(500);

// factor 1/1
assert_eq!(base.checked_multiply_ratio(1i128, 1i128).unwrap(), base);
assert_eq!(base.checked_multiply_ratio(3i128, 3i128).unwrap(), base);
assert_eq!(
base.checked_multiply_ratio(654321i128, 654321i128).unwrap(),
base
);
assert_eq!(
base.checked_multiply_ratio(i128::MAX, i128::MAX).unwrap(),
base
);

// factor 3/2
assert_eq!(
base.checked_multiply_ratio(3i128, 2i128).unwrap(),
Int128(750)
);
assert_eq!(
base.checked_multiply_ratio(333333i128, 222222i128).unwrap(),
Int128(750)
);

// factor 2/3 (integer devision always floors the result)
assert_eq!(
base.checked_multiply_ratio(2i128, 3i128).unwrap(),
Int128(333)
);
assert_eq!(
base.checked_multiply_ratio(222222i128, 333333i128).unwrap(),
Int128(333)
);

// factor 5/6 (integer devision always floors the result)
assert_eq!(
base.checked_multiply_ratio(5i128, 6i128).unwrap(),
Int128(416)
);
assert_eq!(
base.checked_multiply_ratio(100i128, 120i128).unwrap(),
Int128(416)
);
}

#[test]
fn int128_checked_multiply_ratio_does_not_panic() {
assert_eq!(
Int128(500i128).checked_multiply_ratio(1i128, 0i128),
Err(CheckedMultiplyRatioError::DivideByZero),
);
assert_eq!(
Int128(500i128).checked_multiply_ratio(i128::MAX, 1i128),
Err(CheckedMultiplyRatioError::Overflow),
);
}

#[test]
fn int128_shr_works() {
let original = Int128::from_be_bytes([
Expand Down
105 changes: 103 additions & 2 deletions packages/std/src/math/int256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize};

use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
use crate::{
forward_ref_partial_eq, ConversionOverflowError, Int128, Int512, Int64, Uint128, Uint256,
Uint64,
forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int128, Int512,
Int64, Uint128, Uint256, Uint64,
};

/// Used internally - we don't want to leak this type since we might change
Expand Down Expand Up @@ -155,6 +155,48 @@ impl Int256 {
Self(self.0.pow(exp))
}

/// Returns `self * numerator / denominator`.
///
/// Due to the nature of the integer division involved, the result is always floored.
/// E.g. 5 * 99/100 = 4.
pub fn checked_multiply_ratio<A: Into<Self>, B: Into<Self>>(
&self,
numerator: A,
denominator: B,
) -> Result<Self, CheckedMultiplyRatioError> {
let numerator = numerator.into();
let denominator = denominator.into();
if denominator.is_zero() {
return Err(CheckedMultiplyRatioError::DivideByZero);
}
match (self.full_mul(numerator) / Int512::from(denominator)).try_into() {
Ok(ratio) => Ok(ratio),
Err(_) => Err(CheckedMultiplyRatioError::Overflow),
}
}

/// Multiplies two [`Int256`] values without overflow, producing an
/// [`Int512`].
///
/// # Examples
///
/// ```
/// use cosmwasm_std::Int256;
///
/// let a = Int256::MAX;
/// let result = a.full_mul(2i32);
/// assert_eq!(
/// result.to_string(),
/// "115792089237316195423570985008687907853269984665640564039457584007913129639934"
/// );
/// ```
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn full_mul(self, rhs: impl Into<Self>) -> Int512 {
Int512::from(self)
.checked_mul(Int512::from(rhs.into()))
.unwrap()
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
Expand Down Expand Up @@ -1009,6 +1051,65 @@ mod tests {
_ = Int256::MAX.pow(2u32);
}

#[test]
fn int256_checked_multiply_ratio_works() {
let base = Int256::from_i128(500);

// factor 1/1
assert_eq!(base.checked_multiply_ratio(1i128, 1i128).unwrap(), base);
assert_eq!(base.checked_multiply_ratio(3i128, 3i128).unwrap(), base);
assert_eq!(
base.checked_multiply_ratio(654321i128, 654321i128).unwrap(),
base
);
assert_eq!(
base.checked_multiply_ratio(i128::MAX, i128::MAX).unwrap(),
base
);

// factor 3/2
assert_eq!(
base.checked_multiply_ratio(3i128, 2i128).unwrap(),
Int256::from_i128(750)
);
assert_eq!(
base.checked_multiply_ratio(333333i128, 222222i128).unwrap(),
Int256::from_i128(750)
);

// factor 2/3 (integer devision always floors the result)
assert_eq!(
base.checked_multiply_ratio(2i128, 3i128).unwrap(),
Int256::from_i128(333)
);
assert_eq!(
base.checked_multiply_ratio(222222i128, 333333i128).unwrap(),
Int256::from_i128(333)
);

// factor 5/6 (integer devision always floors the result)
assert_eq!(
base.checked_multiply_ratio(5i128, 6i128).unwrap(),
Int256::from_i128(416)
);
assert_eq!(
base.checked_multiply_ratio(100i128, 120i128).unwrap(),
Int256::from_i128(416)
);
}

#[test]
fn int256_checked_multiply_ratio_does_not_panic() {
assert_eq!(
Int256::from_i128(500i128).checked_multiply_ratio(1i128, 0i128),
Err(CheckedMultiplyRatioError::DivideByZero),
);
assert_eq!(
Int256::MAX.checked_multiply_ratio(Int256::MAX, 1i128),
Err(CheckedMultiplyRatioError::Overflow),
);
}

#[test]
fn int256_shr_works() {
let original = Int256::new([
Expand Down
94 changes: 93 additions & 1 deletion packages/std/src/math/int64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use schemars::JsonSchema;
use serde::{de, ser, Deserialize, Deserializer, Serialize};

use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError};
use crate::{forward_ref_partial_eq, ConversionOverflowError, Int128, Int256, Int512, Uint64};
use crate::{
forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int128, Int256,
Int512, Uint64,
};

use super::conversion::shrink_be_int;

Expand Down Expand Up @@ -97,6 +100,45 @@ impl Int64 {
Self(self.0.pow(exp))
}

/// Returns `self * numerator / denominator`.
///
/// Due to the nature of the integer division involved, the result is always floored.
/// E.g. 5 * 99/100 = 4.
pub fn checked_multiply_ratio<A: Into<Self>, B: Into<Self>>(
&self,
numerator: A,
denominator: B,
) -> Result<Self, CheckedMultiplyRatioError> {
let numerator = numerator.into();
let denominator = denominator.into();
if denominator.is_zero() {
return Err(CheckedMultiplyRatioError::DivideByZero);
}
match (self.full_mul(numerator) / Int128::from(denominator)).try_into() {
Ok(ratio) => Ok(ratio),
Err(_) => Err(CheckedMultiplyRatioError::Overflow),
}
}

/// Multiplies two [`Int64`] values without overflow, producing an
/// [`Int128`].
///
/// # Examples
///
/// ```
/// use cosmwasm_std::Int64;
///
/// let a = Int64::MAX;
/// let result = a.full_mul(2i32);
/// assert_eq!(result.to_string(), "18446744073709551614");
/// ```
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn full_mul(self, rhs: impl Into<Self>) -> Int128 {
Int128::from(self)
.checked_mul(Int128::from(rhs.into()))
.unwrap()
}

pub fn checked_add(self, other: Self) -> Result<Self, OverflowError> {
self.0
.checked_add(other.0)
Expand Down Expand Up @@ -888,6 +930,56 @@ mod tests {
_ = Int64::MAX.pow(2u32);
}

#[test]
fn int64_checked_multiply_ratio_works() {
let base = Int64(500);

// factor 1/1
assert_eq!(base.checked_multiply_ratio(1i64, 1i64).unwrap(), base);
assert_eq!(base.checked_multiply_ratio(3i64, 3i64).unwrap(), base);
assert_eq!(
base.checked_multiply_ratio(654321i64, 654321i64).unwrap(),
base
);
assert_eq!(
base.checked_multiply_ratio(i64::MAX, i64::MAX).unwrap(),
base
);

// factor 3/2
assert_eq!(base.checked_multiply_ratio(3i64, 2i64).unwrap(), Int64(750));
assert_eq!(
base.checked_multiply_ratio(333333i64, 222222i64).unwrap(),
Int64(750)
);

// factor 2/3 (integer devision always floors the result)
assert_eq!(base.checked_multiply_ratio(2i64, 3i64).unwrap(), Int64(333));
assert_eq!(
base.checked_multiply_ratio(222222i64, 333333i64).unwrap(),
Int64(333)
);

// factor 5/6 (integer devision always floors the result)
assert_eq!(base.checked_multiply_ratio(5i64, 6i64).unwrap(), Int64(416));
assert_eq!(
base.checked_multiply_ratio(100i64, 120i64).unwrap(),
Int64(416)
);
}

#[test]
fn int64_checked_multiply_ratio_does_not_panic() {
assert_eq!(
Int64(500i64).checked_multiply_ratio(1i64, 0i64),
Err(CheckedMultiplyRatioError::DivideByZero),
);
assert_eq!(
Int64(500i64).checked_multiply_ratio(i64::MAX, 1i64),
Err(CheckedMultiplyRatioError::Overflow),
);
}

#[test]
fn int64_shr_works() {
let original = Int64::from_be_bytes([0u8, 0u8, 0u8, 0u8, 2u8, 0u8, 4u8, 2u8]);
Expand Down

0 comments on commit 7b2e753

Please sign in to comment.