diff --git a/gradually-update/src/tests.rs b/gradually-update/src/tests.rs index 9d8b0e1267ee49d3e41107b34d579f0ac75ff837..635c5f809428b51997b560197803521fb52b6a1f 100644 --- a/gradually-update/src/tests.rs +++ b/gradually-update/src/tests.rs @@ -5,7 +5,7 @@ use super::*; use frame_support::{assert_noop, assert_ok, traits::OnFinalize}; use mock::{ExtBuilder, GraduallyUpdateModule, Origin, Runtime, System, TestEvent}; -use orml_utilities::FixedU128; +use orml_utilities::{FixedU128, FixedUnsignedNumber}; use sp_runtime::Permill; fn storage_set(key: &Vec<u8>, value: &Vec<u8>) { @@ -303,8 +303,8 @@ fn fixedu128_should_work() { ExtBuilder::default().build().execute_with(|| { let update = GraduallyUpdate { key: vec![1], - target_value: FixedU128::from_rational(30, 1).encode(), - per_block: FixedU128::from_rational(1, 1).encode(), + target_value: FixedU128::saturating_from_rational(30, 1).encode(), + per_block: FixedU128::saturating_from_rational(1, 1).encode(), }; assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone())); assert_eq!(storage_get(&update.key), vec![]); diff --git a/prices/src/lib.rs b/prices/src/lib.rs index 4b07248fd61f5d21d8d4a148b9a32f081c018483..8944e79074b1fcfd29cf879389754e3052a33f10 100644 --- a/prices/src/lib.rs +++ b/prices/src/lib.rs @@ -3,7 +3,7 @@ use frame_support::Parameter; use orml_traits::{DataProvider, PriceProvider}; use orml_utilities::FixedU128; -use sp_runtime::traits::{MaybeSerializeDeserialize, Member}; +use sp_runtime::traits::{CheckedDiv, MaybeSerializeDeserialize, Member}; use sp_std::marker::PhantomData; pub type Price = FixedU128; diff --git a/prices/src/tests.rs b/prices/src/tests.rs index 231f5d922e971804a17a88ba6d98fce6ea709094..997d2cd53478b7326f31f6aa36b5b93915ff5fe6 100644 --- a/prices/src/tests.rs +++ b/prices/src/tests.rs @@ -3,14 +3,15 @@ #![cfg(test)] use super::*; +use orml_utilities::FixedUnsignedNumber; pub struct MockDataProvider; impl DataProvider<u32, Price> for MockDataProvider { fn get(currency: &u32) -> Option<Price> { match currency { - 0 => Some(Price::from_parts(0)), - 1 => Some(Price::from_parts(1)), - 2 => Some(Price::from_parts(2)), + 0 => Some(Price::from_inner(0)), + 1 => Some(Price::from_inner(1)), + 2 => Some(Price::from_inner(2)), _ => None, } } @@ -20,8 +21,14 @@ type TestPriceProvider = DefaultPriceProvider<u32, MockDataProvider>; #[test] fn get_price_should_work() { - assert_eq!(TestPriceProvider::get_price(1, 2), Some(Price::from_rational(1, 2))); - assert_eq!(TestPriceProvider::get_price(2, 1), Some(Price::from_rational(2, 1))); + assert_eq!( + TestPriceProvider::get_price(1, 2), + Some(Price::saturating_from_rational(1, 2)) + ); + assert_eq!( + TestPriceProvider::get_price(2, 1), + Some(Price::saturating_from_rational(2, 1)) + ); } #[test] @@ -35,5 +42,5 @@ fn price_is_none_should_not_panic() { fn price_is_zero_should_not_panic() { assert_eq!(TestPriceProvider::get_price(0, 0), None); assert_eq!(TestPriceProvider::get_price(1, 0), None); - assert_eq!(TestPriceProvider::get_price(0, 1), Some(Price::from_parts(0))); + assert_eq!(TestPriceProvider::get_price(0, 1), Some(Price::from_inner(0))); } diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index a2642b900ca98b9a8c8716b366413e6f3f406c3c..19b6e63f101103533a62423864474cf65bb2488a 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -14,6 +14,7 @@ sp-core = { version = "2.0.0-rc1", default-features = false } sp-runtime = { version = "2.0.0-rc1", default-features = false } sp-io = { version = "2.0.0-rc1", default-features = false } sp-std = { version = "2.0.0-rc1", default-features = false } +sp-arithmetic= { version = "2.0.0-rc1", default-features = false } frame-system = { version = "2.0.0-rc1", default-features = false } frame-support = { version = "2.0.0-rc1", default-features = false } diff --git a/utilities/src/fixed_u128.rs b/utilities/src/fixed_u128.rs index 5146ed05f6e4134efdb200372e4f0a4279b67d3b..2b3adf0093290759dfb0316eec0a7d0c4ea105b8 100644 --- a/utilities/src/fixed_u128.rs +++ b/utilities/src/fixed_u128.rs @@ -1,4 +1,5 @@ use codec::{CompactAs, Decode, Encode}; +use sp_arithmetic::traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, Zero}; use sp_core::U256; use sp_runtime::{ traits::{Bounded, Saturating, UniqueSaturatedInto}, @@ -6,7 +7,8 @@ use sp_runtime::{ }; use sp_std::{ convert::{Into, TryFrom, TryInto}, - fmt, + fmt::{self, Debug}, + ops::{self, Add, Div, Mul, Sub}, }; #[cfg(feature = "std")] @@ -19,145 +21,293 @@ pub struct FixedU128(u128); const DIV: u128 = 1_000_000_000_000_000_000; -impl FixedU128 { - /// Create self from a natural number. +/// Integer types that can be used to interact with `FixedPointNumber` implementations. +pub trait FixedPointOperand: + Copy + + Clone + + Bounded + + Zero + + Saturating + + PartialOrd + + UniqueSaturatedInto<u128> + + TryFrom<u128> + + TryInto<u128> + + TryFrom<U256> +{ +} + +impl FixedPointOperand for i128 {} +impl FixedPointOperand for u128 {} +impl FixedPointOperand for i64 {} +impl FixedPointOperand for u64 {} +impl FixedPointOperand for i32 {} +impl FixedPointOperand for u32 {} +impl FixedPointOperand for i16 {} +impl FixedPointOperand for u16 {} +impl FixedPointOperand for i8 {} +impl FixedPointOperand for u8 {} + +pub trait FixedUnsignedNumber: + Sized + + Copy + + Default + + fmt::Debug + + Saturating + + Bounded + + Eq + + PartialEq + + Ord + + PartialOrd + + CheckedSub + + CheckedAdd + + CheckedMul + + CheckedDiv + + Add + + Sub + + Div + + Mul + + Zero + + One +{ + type Inner: Debug + One + CheckedMul + CheckedDiv + FixedPointOperand + Into<U256> + Into<u128>; + + /// Precision of this fixed point implementation. It should be a power of `10`. + const DIV: Self::Inner; + + /// Precision of this fixed point implementation. + fn accuracy() -> Self::Inner { + Self::DIV + } + + /// Create `self` from a natural number. /// /// Note that this might be lossy. - pub fn from_natural(int: u128) -> Self { - Self(int.saturating_mul(DIV)) + fn from_natural(int: Self::Inner) -> Self; + + /// Builds this type from an integer number. + fn from_inner(int: Self::Inner) -> Self; + + /// Consumes `self` and returns the inner raw value. + fn into_inner(self) -> Self::Inner; + + /// Creates self from an integer number `int`. + /// + /// Returns `Self::max` or `Self::min` if `int` exceeds accuracy. + fn saturating_from_integer<N: UniqueSaturatedInto<Self::Inner> + PartialOrd + Zero>(int: N) -> Self { + if int < N::zero() { + return Self::min_value(); + } + + Self::from_inner(int.unique_saturated_into().saturating_mul(Self::DIV)) } - /// Accuracy of `FixedU128`. - pub const fn accuracy() -> u128 { - DIV + /// Creates `self` from an integer number `int`. + /// + /// Returns `None` if `int` exceeds accuracy. + fn checked_from_integer(int: Self::Inner) -> Option<Self> { + int.checked_mul(&Self::DIV).map(Self::from_inner) } - /// Raw constructor. Equal to `parts / DIV`. - pub fn from_parts(parts: u128) -> Self { - Self(parts) + /// Creates `self` from a rational number. Equal to `n / d`. + /// + /// Panics if `d = 0`. Returns `Self::max` or `Self::min` if `n / d` exceeds accuracy. + fn saturating_from_rational<N: FixedPointOperand, D: FixedPointOperand>(n: N, d: D) -> Self { + if d == D::zero() { + panic!("attempt to divide by zero") + } + Self::checked_from_rational(n, d).unwrap_or_else(|| to_bound(n, d)) } - /// Creates self from a rational number. Equal to `n/d`. + /// Creates `self` from a rational number. Equal to `n / d`. /// - /// Note that this might be lossy. - pub fn from_rational<N: UniqueSaturatedInto<u128>>(n: N, d: N) -> Self { + /// Returns `None` if `d == 0` or `n / d` exceeds accuracy. + fn checked_from_rational<N: FixedPointOperand, D: FixedPointOperand>(n: N, d: D) -> Option<Self> { + if d == D::zero() { + return None; + } + // this should really be `N: Into<U256>` or else might give wrong result // TODO: Should have a better way to enforce this requirement - let n = n.unique_saturated_into(); + // let n = n.unique_saturated_into(); + // let d = d.unique_saturated_into(); + // let d = U256::from(d); + + let n: u128 = (n).try_into().ok()?; let n = U256::from(n); - let d = d.unique_saturated_into(); + let d: u128 = (d).try_into().ok()?; let d = U256::from(d); - Self( - (n.saturating_mul(DIV.into()) / d.max(U256::one())) - .try_into() - .unwrap_or_else(|_| Bounded::max_value()), - ) + + n.checked_mul(U256::from(Self::DIV.unique_saturated_into())) + .and_then(|n| n.checked_div(d)) + .and_then(|n| n.try_into().ok()) + .map(Self::from_inner) } - /// Consume self and return the inner raw `u128` value. + /// Checked mul for int type `N`. Equal to `self * n`. /// - /// Note this is a low level function, as the returned value is represented with accuracy. - pub fn deconstruct(self) -> u128 { - self.0 + /// Returns `None` if the result does not fit in `N`. + fn checked_mul_int<N: FixedPointOperand>(&self, other: N) -> Option<N> { + let lhs: U256 = self.into_inner().into(); + let rhs: u128 = other.try_into().ok()?; + let rhs: U256 = U256::from(rhs); + + lhs.checked_mul(rhs) + .and_then(|n| n.checked_div(U256::from(Self::DIV.unique_saturated_into()))) + .and_then(|n| n.try_into().ok()) + .and_then(from_u128) } - /// Takes the reciprocal(inverse) of FixedU128, 1/x - pub fn recip(&self) -> Option<Self> { - Self::from_natural(1u128).checked_div(self) + /// Saturating multiplication for integer type `N`. Equal to `self * n`. + /// + /// Returns `N::min` or `N::max` if the result does not fit in `N`. + fn saturating_mul_int<N: FixedPointOperand>(self, n: N) -> N { + self.checked_mul_int(n) + .unwrap_or_else(|| to_bound(self.into_inner(), n)) } - /// Checked add. Same semantic to `num_traits::CheckedAdd`. - pub fn checked_add(&self, rhs: &Self) -> Option<Self> { - self.0.checked_add(rhs.0).map(Self) + /// Checked division for integer type `N`. Equal to `self / d`. + /// + /// Returns `None` if the result does not fit in `N` or `d == 0`. + fn checked_div_int<N: FixedPointOperand>(&self, other: N) -> Option<N> { + let lhs: u128 = self.into_inner().into(); + let rhs: u128 = other.try_into().ok()?; + + lhs.checked_div(rhs) + .and_then(|n| n.checked_div(Self::DIV.unique_saturated_into())) + .and_then(from_u128) } - /// Checked sub. Same semantic to `num_traits::CheckedSub`. - pub fn checked_sub(&self, rhs: &Self) -> Option<Self> { - self.0.checked_sub(rhs.0).map(Self) + /// Saturating division for integer type `N`. Equal to `self / d`. + /// + /// Panics if `d == 0`. Returns `N::min` or `N::max` if the result does not fit in `N`. + fn saturating_div_int<N: FixedPointOperand>(self, d: N) -> N { + if d == N::zero() { + panic!("attempt to divide by zero") + } + self.checked_div_int(d) + .unwrap_or_else(|| to_bound(self.into_inner(), d)) } - /// Checked mul. Same semantic to `num_traits::CheckedMul`. - pub fn checked_mul(&self, rhs: &Self) -> Option<Self> { - if let Some(r) = U256::from(self.0) - .checked_mul(U256::from(rhs.0)) - .and_then(|n| n.checked_div(U256::from(DIV))) - { - if let Ok(r) = TryInto::<u128>::try_into(r) { - return Some(Self(r)); - } - } + /// Saturating multiplication for integer type `N`, adding the result back. + /// Equal to `self * n + n`. + /// + /// Returns `N::min` or `N::max` if the multiplication or final result does not fit in `N`. + fn saturating_mul_acc_int<N: FixedPointOperand>(self, n: N) -> N { + self.saturating_mul_int(n).saturating_add(n) + } - None + /// Takes the reciprocal (inverse). Equal to `1 / self`. + /// + /// Returns `None` if `self = 0`. + fn reciprocal(self) -> Option<Self> { + Self::one().checked_div(&self) } - /// Checked div. Same semantic to `num_traits::CheckedDiv`. - pub fn checked_div(&self, rhs: &Self) -> Option<Self> { - if let Some(r) = U256::from(self.0) - .checked_mul(U256::from(DIV)) - .and_then(|n| n.checked_div(U256::from(rhs.0))) - { - if let Ok(r) = TryInto::<u128>::try_into(r) { - return Some(Self(r)); - } + /// Returns the integer part. + fn trunc(self) -> Self { + self.into_inner() + .checked_div(&Self::DIV) + .expect("panics only if DIV is zero, DIV is not zero; qed") + .checked_mul(&Self::DIV) + .map(Self::from_inner) + .expect("can not overflow since fixed number is >= integer part") + } + + /// Returns the fractional part. + /// + /// Note: the returned fraction will be non-negative for negative numbers, + /// except in the case where the integer part is zero. + fn frac(self) -> Self { + let integer = self.trunc(); + + self.saturating_sub(integer) + } + + /// Returns the smallest integer greater than or equal to a number. + /// + /// Saturates to `Self::max` (truncated) if the result does not fit. + fn ceil(self) -> Self { + if self.is_zero() { + return self.trunc(); } + self.saturating_add(Self::one()).trunc() + } - None + /// Returns the largest integer less than or equal to a number. + /// + /// Saturates to `Self::min` (truncated) if the result does not fit. + fn floor(self) -> Self { + self.trunc() } - /// Checked mul for int type `N`. - pub fn checked_mul_int<N>(&self, other: &N) -> Option<N> - where - N: Copy + TryFrom<u128> + TryInto<u128>, - { - if let Ok(n) = N::try_into(*other) { - if let Some(n) = U256::from(self.0) - .checked_mul(U256::from(n)) - .and_then(|n| n.checked_div(U256::from(DIV))) - { - if let Ok(r) = TryInto::<u128>::try_into(n) { - if let Ok(r) = TryInto::<N>::try_into(r) { - return Some(r); - } - } - } + /// Returns the number rounded to the nearest integer. Rounds half-way cases away from 0.0. + /// + /// Saturates to `Self::min` or `Self::max` (truncated) if the result does not fit. + fn round(self) -> Self { + let n = self.frac().saturating_mul(Self::saturating_from_integer(10)); + if n < Self::saturating_from_integer(5) { + self.floor() + } else { + self.ceil() } + } +} + +impl FixedUnsignedNumber for FixedU128 { + type Inner = u128; - None + const DIV: Self::Inner = DIV; + + fn from_natural(n: Self::Inner) -> Self { + Self::from_inner(n.saturating_mul(Self::DIV)) } - /// Checked mul for int type `N`. - pub fn saturating_mul_int<N>(&self, other: &N) -> N - where - N: Copy + TryFrom<u128> + TryInto<u128> + Bounded, - { - self.checked_mul_int(other).unwrap_or_else(Bounded::max_value) + fn from_inner(n: Self::Inner) -> Self { + Self(n) } - /// Checked div for int type `N`. - pub fn checked_div_int<N>(&self, other: &N) -> Option<N> - where - N: Copy + TryFrom<u128> + TryInto<u128>, - { - if let Ok(n) = N::try_into(*other) { - if let Some(n) = self.0.checked_div(n).and_then(|n| n.checked_div(DIV)) { - if let Ok(r) = TryInto::<N>::try_into(n) { - return Some(r); - } - } - } + fn into_inner(self) -> Self::Inner { + self.0 + } +} - None +/// Returns `R::max` if the sign of `n * m` is positive, `R::min` otherwise. +fn to_bound<N: FixedPointOperand, D: FixedPointOperand, R: Bounded + Zero>(n: N, m: D) -> R { + if n < N::zero() { + return R::zero(); } + if m < D::zero() { + return R::zero(); + } + + R::max_value() +} + +fn from_u128<N: FixedPointOperand>(n: u128) -> Option<N> { + let r: N = n.try_into().ok()?; + Some(r) +} - pub fn zero() -> Self { +impl Zero for FixedU128 { + fn zero() -> Self { Self(0) } - pub fn is_zero(&self) -> bool { + fn is_zero(&self) -> bool { self.0 == 0 } } +impl One for FixedU128 { + fn one() -> Self { + Self(DIV) + } + + fn is_one(&self) -> bool { + self.0 == DIV + } +} + impl Saturating for FixedU128 { fn saturating_add(self, rhs: Self) -> Self { Self(self.0.saturating_add(rhs.0)) @@ -177,13 +327,13 @@ impl Saturating for FixedU128 { fn saturating_pow(self, exp: usize) -> Self { if exp == 0 { - return Self::from_natural(1); + return Self::saturating_from_integer(1); } let exp = exp as u64; let msb_pos = 64 - exp.leading_zeros(); - let mut result = Self::from_natural(1); + let mut result = Self::saturating_from_integer(1); let mut pow_val = self; for i in 0..msb_pos { if ((1 << i) & exp) > 0 { @@ -195,6 +345,76 @@ impl Saturating for FixedU128 { } } +impl ops::Add for FixedU128 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl ops::Sub for FixedU128 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl ops::Mul for FixedU128 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + self.checked_mul(&rhs) + .unwrap_or_else(|| panic!("attempt to multiply with overflow")) + } +} + +impl ops::Div for FixedU128 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + if rhs.0 == 0 { + panic!("attempt to divide by zero") + } + + self.checked_div(&rhs) + .unwrap_or_else(|| panic!("attempt to divide with overflow")) + } +} + +impl CheckedAdd for FixedU128 { + fn checked_add(&self, rhs: &Self) -> Option<Self> { + self.0.checked_add(rhs.0).map(Self) + } +} + +impl CheckedSub for FixedU128 { + fn checked_sub(&self, rhs: &Self) -> Option<Self> { + self.0.checked_sub(rhs.0).map(Self) + } +} + +impl CheckedMul for FixedU128 { + fn checked_mul(&self, rhs: &Self) -> Option<Self> { + U256::from(self.0) + .checked_mul(U256::from(rhs.0)) + .and_then(|n| n.checked_div(U256::from(DIV))) + .and_then(|n| TryInto::<u128>::try_into(n).ok()) + .map(Self) + } +} + +impl CheckedDiv for FixedU128 { + fn checked_div(&self, rhs: &Self) -> Option<Self> { + U256::from(self.0) + .checked_mul(U256::from(DIV)) + .and_then(|n| n.checked_div(U256::from(rhs.0))) + .and_then(|n| TryInto::<u128>::try_into(n).ok()) + .map(Self) + } +} + impl Bounded for FixedU128 { fn max_value() -> Self { Self(u128::max_value()) @@ -218,27 +438,50 @@ impl fmt::Debug for FixedU128 { } } +#[cfg(feature = "std")] +impl sp_std::fmt::Display for FixedU128 { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(feature = "std")] +impl sp_std::str::FromStr for FixedU128 { + type Err = &'static str; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let inner: u128 = s.parse().map_err(|_| "invalid string input for fixed u128")?; + Ok(Self::from_inner(inner)) + } +} + +impl From<u128> for FixedU128 { + fn from(n: u128) -> Self { + Self::saturating_from_integer(n) + } +} + impl From<Permill> for FixedU128 { fn from(val: Permill) -> Self { - FixedU128::from_rational(val.deconstruct(), Permill::ACCURACY) + FixedU128::saturating_from_rational(val.deconstruct(), Permill::ACCURACY) } } impl From<Percent> for FixedU128 { fn from(val: Percent) -> Self { - FixedU128::from_rational(val.deconstruct(), Percent::ACCURACY) + FixedU128::saturating_from_rational(val.deconstruct(), Percent::ACCURACY) } } impl From<Perbill> for FixedU128 { fn from(val: Perbill) -> Self { - FixedU128::from_rational(val.deconstruct(), Perbill::ACCURACY) + FixedU128::saturating_from_rational(val.deconstruct(), Perbill::ACCURACY) } } impl From<Perquintill> for FixedU128 { fn from(val: Perquintill) -> Self { - FixedU128::from_rational(val.deconstruct(), Perquintill::ACCURACY) + FixedU128::saturating_from_rational(val.deconstruct(), Perquintill::ACCURACY) } } @@ -250,7 +493,7 @@ impl FixedU128 { fn try_from_u128_str(s: &str) -> Result<Self, &'static str> { let parts: u128 = s.parse().map_err(|_| "invalid string input")?; - Ok(Self::from_parts(parts)) + Ok(Self::from_inner(parts)) } } @@ -285,14 +528,51 @@ mod tests { use sp_runtime::{Perbill, Percent, Permill, Perquintill}; fn max() -> FixedU128 { - FixedU128::from_parts(u128::max_value()) + FixedU128::from_inner(u128::max_value()) + } + #[test] + fn to_bound_works() { + let a = 1i32; + let b = 1i32; + + // Pos + Pos => Max. + assert_eq!(to_bound::<_, _, i32>(a, b), i32::max_value()); + + let a = -1i32; + let b = -1i32; + + // Neg + Neg => 0. + assert_eq!(to_bound::<_, _, i32>(a, b), 0); + + let a = 1i32; + let b = -1i32; + + // Pos + Neg => 0. + assert_eq!(to_bound::<_, _, i32>(a, b), 0); + + let a = -1i32; + let b = 1i32; + + // Neg + Pos => 0. + assert_eq!(to_bound::<_, _, i32>(a, b), 0); + + let a = 1i32; + let b = -1i32; + + // Pos + Neg => Min (unsigned). + assert_eq!(to_bound::<_, _, u32>(a, b), 0); } #[test] fn fixed128_semantics() { - assert_eq!(FixedU128::from_rational(5, 2).0, 5 * 1_000_000_000_000_000_000 / 2); - assert_eq!(FixedU128::from_rational(5, 2), FixedU128::from_rational(10, 4)); - assert_eq!(FixedU128::from_rational(5, 0), FixedU128::from_rational(5, 1)); + assert_eq!( + FixedU128::saturating_from_rational(5, 2).into_inner(), + 5 * 1_000_000_000_000_000_000 / 2 + ); + assert_eq!( + FixedU128::saturating_from_rational(5, 2), + FixedU128::saturating_from_rational(10, 4) + ); // biggest value that can be created. assert_ne!(max(), FixedU128::from_natural(340_282_366_920_938_463_463)); @@ -306,45 +586,30 @@ mod tests { assert_eq!(a.checked_add(&b), Some(FixedU128::from_natural(1 + 2))); assert_eq!(a.checked_sub(&b), Some(FixedU128::from_natural(2 - 1))); assert_eq!(a.checked_mul(&b), Some(FixedU128::from_natural(1 * 2))); - assert_eq!(a.checked_div(&b), Some(FixedU128::from_rational(2, 1))); + assert_eq!(a.checked_div(&b), Some(FixedU128::saturating_from_rational(2, 1))); - let a = FixedU128::from_rational(5, 2); - let b = FixedU128::from_rational(3, 2); - assert_eq!(a.checked_add(&b), Some(FixedU128::from_rational(8, 2))); - assert_eq!(a.checked_sub(&b), Some(FixedU128::from_rational(2, 2))); - assert_eq!(a.checked_mul(&b), Some(FixedU128::from_rational(15, 4))); - assert_eq!(a.checked_div(&b), Some(FixedU128::from_rational(10, 6))); + let a = FixedU128::saturating_from_rational(5, 2); + let b = FixedU128::saturating_from_rational(3, 2); + assert_eq!(a.checked_add(&b), Some(FixedU128::saturating_from_rational(8, 2))); + assert_eq!(a.checked_sub(&b), Some(FixedU128::saturating_from_rational(2, 2))); + assert_eq!(a.checked_mul(&b), Some(FixedU128::saturating_from_rational(15, 4))); + assert_eq!(a.checked_div(&b), Some(FixedU128::saturating_from_rational(10, 6))); let a = FixedU128::from_natural(120); let b = 2i32; - assert_eq!(a.checked_div_int::<i32>(&b), Some(60)); + assert_eq!(a.checked_div_int::<i32>(b), Some(60)); - let a = FixedU128::from_rational(20, 1); + let a = FixedU128::saturating_from_rational(20, 1); let b = 2i32; - assert_eq!(a.checked_div_int::<i32>(&b), Some(10)); + assert_eq!(a.checked_div_int::<i32>(b), Some(10)); let a = FixedU128::from_natural(120); let b = 2i32; - assert_eq!(a.checked_mul_int::<i32>(&b), Some(240)); + assert_eq!(a.checked_mul_int::<i32>(b), Some(240)); - let a = FixedU128::from_rational(1, 2); + let a = FixedU128::saturating_from_rational(1, 2); let b = 20i32; - assert_eq!(a.checked_mul_int::<i32>(&b), Some(10)); - } - - #[test] - fn saturating_mul_int_works() { - let a = FixedU128::from_rational(10, 1); - let b = u32::max_value() / 5; - assert_eq!(a.saturating_mul_int(&b), u32::max_value()); - - let a = FixedU128::from_rational(3, 1); - let b = 100u8; - assert_eq!(a.saturating_mul_int(&b), 255u8); - - let a = FixedU128::from_rational(10, 1); - let b = 123; - assert_eq!(a.saturating_mul_int(&b), 1230); + assert_eq!(a.checked_mul_int::<i32>(b), Some(10)); } #[test] @@ -359,81 +624,531 @@ mod tests { } #[test] - fn checked_div_with_zero_should_be_none() { - let a = FixedU128::from_natural(1); - let b = FixedU128::from_natural(0); - assert_eq!(a.checked_div(&b), None); + fn op_checked_add_overflow_should_be_none() { + let a = FixedU128::max_value(); + let b = 1.into(); + assert!(a.checked_add(&b).is_none()); } #[test] - fn checked_div_int_with_zero_should_be_none() { - let a = FixedU128::from_natural(1); - let b = 0i32; - assert_eq!(a.checked_div_int(&b), None); + #[should_panic(expected = "attempt to add with overflow")] + fn op_add_overflow_should_panic() { + let a = FixedU128::max_value(); + let b = 1.into(); + let _c = a + b; } #[test] - fn under_flow_should_be_none() { - let a = FixedU128::from_natural(2); - let b = FixedU128::from_natural(3); - assert_eq!(a.checked_sub(&b), None); + fn op_add_works() { + let a = FixedU128::saturating_from_rational(1, 2); + let b = FixedU128::saturating_from_rational(5, 3); + assert_eq!(FixedU128::saturating_from_rational(13, 6), a + b); + } + + #[test] + fn op_checked_sub_underflow_should_be_none() { + let a = FixedU128::min_value(); + let b = 1.into(); + assert!(a.checked_sub(&b).is_none()); + } + + #[test] + #[should_panic(expected = "attempt to subtract with overflow")] + fn op_sub_underflow_should_panic() { + let a = FixedU128::min_value(); + let b = 1.into(); + let _c = a - b; + } + + #[test] + fn op_sub_works() { + let a = FixedU128::saturating_from_rational(1, 2); + let b = FixedU128::saturating_from_rational(5, 3); + assert_eq!(FixedU128::saturating_from_rational(7, 6), b - a); + } + + #[test] + fn op_checked_mul_overflow_should_be_none() { + let a = FixedU128::max_value(); + let b = 2.into(); + assert!(a.checked_mul(&b).is_none()); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn op_mul_overflow_should_panic() { + let a = FixedU128::max_value(); + let b = 2.into(); + let _c = a * b; + } + + #[test] + fn op_mul_works() { + let a = FixedU128::saturating_from_rational(1, 2); + let b = FixedU128::saturating_from_rational(5, 3); + assert_eq!(FixedU128::saturating_from_rational(5, 6), a * b); + } + + #[test] + fn op_checked_div_with_zero_should_be_none() { + let a = FixedU128::min_value(); + let b = 0.into(); + assert!(a.checked_div(&b).is_none()); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn op_div_zero_should_panic() { + let a = FixedU128::max_value(); + let b = 0.into(); + let _c = a / b; + } + + #[test] + fn op_div_works() { + let a = FixedU128::saturating_from_rational(1, 2); + let b = FixedU128::saturating_from_rational(5, 3); + assert_eq!(FixedU128::saturating_from_rational(3, 10), a / b); + } + + #[test] + fn saturation_from_integer_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + let accuracy = FixedU128::accuracy(); + + // Cases where integer fits. + let a = FixedU128::saturating_from_integer(42); + assert_eq!(a.into_inner(), 42 * accuracy); + + // Cases where pass an negative number, should return zero + let a = FixedU128::saturating_from_integer(-42); + assert_eq!(a.into_inner(), 0); + + // Max/min integers that fit. + let a = FixedU128::saturating_from_integer(inner_max / accuracy); + assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); + + let a = FixedU128::saturating_from_integer(inner_min / accuracy); + assert_eq!(a.into_inner(), (inner_min / accuracy) * accuracy); + + // Cases where integer doesn't fit, so it saturates. + let a = FixedU128::saturating_from_integer(inner_max / accuracy + 1); + assert_eq!(a.into_inner(), inner_max); + } + + #[test] + fn checked_from_integer_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + let accuracy = FixedU128::accuracy(); + + // Cases where integer fits. + let a = FixedU128::checked_from_integer(42).expect("42 * accuracy < inner_max, qed"); + assert_eq!(a.into_inner(), 42 * accuracy); + + // Max/min integers that fit. + let a = FixedU128::checked_from_integer(inner_max / accuracy).expect("inner_max / accuracy < inner_max, qed"); + assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); + + let a = FixedU128::checked_from_integer(inner_min).expect("inner_min = 0, qed"); + assert_eq!(a.into_inner(), inner_min); + + // Cases where integer not fit. + let a = FixedU128::checked_from_integer(inner_max / accuracy + 1); + assert_eq!(a, None); + } + + #[test] + fn from_inner_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + assert_eq!(FixedU128::max_value(), FixedU128::from_inner(inner_max)); + assert_eq!(FixedU128::min_value(), FixedU128::from_inner(inner_min)); + } + + #[test] + fn saturating_from_ration_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + let accuracy = FixedU128::accuracy(); + + // Cases where parameters fit. + let a = FixedU128::saturating_from_rational(3, 5); + assert_eq!(a.into_inner(), 3 * accuracy / 5); + + // Cases where MIX/MIN + let a = FixedU128::saturating_from_rational(inner_min, 1); + assert_eq!(a.into_inner(), inner_min); + + let a = FixedU128::saturating_from_rational(inner_max, 1); + assert_eq!(a.into_inner(), inner_max); + + // Cases where parameters are negative should return zero + let a = FixedU128::saturating_from_rational(-1, 1); + assert_eq!(a.into_inner(), 0); + + let a = FixedU128::saturating_from_rational(1, -1); + assert_eq!(a.into_inner(), 0); + + let a = FixedU128::saturating_from_rational(-3, -5); + assert_eq!(a.into_inner(), 0); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn saturation_from_ration_with_zero_should_panic() { + let _a = FixedU128::saturating_from_rational(100, 0); + } + + #[test] + fn checked_from_rational_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + let accuracy = FixedU128::accuracy(); + + let a = FixedU128::checked_from_rational(3, 5).expect("3 * accuracy / 5 < inner_max, qed"); + assert_eq!(a.into_inner(), 3 * accuracy / 5); + + // Case: limit + let a = FixedU128::checked_from_rational(inner_min, 1).expect("inner_min / 1 = inner_min, qed"); + assert_eq!(a.into_inner(), inner_min); + + let a = FixedU128::saturating_from_rational(inner_max, 1); + assert_eq!(a.into_inner(), inner_max); + + let a = FixedU128::checked_from_rational(inner_max / accuracy, 1) + .expect("inner_max / accuracy * accuracy < inner_max, qed"); + assert_eq!(a.into_inner(), inner_max / accuracy * accuracy); + + let a = FixedU128::checked_from_rational(inner_min as i128 - 1, 1); + assert_eq!(a, None); + + let a = FixedU128::checked_from_rational(inner_max, 1); + assert_eq!(a, None); + + // Cases where parameters are negative should return None + let a = FixedU128::checked_from_rational(3, -5); + assert_eq!(a, None); + + let a = FixedU128::checked_from_rational(-3, 5); + assert_eq!(a, None); + + let a = FixedU128::checked_from_rational(-3, -5); + assert_eq!(a, None); + + // Case: divided zero should return None + let a = FixedU128::checked_from_rational(10, 0); + assert_eq!(a, None); + } + + #[test] + fn checked_mul_int_works() { + let a = FixedU128::saturating_from_rational(10, 1); + let b = u32::max_value() / 5; + assert_eq!(a.checked_mul_int(b), None); + + let a = FixedU128::saturating_from_integer(120); + let b = 2i32; + assert_eq!(a.checked_mul_int::<i32>(b), Some(240)); + + let a = FixedU128::saturating_from_rational(1, 2); + let b = 20i32; + assert_eq!(a.checked_mul_int::<i32>(b), Some(10)); + + // Case where the integer is negative should return None + let a = FixedU128::saturating_from_rational(1, 2); + let b = -20i32; + assert_eq!(a.checked_mul_int::<i32>(b), None); } #[test] - fn over_flow_should_be_none() { - let a = FixedU128::from_parts(u128::max_value() - 1); - let b = FixedU128::from_parts(2); - assert_eq!(a.checked_add(&b), None); + fn saturating_mul_int_works() { + let a = FixedU128::saturating_from_integer(2); + // Max - 1. + assert_eq!(a.saturating_mul_int((i128::max_value() - 1) / 2), i128::max_value() - 1); + // Max + assert_eq!(a.saturating_mul_int(i128::max_value() / 2), i128::max_value() - 1); + // Max + 1 => saturates to max. + assert_eq!(a.saturating_mul_int(i128::max_value() / 2 + 1), i128::max_value()); + + let a = FixedU128::saturating_from_rational(1, 2); + assert_eq!(a.saturating_mul_int(42i32), 21); + assert_eq!(a.saturating_mul_int(i128::max_value()), i128::max_value() / 2); + + // Case: saturating max + let c = FixedU128::saturating_from_integer(255); + assert_eq!(c.saturating_mul_int(2i8), i8::max_value()); + assert_eq!(c.saturating_mul_int(i128::max_value()), i128::max_value()); + + // Case: negative => 0 + assert_eq!(a.saturating_mul_int(-2i8), 0i8); + assert_eq!(a.saturating_mul_int(i128::min_value()), 0i128); + } + #[test] + fn checked_div_int_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + let accuracy = FixedU128::accuracy(); + + let a = FixedU128::from_inner(inner_max); + let b = FixedU128::from_inner(inner_min); + let c = FixedU128::zero(); + let d = FixedU128::one(); + let e = FixedU128::saturating_from_integer(6); + let f = FixedU128::saturating_from_integer(5); + + assert_eq!(e.checked_div_int(2.into()), Some(3)); + assert_eq!(f.checked_div_int(2.into()), Some(2)); + + assert_eq!(a.checked_div_int(i128::max_value()), Some(0)); + assert_eq!(a.checked_div_int(2), Some(inner_max / (2 * accuracy))); + assert_eq!(a.checked_div_int(inner_max / accuracy), Some(1)); + assert_eq!(a.checked_div_int(1i8), None); + + assert_eq!(b.checked_div_int(2), Some(0)); + assert_eq!(b.checked_div_int(1i8), Some(0)); + + assert_eq!(c.checked_div_int(1), Some(0)); + assert_eq!(c.checked_div_int(i128::max_value()), Some(0)); + assert_eq!(c.checked_div_int(1i8), Some(0)); + + assert_eq!(d.checked_div_int(1), Some(1)); + assert_eq!(d.checked_div_int(i32::max_value()), Some(0)); + + assert_eq!(d.checked_div_int(1i8), Some(1)); + + assert_eq!(a.checked_div_int(0), None); + assert_eq!(b.checked_div_int(0), None); + assert_eq!(c.checked_div_int(0), None); + assert_eq!(d.checked_div_int(0), None); + } + + #[test] + fn saturating_div_int_works() { let a = FixedU128::max_value(); - let b = FixedU128::from_rational(2, 1); - assert_eq!(a.checked_mul(&b), None); + let b = 5u32; + assert_eq!(a.saturating_div_int(b), u32::max_value()); + + let a = FixedU128::saturating_from_integer(100); + let b = 10u8; + assert_eq!(a.saturating_div_int(b), 10u8); + + // Case where the integer is negative should return zero + let a = FixedU128::saturating_from_integer(100); + let b = -10; + assert_eq!(a.saturating_div_int(b), 0); + + let a = FixedU128::saturating_from_integer(100); + let b = 10; + assert_eq!(a.saturating_div_int(b), 10); + } + + #[test] + fn saturating_mul_acc_int_works() { + assert_eq!(FixedU128::zero().saturating_mul_acc_int(42u8), 42u8); + assert_eq!(FixedU128::one().saturating_mul_acc_int(42u8), 2 * 42u8); + + assert_eq!( + FixedU128::one().saturating_mul_acc_int(i128::max_value()), + i128::max_value() + ); + assert_eq!( + FixedU128::one().saturating_mul_acc_int(i128::min_value()), + i128::min_value() + ); + + assert_eq!( + FixedU128::one().saturating_mul_acc_int(u128::max_value() / 2), + u128::max_value() - 1 + ); + } + + #[test] + fn checked_div_works() { + let inner_max = <FixedU128 as FixedUnsignedNumber>::Inner::max_value(); + let inner_min = <FixedU128 as FixedUnsignedNumber>::Inner::min_value(); + + let a = FixedU128::from_inner(inner_max); + let b = FixedU128::from_inner(inner_min); + let c = FixedU128::zero(); + let d = FixedU128::one(); + let e = FixedU128::saturating_from_integer(6); + let f = FixedU128::saturating_from_integer(5); + + assert_eq!(e.checked_div(&2.into()), Some(3.into())); + assert_eq!( + f.checked_div(&2.into()), + Some(FixedU128::saturating_from_rational(10, 4)) + ); + + assert_eq!(a.checked_div(&inner_max.into()), Some(1.into())); + assert_eq!(a.checked_div(&2.into()), Some(FixedU128::from_inner(inner_max / 2))); + assert_eq!(a.checked_div(&FixedU128::max_value()), Some(1.into())); + assert_eq!(a.checked_div(&d), Some(a)); + + // Cases inner_min is zero + assert_eq!(b.checked_div(&b), None); + + assert_eq!(c.checked_div(&1.into()), Some(0.into())); + assert_eq!(c.checked_div(&FixedU128::max_value()), Some(0.into())); + + assert_eq!(d.checked_div(&1.into()), Some(1.into())); + + assert_eq!(a.checked_div(&FixedU128::one()), Some(a)); + assert_eq!(b.checked_div(&FixedU128::one()), Some(b)); + assert_eq!(c.checked_div(&FixedU128::one()), Some(c)); + assert_eq!(d.checked_div(&FixedU128::one()), Some(d)); + + assert_eq!(a.checked_div(&FixedU128::zero()), None); + assert_eq!(b.checked_div(&FixedU128::zero()), None); + assert_eq!(c.checked_div(&FixedU128::zero()), None); + assert_eq!(d.checked_div(&FixedU128::zero()), None); + } + + #[test] + fn trunc_works() { + let n = FixedU128::saturating_from_rational(5, 2).trunc(); + assert_eq!(n, FixedU128::saturating_from_integer(2)); + } + + #[test] + fn frac_works() { + let n = FixedU128::saturating_from_rational(5, 2); + let i = n.trunc(); + let f = n.frac(); + + assert_eq!(n, i + f); + + let n = FixedU128::saturating_from_rational(5, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 5.into()); + + let n = FixedU128::saturating_from_rational(1, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 5.into()); + } + + #[test] + fn ceil_works() { + let n = FixedU128::saturating_from_rational(5, 2); + assert_eq!(n.ceil(), 3.into()); + + // On the limits: + let n = FixedU128::max_value(); + assert_eq!(n.ceil(), n.trunc()); + + let n = FixedU128::min_value(); + assert_eq!(n.ceil(), n.trunc()); + } + + #[test] + fn floor_works() { + let n = FixedU128::saturating_from_rational(5, 2); + assert_eq!(n.floor(), 2.into()); + + // On the limits: + let n = FixedU128::max_value(); + assert_eq!(n.floor(), n.trunc()); + + let n = FixedU128::min_value(); + assert_eq!(n.floor(), n.trunc()); + } + + #[test] + fn round_works() { + let n = FixedU128::zero(); + assert_eq!(n.round(), n); + + let n = FixedU128::one(); + assert_eq!(n.round(), n); + + let n = FixedU128::saturating_from_rational(5, 2); + assert_eq!(n.round(), 3.into()); + + let n = FixedU128::max_value(); + assert_eq!(n.round(), n.trunc()); + + let n = FixedU128::min_value(); + assert_eq!(n.round(), n.trunc()); + + // On the limit: + + // floor(max - 1) + 0.33.. + let n = FixedU128::max_value() + .saturating_sub(1.into()) + .trunc() + .saturating_add(FixedU128::saturating_from_rational(1, 3)); + + assert_eq!(n.round(), (FixedU128::max_value() - 1.into()).trunc()); + + // floor(min + 1) - 0.33.. + let n = FixedU128::min_value() + .saturating_add(1.into()) + .trunc() + .saturating_sub(FixedU128::saturating_from_rational(1, 3)); + + assert_eq!(n.round(), (FixedU128::min_value() + 1.into()).trunc()); + + // floor(max - 1) + 0.6 + let n = FixedU128::max_value() + .saturating_sub(1.into()) + .trunc() + .saturating_add(FixedU128::saturating_from_rational(1, 2)); + + assert_eq!(n.round(), FixedU128::max_value().trunc()); - let a = FixedU128::from_natural(255); - let b = 2u8; - assert_eq!(a.checked_mul_int(&b), None); + // floor(min + 1) - 0.6 + let n = FixedU128::min_value() + .saturating_add(1.into()) + .trunc() + .saturating_sub(FixedU128::saturating_from_rational(10, 6)); - let a = FixedU128::from_natural(256); - let b = 1u8; - assert_eq!(a.checked_div_int(&b), None); + assert_eq!(n.round(), FixedU128::min_value().trunc()); } #[test] fn perthing_into_fixed_u128() { let ten_percent_percent: FixedU128 = Percent::from_percent(10).into(); - assert_eq!(ten_percent_percent.deconstruct(), DIV / 10); + assert_eq!(ten_percent_percent.into_inner(), DIV / 10); let ten_percent_permill: FixedU128 = Permill::from_percent(10).into(); - assert_eq!(ten_percent_permill.deconstruct(), DIV / 10); + assert_eq!(ten_percent_permill.into_inner(), DIV / 10); let ten_percent_perbill: FixedU128 = Perbill::from_percent(10).into(); - assert_eq!(ten_percent_perbill.deconstruct(), DIV / 10); + assert_eq!(ten_percent_perbill.into_inner(), DIV / 10); let ten_percent_perquintill: FixedU128 = Perquintill::from_percent(10).into(); - assert_eq!(ten_percent_perquintill.deconstruct(), DIV / 10); + assert_eq!(ten_percent_perquintill.into_inner(), DIV / 10); } #[test] - fn recip_should_work() { + fn recip_works() { let a = FixedU128::from_natural(2); - assert_eq!(a.recip(), Some(FixedU128::from_rational(1, 2))); + assert_eq!(a.reciprocal(), Some(FixedU128::saturating_from_rational(1, 2))); let a = FixedU128::from_natural(2); - assert_eq!(a.recip().unwrap().checked_mul_int(&4i32), Some(2i32)); + assert_eq!(a.reciprocal().unwrap().checked_mul_int(4i32), Some(2i32)); - let a = FixedU128::from_rational(100, 121); - assert_eq!(a.recip(), Some(FixedU128::from_rational(121, 100))); + let a = FixedU128::saturating_from_rational(100, 121); + assert_eq!(a.reciprocal(), Some(FixedU128::saturating_from_rational(121, 100))); - let a = FixedU128::from_rational(1, 2); - assert_eq!(a.recip().unwrap().checked_mul(&a), Some(FixedU128::from_natural(1))); + let a = FixedU128::saturating_from_rational(1, 2); + assert_eq!( + a.reciprocal().unwrap().checked_mul(&a), + Some(FixedU128::from_natural(1)) + ); let a = FixedU128::from_natural(0); - assert_eq!(a.recip(), None); + assert_eq!(a.reciprocal(), None); } #[test] - fn serialize_deserialize_should_work() { - let two_point_five = FixedU128::from_rational(5, 2); + fn serialize_deserialize_works() { + let two_point_five = FixedU128::saturating_from_rational(5, 2); let serialized = serde_json::to_string(&two_point_five).unwrap(); assert_eq!(serialized, "\"2500000000000000000\""); let deserialized: FixedU128 = serde_json::from_str(&serialized).unwrap(); diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 7fb5b28a1d0e8d026c0f1b6375bca70db11d424a..4e8662c9a7b5c324d76348a708bdacd05fdf261a 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -5,5 +5,6 @@ pub mod linked_item; pub mod ordered_set; pub use fixed_u128::FixedU128; +pub use fixed_u128::FixedUnsignedNumber; pub use linked_item::{LinkedItem, LinkedList}; pub use ordered_set::OrderedSet;