From e044602f9eac6d07ba822e2d53ca465638c4d20e Mon Sep 17 00:00:00 2001 From: AlexSedNZ Date: Tue, 29 Sep 2020 14:28:21 +1300 Subject: [PATCH 1/3] Implement FixedPoint trait. (#5877) * Implement Fixed trait. * Fix tests * Fix tests * Fix tests 2 * Address review comment regarding from_i129. * Remove precision by using log10() as suggested in review. * Add small comments. * Use checked versions + panic for ops::*. * Remove repeated test. * Uncomment test. * Remove casts. * Add more comments. * Add tests. * Panic on saturating_div_int * More tests. * More docs. * Saturating renames. * Fix to_bound doc. * Move some impl to trait. * Add range * Add macro pre. * More round() tests. * Delete confusion. * More impl to trait * Add doc for fixedpoint op. * Remove trailing spaces. * Suggested docs changes. * More tests and comments for roundings. * Some quickcheck tests. * Add missing panic, more test/comments. * Nits. * Rename. * Remove primitives-types import. * Apply review suggestions * Fix long lines and add some fuzz. * fix long line * Update fuzzer * Bump impl * fix warnings Co-authored-by: Gavin Wood Co-authored-by: Shawn Tabrizi Commit: c5280a6a6d54508ca24672d87aafe33782d1068e [c5280a6a6] Parents: d047cb3fd8 Author: Marcio Diaz Date: 22 May 2020 at 5:32:44 AM NZST Committer: AlexSedNZ Commit Date: 29 September 2020 at 2:12:51 PM NZDT Labels: origin/fix/141-weight-multiplier GPG Details: [GOOD] Alex Sedighi --- Cargo.lock | 4 +- bin/node/executor/tests/basic.rs | 8 +- bin/node/executor/tests/fees.rs | 8 +- bin/node/runtime/src/impls.rs | 76 +- bin/node/runtime/src/lib.rs | 2 +- frame/balances/src/tests.rs | 4 +- frame/transaction-payment/src/lib.rs | 19 +- primitives/arithmetic/fuzzer/Cargo.lock | 401 ------ primitives/arithmetic/fuzzer/Cargo.toml | 6 +- primitives/arithmetic/fuzzer/src/fixed.rs | 82 ++ primitives/arithmetic/src/fixed.rs | 1489 +++++++++++++++++++++ primitives/arithmetic/src/lib.rs | 18 +- primitives/arithmetic/src/traits.rs | 4 +- primitives/runtime/src/lib.rs | 2 +- 14 files changed, 1654 insertions(+), 469 deletions(-) delete mode 100644 primitives/arithmetic/fuzzer/Cargo.lock create mode 100644 primitives/arithmetic/fuzzer/src/fixed.rs create mode 100644 primitives/arithmetic/src/fixed.rs diff --git a/Cargo.lock b/Cargo.lock index 8e04967ecc..a04d8bc26c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2098,9 +2098,9 @@ dependencies = [ [[package]] name = "honggfuzz" -version = "0.5.47" +version = "0.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3de2c3273ef7735df1c5a72128ca85b1d20105b9aac643cdfd7a6e581311150" +checksum = "832bac18a82ec7d6c21887daa8616b238fe90d5d5e762d0d4b9372cdaa9e097f" dependencies = [ "arbitrary", "lazy_static", diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index 49b9876afa..346d06e6b1 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -24,7 +24,7 @@ use sp_core::{ NeverNativeValue, map, traits::Externalities, storage::{well_known_keys, Storage}, }; use sp_runtime::{ - ApplyExtrinsicResult, Fixed64, + ApplyExtrinsicResult, Fixed128, FixedPointNumber, traits::{Hash as HashT, Convert, BlakeTwo256}, transaction_validity::InvalidTransaction, }; @@ -53,15 +53,15 @@ pub const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY; /// Default transfer fee // NOTE: Transfer fee increased by 1 byte * TransactionByteFee as we include Option in SignedExtra. // Option always takes up one byte in extrinsic payload -fn transfer_fee(extrinsic: &E, fee_multiplier: Fixed64) -> Balance { +fn transfer_fee(extrinsic: &E, fee_multiplier: Fixed128) -> Balance { let length_fee = TransactionByteFee::get() * (extrinsic.encode().len() as Balance); let weight = default_transfer_call().get_dispatch_info().weight; let weight_fee = ::WeightToFee::convert(weight); - + let base_fee = TransactionBaseFee::get(); - base_fee + fee_multiplier.saturated_multiply_accumulate(length_fee + weight_fee) + base_fee + fee_multiplier.saturating_mul_acc_int(length_fee + weight_fee) } fn xt() -> UncheckedExtrinsic { diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index 660450d0c3..010bb2db11 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -21,7 +21,7 @@ use frame_support::{ weights::GetDispatchInfo, }; use sp_core::{NeverNativeValue, map, storage::Storage}; -use sp_runtime::{Fixed64, Perbill, traits::{Convert, BlakeTwo256}}; +use sp_runtime::{FixedPointNumber, Fixed128, Perbill, traits::{Convert, BlakeTwo256}}; use node_runtime::{ CheckedExtrinsic, Call, Runtime, Balances, TransactionPayment, TransactionBaseFee, TransactionByteFee, WeightFeeCoefficient, @@ -39,7 +39,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { let mut t = new_test_ext(COMPACT_CODE, false); // initial fee multiplier must be zero - let mut prev_multiplier = Fixed64::from_parts(0); + let mut prev_multiplier = Fixed128::from_inner(0); t.execute_with(|| { assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier); @@ -202,7 +202,7 @@ fn transaction_fee_is_correct_ultimate() { fn block_weight_capacity_report() { // Just report how many transfer calls you could fit into a block. The number should at least // be a few hundred (250 at the time of writing but can change over time). Runs until panic. - use node_primitives::Index; + use node_primitives::{Index, Hash}; // execution ext. let mut t = new_test_ext(COMPACT_CODE, false); @@ -269,7 +269,7 @@ fn block_length_capacity_report() { // Just report how big a block can get. Executes until panic. Should be ignored unless if // manually inspected. The number should at least be a few megabytes (5 at the time of // writing but can change over time). - use node_primitives::Index; + use node_primitives::{Index, Hash}; // execution ext. let mut t = new_test_ext(COMPACT_CODE, false); diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 5de0dc27a4..5e41e16aee 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -18,7 +18,7 @@ use node_primitives::Balance; use sp_runtime::traits::{Convert, Saturating}; -use sp_runtime::{Fixed64, Perbill}; +use sp_runtime::{FixedPointNumber, Fixed128, Perbill}; use frame_support::{traits::{OnUnbalanced, Currency, Get}, weights::Weight}; use crate::{Balances, System, Authorship, MaximumBlockWeight, NegativeImbalance}; @@ -68,8 +68,8 @@ impl> Convert for LinearWeightToFee { /// https://research.web3.foundation/en/latest/polkadot/Token%20Economics/#relay-chain-transaction-fees pub struct TargetedFeeAdjustment(sp_std::marker::PhantomData); -impl> Convert for TargetedFeeAdjustment { - fn convert(multiplier: Fixed64) -> Fixed64 { +impl> Convert for TargetedFeeAdjustment { + fn convert(multiplier: Fixed128) -> Fixed128 { let block_weight = System::all_extrinsics_weight(); let max_weight = MaximumBlockWeight::get(); let target_weight = (T::get() * max_weight) as u128; @@ -78,15 +78,14 @@ impl> Convert for TargetedFeeAdjustment { // determines if the first_term is positive let positive = block_weight >= target_weight; let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight); - // diff is within u32, safe. - let diff = Fixed64::from_rational(diff_abs as i64, max_weight as u64); + // safe, diff_abs cannot exceed u64. + let diff = Fixed128::saturating_from_rational(diff_abs, max_weight.max(1)); let diff_squared = diff.saturating_mul(diff); // 0.00004 = 4/100_000 = 40_000/10^9 - let v = Fixed64::from_rational(4, 100_000); - // 0.00004^2 = 16/10^10 ~= 2/10^9. Taking the future /2 into account, then it is just 1 - // parts from a billionth. - let v_squared_2 = Fixed64::from_rational(1, 1_000_000_000); + let v = Fixed128::saturating_from_rational(4, 100_000); + // 0.00004^2 = 16/10^10 Taking the future /2 into account... 8/10^10 + let v_squared_2 = Fixed128::saturating_from_rational(8, 10_000_000_000u64); let first_term = v.saturating_mul(diff); // It is very unlikely that this will exist (in our poor perbill estimate) but we are giving @@ -107,7 +106,7 @@ impl> Convert for TargetedFeeAdjustment { // multiplier. While at -1, it means that the network is so un-congested that all // transactions have no weight fee. We stop here and only increase if the network // became more busy. - .max(Fixed64::from_rational(-1, 1)) + .max(Fixed128::saturating_from_integer(-1)) } } } @@ -129,24 +128,24 @@ mod tests { } // poc reference implementation. - fn fee_multiplier_update(block_weight: Weight, previous: Fixed64) -> Fixed64 { - let block_weight = block_weight as f32; - let v: f32 = 0.00004; + fn fee_multiplier_update(block_weight: Weight, previous: Fixed128) -> Fixed128 { + let block_weight = block_weight as f64; + let v: f64 = 0.00004; // maximum tx weight - let m = max() as f32; + let m = max() as f64; // Ideal saturation in terms of weight - let ss = target() as f32; + let ss = target() as f64; // Current saturation in terms of weight let s = block_weight; let fm = v * (s/m - ss/m) + v.powi(2) * (s/m - ss/m).powi(2) / 2.0; - let addition_fm = Fixed64::from_parts((fm * 1_000_000_000_f32).round() as i64); + let addition_fm = Fixed128::from_inner((fm * Fixed128::accuracy() as f64).round() as i128); previous.saturating_add(addition_fm) } - fn feemul(parts: i64) -> Fixed64 { - Fixed64::from_parts(parts) + fn feemul(parts: i128) -> Fixed128 { + Fixed128::from_inner(parts) } fn run_with_system_weight(w: Weight, assertions: F) where F: Fn() -> () { @@ -160,7 +159,7 @@ mod tests { #[test] fn fee_multiplier_update_poc_works() { - let fm = Fixed64::from_rational(0, 1); + let fm = Fixed128::saturating_from_rational(0, 1); let test_set = vec![ (0, fm.clone()), (100, fm.clone()), @@ -171,9 +170,10 @@ mod tests { test_set.into_iter().for_each(|(w, fm)| { run_with_system_weight(w, || { assert_eq_error_rate!( - fee_multiplier_update(w, fm).into_inner(), - TargetedFeeAdjustment::::convert(fm).into_inner(), - 5, + fee_multiplier_update(w, fm), + TargetedFeeAdjustment::::convert(fm), + // Error is only 1 in 10^18 + Fixed128::from_inner(1), ); }) }) @@ -184,12 +184,12 @@ mod tests { // just a few txs per_block. let block_weight = 0; run_with_system_weight(block_weight, || { - let mut fm = Fixed64::default(); + let mut fm = Fixed128::default(); let mut iterations: u64 = 0; loop { let next = TargetedFeeAdjustment::::convert(fm); fm = next; - if fm == Fixed64::from_rational(-1, 1) { break; } + if fm == Fixed128::saturating_from_integer(-1) { break; } iterations += 1; } println!("iteration {}, new fm = {:?}. Weight fee is now zero", iterations, fm); @@ -217,7 +217,7 @@ mod tests { run_with_system_weight(block_weight, || { // initial value configured on module - let mut fm = Fixed64::default(); + let mut fm = Fixed128::default(); assert_eq!(fm, TransactionPayment::next_fee_multiplier()); let mut iterations: u64 = 0; @@ -228,7 +228,7 @@ mod tests { fm = next; iterations += 1; let fee = ::WeightToFee::convert(tx_weight); - let adjusted_fee = fm.saturated_multiply_accumulate(fee); + let adjusted_fee = fm.saturating_mul_acc_int(fee); println!( "iteration {}, new fm = {:?}. Fee at this point is: {} units / {} millicents, \ {} cents, {} dollars", @@ -248,14 +248,14 @@ mod tests { run_with_system_weight(target() / 4, || { // Light block. Fee is reduced a little. assert_eq!( - TargetedFeeAdjustment::::convert(Fixed64::default()), + TargetedFeeAdjustment::::convert(Fixed128::default()), feemul(-7500), ); }); run_with_system_weight(target() / 2, || { // a bit more. Fee is decreased less, meaning that the fee increases as the block grows. assert_eq!( - TargetedFeeAdjustment::::convert(Fixed64::default()), + TargetedFeeAdjustment::::convert(Fixed128::default()), feemul(-5000), ); @@ -263,14 +263,14 @@ mod tests { run_with_system_weight(target(), || { // ideal. Original fee. No changes. assert_eq!( - TargetedFeeAdjustment::::convert(Fixed64::default()), + TargetedFeeAdjustment::::convert(Fixed128::default()), feemul(0), ); }); run_with_system_weight(target() * 2, || { // // More than ideal. Fee is increased. assert_eq!( - TargetedFeeAdjustment::::convert(Fixed64::default()), + TargetedFeeAdjustment::::convert(Fixed128::default()), feemul(10000), ); }); @@ -280,7 +280,7 @@ mod tests { fn stateful_weight_mul_grow_to_infinity() { run_with_system_weight(target() * 2, || { assert_eq!( - TargetedFeeAdjustment::::convert(Fixed64::default()), + TargetedFeeAdjustment::::convert(Fixed128::default()), feemul(10000) ); assert_eq!( @@ -303,7 +303,7 @@ mod tests { fn stateful_weight_mil_collapse_to_minus_one() { run_with_system_weight(0, || { assert_eq!( - TargetedFeeAdjustment::::convert(Fixed64::default()), + TargetedFeeAdjustment::::convert(Fixed128::default()), feemul(-10000) ); assert_eq!( @@ -316,8 +316,8 @@ mod tests { ); // ... assert_eq!( - TargetedFeeAdjustment::::convert(feemul(1_000_000_000 * -1)), - feemul(-1_000_000_000) + TargetedFeeAdjustment::::convert(Fixed128::saturating_from_integer(-1)), + Fixed128::saturating_from_integer(-1) ); }) } @@ -326,7 +326,7 @@ mod tests { fn weight_to_fee_should_not_overflow_on_large_weights() { let kb = 1024 as Weight; let mb = kb * kb; - let max_fm = Fixed64::from_natural(i64::max_value()); + let max_fm = Fixed128::saturating_from_integer(i128::max_value()); // check that for all values it can compute, correctly. vec![ @@ -343,9 +343,9 @@ mod tests { Weight::max_value(), ].into_iter().for_each(|i| { run_with_system_weight(i, || { - let next = TargetedFeeAdjustment::::convert(Fixed64::default()); - let truth = fee_multiplier_update(i, Fixed64::default()); - assert_eq_error_rate!(truth.into_inner(), next.into_inner(), 5); + let next = TargetedFeeAdjustment::::convert(Fixed128::default()); + let truth = fee_multiplier_update(i, Fixed128::default()); + assert_eq_error_rate!(truth, next, Fixed128::from_inner(50_000_000)); }); }); diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5405c44d2f..0c9c8396d9 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -89,7 +89,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. spec_version: 241, - impl_version: 0, + impl_version: 1, apis: RUNTIME_API_VERSIONS, }; diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index 3d0c9e9207..e46256c835 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -21,7 +21,7 @@ macro_rules! decl_tests { ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { use crate::*; - use sp_runtime::{Fixed64, traits::{SignedExtension, BadOrigin}}; + use sp_runtime::{FixedPointNumber, Fixed128, traits::{SignedExtension, BadOrigin}}; use frame_support::{ assert_noop, assert_ok, assert_err, traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, @@ -127,7 +127,7 @@ macro_rules! decl_tests { .monied(true) .build() .execute_with(|| { - pallet_transaction_payment::NextFeeMultiplier::put(Fixed64::from_natural(1)); + pallet_transaction_payment::NextFeeMultiplier::put(Fixed128::saturating_from_integer(1)); Balances::set_lock(ID_1, &1, 10, WithdrawReason::Reserve.into()); assert_noop!( >::transfer(&1, &2, 1, AllowDeath), diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 8074a5f6a5..a434173752 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -39,7 +39,7 @@ use frame_support::{ weights::{Weight, DispatchInfo, GetDispatchInfo}, }; use sp_runtime::{ - Fixed64, + Fixed128, FixedPointNumber, transaction_validity::{ TransactionPriority, ValidTransaction, InvalidTransaction, TransactionValidityError, TransactionValidity, @@ -48,7 +48,7 @@ use sp_runtime::{ }; use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; -type Multiplier = Fixed64; +type Multiplier = Fixed128; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = @@ -78,7 +78,7 @@ pub trait Trait: frame_system::Trait { decl_storage! { trait Store for Module as TransactionPayment { - pub NextFeeMultiplier get(fn next_fee_multiplier): Multiplier = Multiplier::from_parts(0); + pub NextFeeMultiplier get(fn next_fee_multiplier): Multiplier = Multiplier::from_inner(0); } } @@ -178,11 +178,10 @@ impl ChargeTransactionPayment { // the adjustable part of the fee let adjustable_fee = len_fee.saturating_add(weight_fee); let targeted_fee_adjustment = NextFeeMultiplier::get(); - // adjusted_fee = adjustable_fee + (adjustable_fee * targeted_fee_adjustment) - let adjusted_fee = targeted_fee_adjustment.saturated_multiply_accumulate(adjustable_fee); + let adjusted_fee = targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into()); let base_fee = T::TransactionBaseFee::get(); - base_fee.saturating_add(adjusted_fee).saturating_add(tip) + base_fee.saturating_add(adjusted_fee.saturated_into()).saturating_add(tip) } else { tip } @@ -525,7 +524,7 @@ mod tests { .execute_with(|| { // all fees should be x1.5 - NextFeeMultiplier::put(Fixed64::from_rational(1, 2)); + NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 2)); let len = 10; assert!( @@ -553,7 +552,7 @@ mod tests { .execute_with(|| { // all fees should be x1.5 - NextFeeMultiplier::put(Fixed64::from_rational(1, 2)); + NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 2)); assert_eq!( TransactionPayment::query_info(xt, len), @@ -582,7 +581,7 @@ mod tests { .execute_with(|| { // Next fee multiplier is zero - assert_eq!(NextFeeMultiplier::get(), Fixed64::from_natural(0)); + assert_eq!(NextFeeMultiplier::get(), Fixed128::saturating_from_integer(0)); // Tip only, no fees works let dispatch_info = DispatchInfo { @@ -622,7 +621,7 @@ mod tests { .execute_with(|| { // Add a next fee multiplier - NextFeeMultiplier::put(Fixed64::from_rational(1, 2)); // = 1/2 = .5 + NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 2)); // = 1/2 = .5 // Base fee is unaffected by multiplier let dispatch_info = DispatchInfo { weight: 0, diff --git a/primitives/arithmetic/fuzzer/Cargo.lock b/primitives/arithmetic/fuzzer/Cargo.lock deleted file mode 100644 index 3a4187437a..0000000000 --- a/primitives/arithmetic/fuzzer/Cargo.lock +++ /dev/null @@ -1,401 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "arbitrary" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "bitvec" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" - -[[package]] -name = "byte-slice-cast" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" - -[[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "fixed-hash" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" -dependencies = [ - "byteorder", - "rand", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "getrandom" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "honggfuzz" -version = "0.5.45" -dependencies = [ - "arbitrary", - "lazy_static", - "memmap", -] - -[[package]] -name = "impl-codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "integer-sqrt" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65877bf7d44897a473350b1046277941cee20b263397e90869c50b6e766088b" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" - -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -dependencies = [ - "autocfg", -] - -[[package]] -name = "parity-scale-codec" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" - -[[package]] -name = "primitive-types" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" -dependencies = [ - "fixed-hash", - "impl-codec", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom", - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -dependencies = [ - "c2-chacha", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "serde" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-arithmetic" -version = "2.0.0-alpha.3" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "serde", - "sp-debug-derive", - "sp-std", -] - -[[package]] -name = "sp-arithmetic-fuzzer" -version = "2.0.0" -dependencies = [ - "honggfuzz", - "num-bigint", - "num-traits", - "primitive-types", - "sp-arithmetic", -] - -[[package]] -name = "sp-debug-derive" -version = "2.0.0-alpha.3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-std" -version = "2.0.0-alpha.3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "toml" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" -dependencies = [ - "serde", -] - -[[package]] -name = "uint" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d" -dependencies = [ - "byteorder", - "crunchy", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index 895ee60bd8..b5a3918cdd 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/sp-arithmetic-fuzzer" [dependencies] sp-arithmetic = { version = "2.0.0-alpha.5", path = ".." } -honggfuzz = "0.5" +honggfuzz = "0.5.49" primitive-types = "0.7.0" num-bigint = "0.2" num-traits = "0.2" @@ -23,3 +23,7 @@ path = "src/biguint.rs" [[bin]] name = "rational128" path = "src/rational128.rs" + +[[bin]] +name = "fixed" +path = "src/fixed.rs" \ No newline at end of file diff --git a/primitives/arithmetic/fuzzer/src/fixed.rs b/primitives/arithmetic/fuzzer/src/fixed.rs new file mode 100644 index 0000000000..115d7dbbdb --- /dev/null +++ b/primitives/arithmetic/fuzzer/src/fixed.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run fixed`. `honggfuzz` CLI options can +//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! +//! # Debugging a panic +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug fixed hfuzz_workspace/fixed/*.fuzz`. +//! +//! # More information +//! More information about `honggfuzz` can be found +//! [here](https://docs.rs/honggfuzz/). + +use honggfuzz::fuzz; +use sp_arithmetic::{FixedPointNumber, Fixed64, traits::Saturating}; + +fn main() { + loop { + fuzz!(|data: (i32, i32)| { + let x: i128 = data.0.into(); + let y: i128 = data.1.into(); + + // Check `from_rational` and division are consistent. + if y != 0 { + let f1 = Fixed64::saturating_from_integer(x) / Fixed64::saturating_from_integer(y); + let f2 = Fixed64::saturating_from_rational(x, y); + assert_eq!(f1.into_inner(), f2.into_inner()); + } + + // Check `saturating_mul`. + let a = Fixed64::saturating_from_rational(2, 5); + let b = a.saturating_mul(Fixed64::saturating_from_integer(x)); + let n = b.into_inner() as i128; + let m = 2i128 * x * Fixed64::accuracy() as i128 / 5i128; + assert_eq!(n, m); + + // Check `saturating_mul` and division are inverse. + if x != 0 { + assert_eq!(a, b / Fixed64::saturating_from_integer(x)); + } + + // Check `reciprocal`. + let r = a.reciprocal().unwrap().reciprocal().unwrap(); + assert_eq!(a, r); + + // Check addition. + let a = Fixed64::saturating_from_integer(x); + let b = Fixed64::saturating_from_integer(y); + let c = Fixed64::saturating_from_integer(x.saturating_add(y)); + assert_eq!(a.saturating_add(b), c); + + // Check substraction. + let a = Fixed64::saturating_from_integer(x); + let b = Fixed64::saturating_from_integer(y); + let c = Fixed64::saturating_from_integer(x.saturating_sub(y)); + assert_eq!(a.saturating_sub(b), c); + + // Check `saturating_mul_acc_int`. + let a = Fixed64::saturating_from_rational(2, 5); + let b = a.saturating_mul_acc_int(x); + let xx = Fixed64::saturating_from_integer(x); + let d = a.saturating_mul(xx).saturating_add(xx).into_inner() as i128 / Fixed64::accuracy() as i128; + assert_eq!(b, d); + }); + } +} diff --git a/primitives/arithmetic/src/fixed.rs b/primitives/arithmetic/src/fixed.rs new file mode 100644 index 0000000000..1e3ebe2e25 --- /dev/null +++ b/primitives/arithmetic/src/fixed.rs @@ -0,0 +1,1489 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Decimal Fixed Point implementations for Substrate runtime. + +use sp_std::{ops::{self, Add, Sub, Mul, Div}, fmt::Debug, prelude::*, convert::{TryInto, TryFrom}}; +use codec::{Encode, Decode}; +use crate::{ + helpers_128bit::multiply_by_rational, PerThing, + traits::{ + SaturatedConversion, CheckedSub, CheckedAdd, CheckedMul, CheckedDiv, CheckedNeg, + Bounded, Saturating, UniqueSaturatedInto, Zero, One, Signed + }, +}; + +#[cfg(feature = "std")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// Integer types that can be used to interact with `FixedPointNumber` implementations. +pub trait FixedPointOperand: Copy + Clone + Bounded + Zero + Saturating + + PartialOrd + UniqueSaturatedInto + TryFrom + CheckedNeg {} + +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 {} + +/// Something that implements a decimal fixed point number. +/// +/// The precision is given by `Self::DIV`, i.e. `1 / DIV` can be represented. +/// +/// Each type can store numbers from `Self::Inner::min_value() / Self::DIV` +/// to `Self::Inner::max_value() / Self::DIV`. +/// This is also referred to as the _accuracy_ of the type in the documentation. +pub trait FixedPointNumber: + Sized + Copy + Default + Debug + + Saturating + Bounded + + Eq + PartialEq + Ord + PartialOrd + + CheckedSub + CheckedAdd + CheckedMul + CheckedDiv + + Add + Sub + Div + Mul +{ + /// The underlying data type used for this fixed point number. + type Inner: Debug + One + CheckedMul + CheckedDiv + CheckedNeg + Signed + FixedPointOperand; + + /// 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 + } + + /// 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>(int: N) -> Self { + Self::from_inner(int.unique_saturated_into().saturating_mul(Self::DIV)) + } + + /// Creates `self` from an integer number `int`. + /// + /// Returns `None` if `int` exceeds accuracy. + fn checked_from_integer(int: Self::Inner) -> Option { + int.checked_mul(&Self::DIV).map(|inner| Self::from_inner(inner)) + } + + /// 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: N, d: D) -> Self { + if d == D::zero() { + panic!("attempt to divide by zero") + } + Self::checked_from_rational(n, d).unwrap_or(to_bound(n, d)) + } + + /// Creates `self` from a rational number. Equal to `n / d`. + /// + /// Returns `None` if `d == 0` or `n / d` exceeds accuracy. + fn checked_from_rational(n: N, d: D) -> Option { + if d == D::zero() { + return None + } + + let n: I129 = n.into(); + let d: I129 = d.into(); + let negative = n.negative != d.negative; + + multiply_by_rational(n.value, Self::DIV.unique_saturated_into(), d.value).ok() + .and_then(|value| from_i129(I129 { value, negative })) + .map(|inner| Self::from_inner(inner)) + } + + /// Checked multiplication for integer type `N`. Equal to `self * n`. + /// + /// Returns `None` if the result does not fit in `N`. + fn checked_mul_int(self, n: N) -> Option { + let lhs: I129 = self.into_inner().into(); + let rhs: I129 = n.into(); + let negative = lhs.negative != rhs.negative; + + multiply_by_rational(lhs.value, rhs.value, Self::DIV.unique_saturated_into()).ok() + .and_then(|value| from_i129(I129 { value, negative })) + } + + /// 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(self, n: N) -> N { + self.checked_mul_int(n).unwrap_or(to_bound(self.into_inner(), n)) + } + + /// 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(self, d: N) -> Option { + let lhs: I129 = self.into_inner().into(); + let rhs: I129 = d.into(); + let negative = lhs.negative != rhs.negative; + + lhs.value.checked_div(rhs.value) + .and_then(|n| n.checked_div(Self::DIV.unique_saturated_into())) + .and_then(|value| from_i129(I129 { value, negative })) + } + + /// 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(self, d: N) -> N { + if d == N::zero() { + panic!("attempt to divide by zero") + } + self.checked_div_int(d).unwrap_or(to_bound(self.into_inner(), d)) + } + + /// 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(self, n: N) -> N { + self.saturating_mul_int(n).saturating_add(n) + } + + /// Saturating absolute value. + /// + /// Returns `Self::max` if `self == Self::min`. + fn saturating_abs(self) -> Self { + let inner = self.into_inner(); + if inner.is_positive() { + self + } else { + Self::from_inner(inner.checked_neg().unwrap_or(Self::Inner::max_value())) + } + } + + /// Takes the reciprocal (inverse). Equal to `1 / self`. + /// + /// Returns `None` if `self = 0`. + fn reciprocal(self) -> Option { + Self::one().checked_div(&self) + } + + /// Returns zero. + fn zero() -> Self { + Self::from_inner(Self::Inner::zero()) + } + + /// Checks if the number is zero. + fn is_zero(&self) -> bool { + self.into_inner() == Self::Inner::zero() + } + + /// Returns one. + fn one() -> Self { + Self::from_inner(Self::DIV) + } + + /// Checks if the number is one. + fn is_one(&self) -> bool { + self.into_inner() == Self::Inner::one() + } + + /// Checks if the number is positive. + fn is_positive(self) -> bool { + self.into_inner() >= Self::Inner::zero() + } + + /// Checks if the number is negative. + fn is_negative(self) -> bool { + self.into_inner() < Self::Inner::zero() + } + + /// 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(|inner| Self::from_inner(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(); + let fractional = self.saturating_sub(integer); + if integer == Self::zero() { + fractional + } else { + fractional.saturating_abs() + } + } + + /// 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_negative() { + self.trunc() + } else { + self.saturating_add(Self::one()).trunc() + } + } + + /// 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 { + if self.is_negative() { + self.saturating_sub(Self::one()).trunc() + } else { + self.trunc() + } + } + + /// 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.trunc() + } else { + let extra = Self::saturating_from_integer(self.into_inner().signum()); + (self.saturating_add(extra)).trunc() + } + } +} + +/// Data type used as intermediate storage in some computations to avoid overflow. +struct I129 { + value: u128, + negative: bool, +} + +impl From for I129 { + fn from(n: N) -> I129 { + if n < N::zero() { + let value: u128 = n.checked_neg() + .map(|n| n.unique_saturated_into()) + .unwrap_or(N::max_value().unique_saturated_into().saturating_add(1)); + I129 { value, negative: true } + } else { + I129 { value: n.unique_saturated_into(), negative: false } + } + } +} + +/// Transforms an `I129` to `N` if it is possible. +fn from_i129(n: I129) -> Option { + let max_plus_one: u128 = N::max_value().unique_saturated_into().saturating_add(1); + if n.negative && N::min_value() < N::zero() && n.value == max_plus_one { + Some(N::min_value()) + } else { + let unsigned_inner: N = n.value.try_into().ok()?; + let inner = if n.negative { unsigned_inner.checked_neg()? } else { unsigned_inner }; + Some(inner) + } +} + +/// Returns `R::max` if the sign of `n * m` is positive, `R::min` otherwise. +fn to_bound(n: N, m: D) -> R { + if (n < N::zero()) != (m < D::zero()) { + R::min_value() + } else { + R::max_value() + } +} + +macro_rules! implement_fixed { + ( + $name:ident, + $test_mod:ident, + $inner_type:ty, + $div:tt, + $title:expr $(,)? + ) => { + /// A fixed point number representation in the range. + /// + #[doc = $title] + #[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub struct $name($inner_type); + + impl From<$inner_type> for $name { + fn from(int: $inner_type) -> Self { + $name::saturating_from_integer(int) + } + } + + impl From<(N, D)> for $name { + fn from(r: (N, D)) -> Self { + $name::saturating_from_rational(r.0, r.1) + } + } + + impl FixedPointNumber for $name { + type Inner = $inner_type; + + const DIV: Self::Inner = $div; + + fn from_inner(inner: Self::Inner) -> Self { + Self(inner) + } + + fn into_inner(self) -> Self::Inner { + self.0 + } + } + + impl Saturating for $name { + fn saturating_add(self, rhs: Self) -> Self { + Self(self.0.saturating_add(rhs.0)) + } + + fn saturating_sub(self, rhs: Self) -> Self { + Self(self.0.saturating_sub(rhs.0)) + } + + fn saturating_mul(self, rhs: Self) -> Self { + self.checked_mul(&rhs).unwrap_or(to_bound(self.0, rhs.0)) + } + } + + impl ops::Neg for $name { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0) + } + } + + impl ops::Add for $name { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } + } + + impl ops::Sub for $name { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } + } + + impl ops::Mul for $name { + 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 $name { + 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 CheckedSub for $name { + fn checked_sub(&self, rhs: &Self) -> Option { + self.0.checked_sub(rhs.0).map(Self) + } + } + + impl CheckedAdd for $name { + fn checked_add(&self, rhs: &Self) -> Option { + self.0.checked_add(rhs.0).map(Self) + } + } + + impl CheckedDiv for $name { + fn checked_div(&self, other: &Self) -> Option { + if other.0 == 0 { + return None + } + + let lhs: I129 = self.0.into(); + let rhs: I129 = other.0.into(); + let negative = lhs.negative != rhs.negative; + + multiply_by_rational(lhs.value, Self::DIV as u128, rhs.value).ok() + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self) + } + } + + impl CheckedMul for $name { + fn checked_mul(&self, other: &Self) -> Option { + let lhs: I129 = self.0.into(); + let rhs: I129 = other.0.into(); + let negative = lhs.negative != rhs.negative; + + multiply_by_rational(lhs.value, rhs.value, Self::DIV as u128).ok() + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self) + } + } + + impl Bounded for $name { + fn min_value() -> Self { + Self(::Inner::min_value()) + } + + fn max_value() -> Self { + Self(::Inner::max_value()) + } + } + + impl sp_std::fmt::Debug for $name { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + let integral = { + let int = self.0 / Self::accuracy(); + let signum_for_zero = if int == 0 && self.is_negative() { "-" } else { "" }; + format!("{}{}", signum_for_zero, int) + }; + let precision = (Self::accuracy() as f64).log10() as usize; + let fractional = format!("{:0>weight$}", (self.0 % Self::accuracy()).abs(), weight=precision); + write!(f, "{}({}.{})", stringify!($name), integral, fractional) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } + } + + impl From

for $name { + fn from(p: P) -> Self { + let accuracy = P::ACCURACY.saturated_into(); + let value = p.deconstruct().saturated_into(); + $name::saturating_from_rational(value, accuracy) + } + } + + #[cfg(feature = "std")] + impl sp_std::fmt::Display for $name { + 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 $name { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let inner: ::Inner = s.parse() + .map_err(|_| "invalid string input for fixed point number")?; + Ok(Self::from_inner(inner)) + } + } + + // Manual impl `Serialize` as serde_json does not support i128. + // TODO: remove impl if issue https://github.com/serde-rs/json/issues/548 fixed. + #[cfg(feature = "std")] + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } + } + + // Manual impl `Deserialize` as serde_json does not support i128. + // TODO: remove impl if issue https://github.com/serde-rs/json/issues/548 fixed. + #[cfg(feature = "std")] + impl<'de> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use sp_std::str::FromStr; + let s = String::deserialize(deserializer)?; + $name::from_str(&s).map_err(|err_str| de::Error::custom(err_str)) + } + } + + #[cfg(test)] + mod $test_mod { + use super::*; + use crate::{Perbill, Percent, Permill, Perquintill}; + + fn max() -> $name { + $name::max_value() + } + + fn min() -> $name { + $name::min_value() + } + + fn precision() -> usize { + ($name::accuracy() as f64).log10() as usize + } + + #[test] + fn macro_preconditions() { + assert!($name::DIV > 0); + } + + #[test] + fn from_i129_works() { + let a = I129 { + value: 1, + negative: true, + }; + + // Can't convert negative number to unsigned. + assert_eq!(from_i129::(a), None); + + let a = I129 { + value: u128::max_value() - 1, + negative: false, + }; + + // Max - 1 value fits. + assert_eq!(from_i129::(a), Some(u128::max_value() - 1)); + + let a = I129 { + value: u128::max_value(), + negative: false, + }; + + // Max value fits. + assert_eq!(from_i129::(a), Some(u128::max_value())); + + let a = I129 { + value: i128::max_value() as u128 + 1, + negative: true, + }; + + // Min value fits. + assert_eq!(from_i129::(a), Some(i128::min_value())); + + let a = I129 { + value: i128::max_value() as u128 + 1, + negative: false, + }; + + // Max + 1 does not fit. + assert_eq!(from_i129::(a), None); + + let a = I129 { + value: i128::max_value() as u128, + negative: false, + }; + + // Max value fits. + assert_eq!(from_i129::(a), Some(i128::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 => Max. + assert_eq!(to_bound::<_, _, i32>(a, b), i32::max_value()); + + let a = 1i32; + let b = -1i32; + + // Pos + Neg => Min. + assert_eq!(to_bound::<_, _, i32>(a, b), i32::min_value()); + + let a = -1i32; + let b = 1i32; + + // Neg + Pos => Min. + assert_eq!(to_bound::<_, _, i32>(a, b), i32::min_value()); + + let a = 1i32; + let b = -1i32; + + // Pos + Neg => Min (unsigned). + assert_eq!(to_bound::<_, _, u32>(a, b), 0); + } + + #[test] + #[should_panic(expected = "attempt to negate with overflow")] + fn op_neg_panics() { + let a = $name::min_value(); + let _ = -a; + } + + #[test] + fn op_neg_works() { + let a = $name::saturating_from_integer(5); + let b = -a; + + // Positive. + assert_eq!($name::saturating_from_integer(-5), b); + + let a = $name::saturating_from_integer(-5); + let b = -a; + + // Negative + assert_eq!($name::saturating_from_integer(5), b); + + let a = $name::max_value(); + let b = -a; + + // Max. + assert_eq!($name::min_value() + $name::from_inner(1), b); + + let a = $name::min_value() + $name::from_inner(1); + let b = -a; + + // Min. + assert_eq!($name::max_value(), b); + + let a = $name::zero(); + let b = -a; + + // Zero. + assert_eq!(a, b); + } + + #[test] + #[should_panic(expected = "attempt to add with overflow")] + fn op_add_panics() { + let a = $name::max_value(); + let b = 1.into(); + let _ = a + b; + } + + #[test] + fn op_add_works() { + let a = $name::saturating_from_rational(5, 2); + let b = $name::saturating_from_rational(1, 2); + + // Positive case: 6/2 = 3. + assert_eq!($name::saturating_from_integer(3), a + b); + + let b = $name::saturating_from_rational(1, -2); + + // Negative case: 4/2 = 2. + assert_eq!($name::saturating_from_integer(2), a + b); + } + + #[test] + #[should_panic(expected = "attempt to subtract with overflow")] + fn op_sub_panics() { + let a = $name::min_value(); + let b = 1.into(); + let _c = a - b; + } + + #[test] + fn op_sub_works() { + let a = $name::saturating_from_rational(5, 2); + let b = $name::saturating_from_rational(1, 2); + + // Negative case: 4/2 = 2. + assert_eq!($name::saturating_from_integer(2), a - b); + + let b = $name::saturating_from_rational(1, -2); + + // Positive case: 6/2 = 3. + assert_eq!($name::saturating_from_integer(3), a - b); + } + + #[test] + #[should_panic(expected = "attempt to multiply with overflow")] + fn op_mul_panics() { + let a = $name::max_value(); + let b = 2.into(); + let _c = a * b; + } + + #[test] + fn op_mul_works() { + let a = $name::saturating_from_integer(42); + let b = $name::saturating_from_integer(2); + assert_eq!($name::saturating_from_integer(84), a * b); + + let a = $name::saturating_from_integer(42); + let b = $name::saturating_from_integer(-2); + assert_eq!($name::saturating_from_integer(-84), a * b); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn op_div_panics_on_zero_divisor() { + let a = $name::saturating_from_integer(1); + let b = 0.into(); + let _c = a / b; + } + + #[test] + #[should_panic(expected = "attempt to divide with overflow")] + fn op_div_panics_on_overflow() { + let a = $name::min_value(); + let b = (-1).into(); + let _c = a / b; + } + + #[test] + fn op_div_works() { + let a = $name::saturating_from_integer(42); + let b = $name::saturating_from_integer(2); + assert_eq!($name::saturating_from_integer(21), a / b); + + let a = $name::saturating_from_integer(42); + let b = $name::saturating_from_integer(-2); + assert_eq!($name::saturating_from_integer(-21), a / b); + } + + #[test] + fn from_integer_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + let accuracy = $name::accuracy(); + + // Cases where integer fits. + let a = $name::saturating_from_integer(42); + assert_eq!(a.into_inner(), 42 * accuracy); + + let a = $name::saturating_from_integer(-42); + assert_eq!(a.into_inner(), -42 * accuracy); + + // Max/min integers that fit. + let a = $name::saturating_from_integer(inner_max / accuracy); + assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); + + let a = $name::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 = $name::saturating_from_integer(inner_max / accuracy + 1); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_integer(inner_min / accuracy - 1); + assert_eq!(a.into_inner(), inner_min); + } + + #[test] + fn checked_from_integer_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + let accuracy = $name::accuracy(); + + // Cases where integer fits. + let a = $name::checked_from_integer(42) + .expect("42 * accuracy <= inner_max; qed"); + assert_eq!(a.into_inner(), 42 * accuracy); + + let a = $name::checked_from_integer(-42) + .expect("-42 * accuracy >= inner_min; qed"); + assert_eq!(a.into_inner(), -42 * accuracy); + + // Max/min integers that fit. + let a = $name::checked_from_integer(inner_max / accuracy) + .expect("(inner_max / accuracy) * accuracy <= inner_max; qed"); + assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); + + let a = $name::checked_from_integer(inner_min / accuracy) + .expect("(inner_min / accuracy) * accuracy <= inner_min; qed"); + assert_eq!(a.into_inner(), (inner_min / accuracy) * accuracy); + + // Cases where integer doesn't fit, so it returns `None`. + let a = $name::checked_from_integer(inner_max / accuracy + 1); + assert_eq!(a, None); + + let a = $name::checked_from_integer(inner_min / accuracy - 1); + assert_eq!(a, None); + } + + #[test] + fn from_inner_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + + assert_eq!(max(), $name::from_inner(inner_max)); + assert_eq!(min(), $name::from_inner(inner_min)); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn saturating_from_rational_panics_on_zero_divisor() { + let _ = $name::saturating_from_rational(1, 0); + } + + #[test] + fn saturating_from_rational_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + let accuracy = $name::accuracy(); + + let a = $name::saturating_from_rational(5, 2); + + // Positive case: 2.5 + assert_eq!(a.into_inner(), 25 * accuracy / 10); + + let a = $name::saturating_from_rational(-5, 2); + + // Negative case: -2.5 + assert_eq!(a.into_inner(), -25 * accuracy / 10); + + let a = $name::saturating_from_rational(5, -2); + + // Other negative case: -2.5 + assert_eq!(a.into_inner(), -25 * accuracy / 10); + + let a = $name::saturating_from_rational(-5, -2); + + // Other positive case: 2.5 + assert_eq!(a.into_inner(), 25 * accuracy / 10); + + // Max - 1. + let a = $name::saturating_from_rational(inner_max - 1, accuracy); + assert_eq!(a.into_inner(), inner_max - 1); + + // Min + 1. + let a = $name::saturating_from_rational(inner_min + 1, accuracy); + assert_eq!(a.into_inner(), inner_min + 1); + + // Max. + let a = $name::saturating_from_rational(inner_max, accuracy); + assert_eq!(a.into_inner(), inner_max); + + // Min. + let a = $name::saturating_from_rational(inner_min, accuracy); + assert_eq!(a.into_inner(), inner_min); + + // Max + 1, saturates. + let a = $name::saturating_from_rational(inner_max as u128 + 1, accuracy); + assert_eq!(a.into_inner(), inner_max); + + // Min - 1, saturates. + let a = $name::saturating_from_rational(inner_max as u128 + 2, -accuracy); + assert_eq!(a.into_inner(), inner_min); + + // Zero. + let a = $name::saturating_from_rational(0, 1); + assert_eq!(a.into_inner(), 0); + + let a = $name::saturating_from_rational(inner_max, -accuracy); + assert_eq!(a.into_inner(), -inner_max); + + let a = $name::saturating_from_rational(inner_min, -accuracy); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_min + 1, -accuracy); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_max - 1, accuracy); + assert_eq!(a.into_inner(), inner_max - 1); + + let a = $name::saturating_from_rational(inner_min + 1, accuracy); + assert_eq!(a.into_inner(), inner_min + 1); + + let a = $name::saturating_from_rational(inner_max, 1); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_min, 1); + assert_eq!(a.into_inner(), inner_min); + + let a = $name::saturating_from_rational(inner_min, -1); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_max, -1); + assert_eq!(a.into_inner(), inner_min); + + let a = $name::saturating_from_rational(inner_max, inner_max); + assert_eq!(a.into_inner(), accuracy); + + let a = $name::saturating_from_rational(inner_min, inner_min); + assert_eq!(a.into_inner(), accuracy); + + let a = $name::saturating_from_rational(inner_max, -inner_max); + assert_eq!(a.into_inner(), -accuracy); + + let a = $name::saturating_from_rational(-inner_max, inner_max); + assert_eq!(a.into_inner(), -accuracy); + + let a = $name::saturating_from_rational(inner_max, 3 * accuracy); + assert_eq!(a.into_inner(), inner_max / 3); + + let a = $name::saturating_from_rational(inner_max, -3 * accuracy); + assert_eq!(a.into_inner(), -inner_max / 3); + + let a = $name::saturating_from_rational(inner_min, 2 * accuracy); + assert_eq!(a.into_inner(), inner_min / 2); + + let a = $name::saturating_from_rational(inner_min, accuracy / -3); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_min, accuracy / 3); + assert_eq!(a.into_inner(), inner_min); + + let a = $name::saturating_from_rational(1, accuracy); + assert_eq!(a.into_inner(), 1); + + let a = $name::saturating_from_rational(1, -accuracy); + assert_eq!(a.into_inner(), -1); + + // Out of accuracy. + let a = $name::saturating_from_rational(1, accuracy + 1); + assert_eq!(a.into_inner(), 0); + + let a = $name::saturating_from_rational(1, -accuracy - 1); + assert_eq!(a.into_inner(), 0); + } + + #[test] + fn checked_from_rational_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + let accuracy = $name::accuracy(); + + // Divide by zero => None. + let a = $name::checked_from_rational(1, 0); + assert_eq!(a, None); + + // Max - 1. + let a = $name::checked_from_rational(inner_max - 1, accuracy).unwrap(); + assert_eq!(a.into_inner(), inner_max - 1); + + // Min + 1. + let a = $name::checked_from_rational(inner_min + 1, accuracy).unwrap(); + assert_eq!(a.into_inner(), inner_min + 1); + + // Max. + let a = $name::checked_from_rational(inner_max, accuracy).unwrap(); + assert_eq!(a.into_inner(), inner_max); + + // Min. + let a = $name::checked_from_rational(inner_min, accuracy).unwrap(); + assert_eq!(a.into_inner(), inner_min); + + // Max + 1 => Overflow => None. + let a = $name::checked_from_rational(inner_min, -accuracy); + assert_eq!(a, None); + + // Min - 1 => Underflow => None. + let a = $name::checked_from_rational(inner_max as u128 + 2, -accuracy); + assert_eq!(a, None); + + let a = $name::checked_from_rational(inner_max, 3 * accuracy).unwrap(); + assert_eq!(a.into_inner(), inner_max / 3); + + let a = $name::checked_from_rational(inner_max, -3 * accuracy).unwrap(); + assert_eq!(a.into_inner(), -inner_max / 3); + + let a = $name::checked_from_rational(inner_min, 2 * accuracy).unwrap(); + assert_eq!(a.into_inner(), inner_min / 2); + + let a = $name::checked_from_rational(inner_min, accuracy / -3); + assert_eq!(a, None); + + let a = $name::checked_from_rational(inner_min, accuracy / 3); + assert_eq!(a, None); + + let a = $name::checked_from_rational(1, accuracy).unwrap(); + assert_eq!(a.into_inner(), 1); + + let a = $name::checked_from_rational(1, -accuracy).unwrap(); + assert_eq!(a.into_inner(), -1); + + let a = $name::checked_from_rational(1, accuracy + 1).unwrap(); + assert_eq!(a.into_inner(), 0); + + let a = $name::checked_from_rational(1, -accuracy - 1).unwrap(); + assert_eq!(a.into_inner(), 0); + } + + #[test] + fn checked_mul_int_works() { + let a = $name::saturating_from_integer(2); + // Max - 1. + assert_eq!(a.checked_mul_int((i128::max_value() - 1) / 2), Some(i128::max_value() - 1)); + // Max. + assert_eq!(a.checked_mul_int(i128::max_value() / 2), Some(i128::max_value() - 1)); + // Max + 1 => None. + assert_eq!(a.checked_mul_int(i128::max_value() / 2 + 1), None); + + // Min - 1. + assert_eq!(a.checked_mul_int((i128::min_value() + 1) / 2), Some(i128::min_value() + 2)); + // Min. + assert_eq!(a.checked_mul_int(i128::min_value() / 2), Some(i128::min_value())); + // Min + 1 => None. + assert_eq!(a.checked_mul_int(i128::min_value() / 2 - 1), None); + + let a = $name::saturating_from_rational(1, 2); + assert_eq!(a.checked_mul_int(42i128), Some(21)); + assert_eq!(a.checked_mul_int(i128::max_value()), Some(i128::max_value() / 2)); + assert_eq!(a.checked_mul_int(i128::min_value()), Some(i128::min_value() / 2)); + + let b = $name::saturating_from_rational(1, -2); + assert_eq!(b.checked_mul_int(42i128), Some(-21)); + assert_eq!(b.checked_mul_int(u128::max_value()), None); + assert_eq!(b.checked_mul_int(i128::max_value()), Some(i128::max_value() / -2)); + assert_eq!(b.checked_mul_int(i128::min_value()), Some(i128::min_value() / -2)); + + let c = $name::saturating_from_integer(255); + assert_eq!(c.checked_mul_int(2i8), None); + assert_eq!(c.checked_mul_int(2i128), Some(510)); + assert_eq!(c.checked_mul_int(i128::max_value()), None); + assert_eq!(c.checked_mul_int(i128::min_value()), None); + } + + #[test] + fn saturating_mul_int_works() { + let a = $name::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()); + + // Min - 1. + assert_eq!(a.saturating_mul_int((i128::min_value() + 1) / 2), i128::min_value() + 2); + // Min. + assert_eq!(a.saturating_mul_int(i128::min_value() / 2), i128::min_value()); + // Min + 1 => saturates to min. + assert_eq!(a.saturating_mul_int(i128::min_value() / 2 - 1), i128::min_value()); + + let a = $name::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); + assert_eq!(a.saturating_mul_int(i128::min_value()), i128::min_value() / 2); + + let b = $name::saturating_from_rational(1, -2); + assert_eq!(b.saturating_mul_int(42i32), -21); + assert_eq!(b.saturating_mul_int(i128::max_value()), i128::max_value() / -2); + assert_eq!(b.saturating_mul_int(i128::min_value()), i128::min_value() / -2); + assert_eq!(b.saturating_mul_int(u128::max_value()), u128::min_value()); + + let c = $name::saturating_from_integer(255); + assert_eq!(c.saturating_mul_int(2i8), i8::max_value()); + assert_eq!(c.saturating_mul_int(-2i8), i8::min_value()); + assert_eq!(c.saturating_mul_int(i128::max_value()), i128::max_value()); + assert_eq!(c.saturating_mul_int(i128::min_value()), i128::min_value()); + } + + #[test] + fn checked_mul_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + + let a = $name::saturating_from_integer(2); + + // Max - 1. + let b = $name::from_inner(inner_max - 1); + assert_eq!(a.checked_mul(&(b/2.into())), Some(b)); + + // Max. + let c = $name::from_inner(inner_max); + assert_eq!(a.checked_mul(&(c/2.into())), Some(b)); + + // Max + 1 => None. + let e = $name::from_inner(1); + assert_eq!(a.checked_mul(&(c/2.into()+e)), None); + + // Min + 1. + let b = $name::from_inner(inner_min + 1) / 2.into(); + let c = $name::from_inner(inner_min + 2); + assert_eq!(a.checked_mul(&b), Some(c)); + + // Min. + let b = $name::from_inner(inner_min) / 2.into(); + let c = $name::from_inner(inner_min); + assert_eq!(a.checked_mul(&b), Some(c)); + + // Min - 1 => None. + let b = $name::from_inner(inner_min) / 2.into() - $name::from_inner(1); + assert_eq!(a.checked_mul(&b), None); + + let a = $name::saturating_from_rational(1, 2); + let b = $name::saturating_from_rational(1, -2); + let c = $name::saturating_from_integer(255); + + assert_eq!(a.checked_mul(&42.into()), Some(21.into())); + assert_eq!(b.checked_mul(&42.into()), Some((-21).into())); + assert_eq!(c.checked_mul(&2.into()), Some(510.into())); + + assert_eq!(b.checked_mul(&$name::max_value()), $name::max_value().checked_div(&(-2).into())); + assert_eq!(b.checked_mul(&$name::min_value()), $name::min_value().checked_div(&(-2).into())); + + assert_eq!(c.checked_mul(&$name::max_value()), None); + assert_eq!(c.checked_mul(&$name::min_value()), None); + + assert_eq!(a.checked_mul(&$name::max_value()), $name::max_value().checked_div(&2.into())); + assert_eq!(a.checked_mul(&$name::min_value()), $name::min_value().checked_div(&2.into())); + } + + #[test] + fn checked_div_int_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + let accuracy = $name::accuracy(); + + let a = $name::from_inner(inner_max); + let b = $name::from_inner(inner_min); + let c = $name::zero(); + let d = $name::one(); + let e = $name::saturating_from_integer(6); + let f = $name::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!(a.checked_div_int(-2), Some(-inner_max / (2 * accuracy))); + assert_eq!(a.checked_div_int(inner_max / -accuracy), Some(-1)); + + assert_eq!(b.checked_div_int(i128::min_value()), Some(0)); + assert_eq!(b.checked_div_int(2), Some(inner_min / (2 * accuracy))); + assert_eq!(b.checked_div_int(inner_min / accuracy), Some(1)); + assert_eq!(b.checked_div_int(1i8), None); + + assert_eq!(b.checked_div_int(-2), Some(-(inner_min / (2 * accuracy)))); + assert_eq!(b.checked_div_int(-(inner_min / accuracy)), Some(-1)); + + 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(i128::min_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(i32::min_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] + #[should_panic(expected = "attempt to divide by zero")] + fn saturating_div_int_panics_when_divisor_is_zero() { + let _ = $name::one().saturating_div_int(0); + } + + #[test] + fn saturating_div_int_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + let accuracy = $name::accuracy(); + + let a = $name::saturating_from_integer(5); + assert_eq!(a.saturating_div_int(2), 2); + + let a = $name::saturating_from_integer(5); + assert_eq!(a.saturating_div_int(-2), -2); + + let a = $name::min_value(); + assert_eq!(a.saturating_div_int(-1i128), (inner_max / accuracy) as i128); + + let a = $name::min_value(); + assert_eq!(a.saturating_div_int(1i128), (inner_min / accuracy) as i128); + } + + #[test] + fn saturating_abs_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + + assert_eq!($name::from_inner(inner_min).saturating_abs(), $name::max_value()); + assert_eq!($name::from_inner(inner_max).saturating_abs(), $name::max_value()); + assert_eq!($name::zero().saturating_abs(), 0.into()); + assert_eq!($name::saturating_from_rational(-1, 2).saturating_abs(), (1, 2).into()); + } + + #[test] + fn saturating_mul_acc_int_works() { + assert_eq!($name::zero().saturating_mul_acc_int(42i8), 42i8); + assert_eq!($name::one().saturating_mul_acc_int(42i8), 2 * 42i8); + + assert_eq!($name::one().saturating_mul_acc_int(i128::max_value()), i128::max_value()); + assert_eq!($name::one().saturating_mul_acc_int(i128::min_value()), i128::min_value()); + + assert_eq!($name::one().saturating_mul_acc_int(u128::max_value() / 2), u128::max_value() - 1); + assert_eq!($name::one().saturating_mul_acc_int(u128::min_value()), u128::min_value()); + + let a = $name::saturating_from_rational(-1, 2); + assert_eq!(a.saturating_mul_acc_int(42i8), 21i8); + assert_eq!(a.saturating_mul_acc_int(u128::max_value() - 1), u128::max_value() - 1); + } + + #[test] + fn checked_div_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + + let a = $name::from_inner(inner_max); + let b = $name::from_inner(inner_min); + let c = $name::zero(); + let d = $name::one(); + let e = $name::saturating_from_integer(6); + let f = $name::saturating_from_integer(5); + + assert_eq!(e.checked_div(&2.into()), Some(3.into())); + assert_eq!(f.checked_div(&2.into()), Some((5, 2).into())); + + assert_eq!(a.checked_div(&inner_max.into()), Some(1.into())); + assert_eq!(a.checked_div(&2.into()), Some($name::from_inner(inner_max / 2))); + assert_eq!(a.checked_div(&$name::max_value()), Some(1.into())); + assert_eq!(a.checked_div(&d), Some(a)); + + assert_eq!(a.checked_div(&(-2).into()), Some($name::from_inner(-inner_max / 2))); + assert_eq!(a.checked_div(&-$name::max_value()), Some((-1).into())); + + assert_eq!(b.checked_div(&b), Some($name::one())); + assert_eq!(b.checked_div(&2.into()), Some($name::from_inner(inner_min / 2))); + + assert_eq!(b.checked_div(&(-2).into()), Some($name::from_inner(inner_min / -2))); + assert_eq!(b.checked_div(&a), Some((-1).into())); + + assert_eq!(c.checked_div(&1.into()), Some(0.into())); + assert_eq!(c.checked_div(&$name::max_value()), Some(0.into())); + assert_eq!(c.checked_div(&$name::min_value()), Some(0.into())); + + assert_eq!(d.checked_div(&1.into()), Some(1.into())); + + assert_eq!(a.checked_div(&$name::one()), Some(a)); + assert_eq!(b.checked_div(&$name::one()), Some(b)); + assert_eq!(c.checked_div(&$name::one()), Some(c)); + assert_eq!(d.checked_div(&$name::one()), Some(d)); + + assert_eq!(a.checked_div(&$name::zero()), None); + assert_eq!(b.checked_div(&$name::zero()), None); + assert_eq!(c.checked_div(&$name::zero()), None); + assert_eq!(d.checked_div(&$name::zero()), None); + } + + #[test] + fn trunc_works() { + let n = $name::saturating_from_rational(5, 2).trunc(); + assert_eq!(n, $name::saturating_from_integer(2)); + + let n = $name::saturating_from_rational(-5, 2).trunc(); + assert_eq!(n, $name::saturating_from_integer(-2)); + } + + #[test] + fn frac_works() { + let n = $name::saturating_from_rational(5, 2); + let i = n.trunc(); + let f = n.frac(); + + assert_eq!(n, i + f); + + let n = $name::saturating_from_rational(-5, 2); + let i = n.trunc(); + let f = n.frac(); + + assert_eq!(n, i - f); + + let n = $name::saturating_from_rational(5, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 5.into()); + + let n = $name::saturating_from_rational(1, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 5.into()); + + // The sign is attached to the integer part unless it is zero. + let n = $name::saturating_from_rational(-5, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 5.into()); + + let n = $name::saturating_from_rational(-1, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, (-5).into()); + } + + #[test] + fn ceil_works() { + let n = $name::saturating_from_rational(5, 2); + assert_eq!(n.ceil(), 3.into()); + + let n = $name::saturating_from_rational(-5, 2); + assert_eq!(n.ceil(), (-2).into()); + + // On the limits: + let n = $name::max_value(); + assert_eq!(n.ceil(), n.trunc()); + + let n = $name::min_value(); + assert_eq!(n.ceil(), n.trunc()); + } + + #[test] + fn floor_works() { + let n = $name::saturating_from_rational(5, 2); + assert_eq!(n.floor(), 2.into()); + + let n = $name::saturating_from_rational(-5, 2); + assert_eq!(n.floor(), (-3).into()); + + // On the limits: + let n = $name::max_value(); + assert_eq!(n.floor(), n.trunc()); + + let n = $name::min_value(); + assert_eq!(n.floor(), n.trunc()); + } + + #[test] + fn round_works() { + let n = $name::zero(); + assert_eq!(n.round(), n); + + let n = $name::one(); + assert_eq!(n.round(), n); + + let n = $name::saturating_from_rational(5, 2); + assert_eq!(n.round(), 3.into()); + + let n = $name::saturating_from_rational(-5, 2); + assert_eq!(n.round(), (-3).into()); + + // Saturating: + let n = $name::max_value(); + assert_eq!(n.round(), n.trunc()); + + let n = $name::min_value(); + assert_eq!(n.round(), n.trunc()); + + // On the limit: + + // floor(max - 1) + 0.33.. + let n = $name::max_value() + .saturating_sub(1.into()) + .trunc() + .saturating_add((1, 3).into()); + + assert_eq!(n.round(), ($name::max_value() - 1.into()).trunc()); + + // floor(min + 1) - 0.33.. + let n = $name::min_value() + .saturating_add(1.into()) + .trunc() + .saturating_sub((1, 3).into()); + + assert_eq!(n.round(), ($name::min_value() + 1.into()).trunc()); + + // floor(max - 1) + 0.5 + let n = $name::max_value() + .saturating_sub(1.into()) + .trunc() + .saturating_add((1, 2).into()); + + assert_eq!(n.round(), $name::max_value().trunc()); + + // floor(min + 1) - 0.5 + let n = $name::min_value() + .saturating_add(1.into()) + .trunc() + .saturating_sub((1, 2).into()); + + assert_eq!(n.round(), $name::min_value().trunc()); + } + + #[test] + fn perthing_into_works() { + let ten_percent_percent: $name = Percent::from_percent(10).into(); + assert_eq!(ten_percent_percent.into_inner(), $name::accuracy() / 10); + + let ten_percent_permill: $name = Permill::from_percent(10).into(); + assert_eq!(ten_percent_permill.into_inner(), $name::accuracy() / 10); + + let ten_percent_perbill: $name = Perbill::from_percent(10).into(); + assert_eq!(ten_percent_perbill.into_inner(), $name::accuracy() / 10); + + let ten_percent_perquintill: $name = Perquintill::from_percent(10).into(); + assert_eq!(ten_percent_perquintill.into_inner(), $name::accuracy() / 10); + } + + #[test] + fn fmt_should_work() { + let zero = $name::zero(); + assert_eq!(format!("{:?}", zero), format!("{}(0.{:0>weight$})", stringify!($name), 0, weight=precision())); + + let one = $name::one(); + assert_eq!(format!("{:?}", one), format!("{}(1.{:0>weight$})", stringify!($name), 0, weight=precision())); + + let neg = -$name::one(); + assert_eq!(format!("{:?}", neg), format!("{}(-1.{:0>weight$})", stringify!($name), 0, weight=precision())); + + let frac = $name::saturating_from_rational(1, 2); + assert_eq!(format!("{:?}", frac), format!("{}(0.{:0 Date: Thu, 28 May 2020 14:33:10 +0200 Subject: [PATCH 2/3] Bring Substrate's commit "Enable fixed point u128 (#6214)" --- bin/node/executor/tests/basic.rs | 4 +- bin/node/executor/tests/fees.rs | 4 +- bin/node/runtime/src/impls.rs | 166 ++--- bin/node/runtime/src/lib.rs | 4 +- frame/balances/src/tests.rs | 4 +- frame/transaction-payment/src/lib.rs | 18 +- primitives/arithmetic/fuzzer/Cargo.toml | 4 +- .../fuzzer/src/{fixed.rs => fixed_point.rs} | 36 +- .../src/{fixed.rs => fixed_point.rs} | 567 +++++++++-------- primitives/arithmetic/src/lib.rs | 91 ++- primitives/arithmetic/src/per_things.rs | 572 +++++++++++++----- primitives/arithmetic/src/traits.rs | 26 +- primitives/runtime/src/lib.rs | 5 +- 13 files changed, 986 insertions(+), 515 deletions(-) rename primitives/arithmetic/fuzzer/src/{fixed.rs => fixed_point.rs} (62%) rename primitives/arithmetic/src/{fixed.rs => fixed_point.rs} (74%) diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index 346d06e6b1..731b54b7d3 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -24,7 +24,7 @@ use sp_core::{ NeverNativeValue, map, traits::Externalities, storage::{well_known_keys, Storage}, }; use sp_runtime::{ - ApplyExtrinsicResult, Fixed128, FixedPointNumber, + ApplyExtrinsicResult, FixedI128, FixedPointNumber, traits::{Hash as HashT, Convert, BlakeTwo256}, transaction_validity::InvalidTransaction, }; @@ -53,7 +53,7 @@ pub const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY; /// Default transfer fee // NOTE: Transfer fee increased by 1 byte * TransactionByteFee as we include Option in SignedExtra. // Option always takes up one byte in extrinsic payload -fn transfer_fee(extrinsic: &E, fee_multiplier: Fixed128) -> Balance { +fn transfer_fee(extrinsic: &E, fee_multiplier: FixedI128) -> Balance { let length_fee = TransactionByteFee::get() * (extrinsic.encode().len() as Balance); let weight = default_transfer_call().get_dispatch_info().weight; diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index 010bb2db11..e73f12832d 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -21,7 +21,7 @@ use frame_support::{ weights::GetDispatchInfo, }; use sp_core::{NeverNativeValue, map, storage::Storage}; -use sp_runtime::{FixedPointNumber, Fixed128, Perbill, traits::{Convert, BlakeTwo256}}; +use sp_runtime::{FixedPointNumber, FixedI128, Perbill, traits::{Convert, BlakeTwo256}}; use node_runtime::{ CheckedExtrinsic, Call, Runtime, Balances, TransactionPayment, TransactionBaseFee, TransactionByteFee, WeightFeeCoefficient, @@ -39,7 +39,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { let mut t = new_test_ext(COMPACT_CODE, false); // initial fee multiplier must be zero - let mut prev_multiplier = Fixed128::from_inner(0); + let mut prev_multiplier = FixedI128::from_inner(0); t.execute_with(|| { assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier); diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 5e41e16aee..556476e362 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -18,8 +18,9 @@ use node_primitives::Balance; use sp_runtime::traits::{Convert, Saturating}; -use sp_runtime::{FixedPointNumber, Fixed128, Perbill}; +use sp_runtime::{FixedPointNumber, Perquintill}; use frame_support::{traits::{OnUnbalanced, Currency, Get}, weights::Weight}; +use pallet_transaction_payment::Multiplier; use crate::{Balances, System, Authorship, MaximumBlockWeight, NegativeImbalance}; pub struct Author; @@ -68,28 +69,26 @@ impl> Convert for LinearWeightToFee { /// https://research.web3.foundation/en/latest/polkadot/Token%20Economics/#relay-chain-transaction-fees pub struct TargetedFeeAdjustment(sp_std::marker::PhantomData); -impl> Convert for TargetedFeeAdjustment { - fn convert(multiplier: Fixed128) -> Fixed128 { - let block_weight = System::all_extrinsics_weight(); +impl> Convert for TargetedFeeAdjustment { + fn convert(multiplier: Multiplier) -> Multiplier { let max_weight = MaximumBlockWeight::get(); - let target_weight = (T::get() * max_weight) as u128; + let block_weight = System::all_extrinsics_weight(); + let target_weight = (T::get() * max_weight as u64) as u128; let block_weight = block_weight as u128; // determines if the first_term is positive let positive = block_weight >= target_weight; let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight); // safe, diff_abs cannot exceed u64. - let diff = Fixed128::saturating_from_rational(diff_abs, max_weight.max(1)); + let diff = Multiplier::saturating_from_rational(diff_abs, max_weight.max(1)); let diff_squared = diff.saturating_mul(diff); // 0.00004 = 4/100_000 = 40_000/10^9 - let v = Fixed128::saturating_from_rational(4, 100_000); + let v = Multiplier::saturating_from_rational(4, 100_000); // 0.00004^2 = 16/10^10 Taking the future /2 into account... 8/10^10 - let v_squared_2 = Fixed128::saturating_from_rational(8, 10_000_000_000u64); + let v_squared_2 = Multiplier::saturating_from_rational(8, 10_000_000_000u64); let first_term = v.saturating_mul(diff); - // It is very unlikely that this will exist (in our poor perbill estimate) but we are giving - // it a shot. let second_term = v_squared_2.saturating_mul(diff_squared); if positive { @@ -98,15 +97,15 @@ impl> Convert for TargetedFeeAdjustment { let excess = first_term.saturating_add(second_term); multiplier.saturating_add(excess) } else { - // Proof: first_term > second_term. Safe subtraction. - let negative = first_term - second_term; + // Defensive-only: first_term > second_term. Safe subtraction. + let negative = first_term.saturating_sub(second_term); multiplier.saturating_sub(negative) // despite the fact that apply_to saturates weight (final fee cannot go below 0) // it is crucially important to stop here and don't further reduce the weight fee // multiplier. While at -1, it means that the network is so un-congested that all // transactions have no weight fee. We stop here and only increase if the network // became more busy. - .max(Fixed128::saturating_from_integer(-1)) + .max(Multiplier::saturating_from_integer(-1)) } } } @@ -124,30 +123,27 @@ mod tests { } fn target() -> Weight { - TargetBlockFullness::get() * max() + (TargetBlockFullness::get() * (max() as u64)) as Weight } // poc reference implementation. - fn fee_multiplier_update(block_weight: Weight, previous: Fixed128) -> Fixed128 { - let block_weight = block_weight as f64; - let v: f64 = 0.00004; - + fn fee_multiplier_update(block_weight: Weight, previous: Multiplier) -> Multiplier { // maximum tx weight let m = max() as f64; + // block weight always truncated to max weight + let block_weight = (block_weight as f64).min(m); + let v: f64 = 0.00004; + // Ideal saturation in terms of weight let ss = target() as f64; // Current saturation in terms of weight let s = block_weight; let fm = v * (s/m - ss/m) + v.powi(2) * (s/m - ss/m).powi(2) / 2.0; - let addition_fm = Fixed128::from_inner((fm * Fixed128::accuracy() as f64).round() as i128); + let addition_fm = Multiplier::from_inner((fm * Multiplier::accuracy() as f64).round() as i128); previous.saturating_add(addition_fm) } - fn feemul(parts: i128) -> Fixed128 { - Fixed128::from_inner(parts) - } - fn run_with_system_weight(w: Weight, assertions: F) where F: Fn() -> () { let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default().build_storage::().unwrap().into(); @@ -159,7 +155,7 @@ mod tests { #[test] fn fee_multiplier_update_poc_works() { - let fm = Fixed128::saturating_from_rational(0, 1); + let fm = Multiplier::saturating_from_rational(0, 1); let test_set = vec![ (0, fm.clone()), (100, fm.clone()), @@ -173,7 +169,7 @@ mod tests { fee_multiplier_update(w, fm), TargetedFeeAdjustment::::convert(fm), // Error is only 1 in 10^18 - Fixed128::from_inner(1), + Multiplier::from_inner(1), ); }) }) @@ -184,12 +180,12 @@ mod tests { // just a few txs per_block. let block_weight = 0; run_with_system_weight(block_weight, || { - let mut fm = Fixed128::default(); + let mut fm = Multiplier::default(); let mut iterations: u64 = 0; loop { let next = TargetedFeeAdjustment::::convert(fm); fm = next; - if fm == Fixed128::saturating_from_integer(-1) { break; } + if fm == Multiplier::saturating_from_integer(-1) { break; } iterations += 1; } println!("iteration {}, new fm = {:?}. Weight fee is now zero", iterations, fm); @@ -217,7 +213,7 @@ mod tests { run_with_system_weight(block_weight, || { // initial value configured on module - let mut fm = Fixed128::default(); + let mut fm = Multiplier::default(); assert_eq!(fm, TransactionPayment::next_fee_multiplier()); let mut iterations: u64 = 0; @@ -245,88 +241,106 @@ mod tests { #[test] fn stateless_weight_mul() { + // This test will show that heavy blocks have a weight multiplier greater than 0 + // and light blocks will have a weight multiplier less than 0. run_with_system_weight(target() / 4, || { - // Light block. Fee is reduced a little. + // `fee_multiplier_update` is enough as it is the absolute truth value. + let next = TargetedFeeAdjustment::::convert(Multiplier::default()); assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::default()), - feemul(-7500), + next, + fee_multiplier_update(target() / 4 ,Multiplier::default()) ); + + // Light block. Fee is reduced a little. + assert!(next < Multiplier::zero()) }); run_with_system_weight(target() / 2, || { - // a bit more. Fee is decreased less, meaning that the fee increases as the block grows. + let next = TargetedFeeAdjustment::::convert(Multiplier::default()); assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::default()), - feemul(-5000), + next, + fee_multiplier_update(target() / 2 ,Multiplier::default()) ); + // Light block. Fee is reduced a little. + assert!(next < Multiplier::zero()) + }); run_with_system_weight(target(), || { // ideal. Original fee. No changes. - assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::default()), - feemul(0), - ); + let next = TargetedFeeAdjustment::::convert(Multiplier::default()); + assert_eq!(next, Multiplier::zero()) }); run_with_system_weight(target() * 2, || { - // // More than ideal. Fee is increased. + // More than ideal. Fee is increased. + let next = TargetedFeeAdjustment::::convert(Multiplier::default()); assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::default()), - feemul(10000), + next, + fee_multiplier_update(target() * 2 ,Multiplier::default()) ); + + // Heavy block. Fee is increased a little. + assert!(next > Multiplier::zero()) }); } #[test] fn stateful_weight_mul_grow_to_infinity() { run_with_system_weight(target() * 2, || { - assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::default()), - feemul(10000) - ); - assert_eq!( - TargetedFeeAdjustment::::convert(feemul(10000)), - feemul(20000) - ); - assert_eq!( - TargetedFeeAdjustment::::convert(feemul(20000)), - feemul(30000) - ); - // ... - assert_eq!( - TargetedFeeAdjustment::::convert(feemul(1_000_000_000)), - feemul(1_000_000_000 + 10000) - ); + let mut original = Multiplier::default(); + let mut next = Multiplier::default(); + + (0..1_000).for_each(|_| { + next = TargetedFeeAdjustment::::convert(original); + assert_eq!( + next, + fee_multiplier_update(target() * 2, original), + ); + // must always increase + assert!(next > original); + original = next; + }); }); } #[test] fn stateful_weight_mil_collapse_to_minus_one() { run_with_system_weight(0, || { + let mut original = Multiplier::default(); // 0 + let mut next; + + // decreases + next = TargetedFeeAdjustment::::convert(original); assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::default()), - feemul(-10000) - ); - assert_eq!( - TargetedFeeAdjustment::::convert(feemul(-10000)), - feemul(-20000) + next, + fee_multiplier_update(0, original), ); + assert!(next < original); + original = next; + + // keeps decreasing + next = TargetedFeeAdjustment::::convert(original); assert_eq!( - TargetedFeeAdjustment::::convert(feemul(-20000)), - feemul(-30000) + next, + fee_multiplier_update(0, original), ); - // ... + assert!(next < original); + + // ... stops going down at -1 assert_eq!( - TargetedFeeAdjustment::::convert(Fixed128::saturating_from_integer(-1)), - Fixed128::saturating_from_integer(-1) + TargetedFeeAdjustment::::convert(Multiplier::saturating_from_integer(-1)), + Multiplier::saturating_from_integer(-1) ); }) } + #[test] + // TODO enable the following test after bringing in "Weights to u64 + Balances Weights (#5446)" from Substrate + #[ignore] fn weight_to_fee_should_not_overflow_on_large_weights() { let kb = 1024 as Weight; let mb = kb * kb; - let max_fm = Fixed128::saturating_from_integer(i128::max_value()); + let max_fm = Multiplier::saturating_from_integer(i128::max_value()); // check that for all values it can compute, correctly. vec![ @@ -339,13 +353,17 @@ mod tests { 100 * kb, mb, 10 * mb, + 2147483647, + 4294967295, + MaximumBlockWeight::get() / 2, + MaximumBlockWeight::get(), Weight::max_value() / 2, Weight::max_value(), ].into_iter().for_each(|i| { run_with_system_weight(i, || { - let next = TargetedFeeAdjustment::::convert(Fixed128::default()); - let truth = fee_multiplier_update(i, Fixed128::default()); - assert_eq_error_rate!(truth, next, Fixed128::from_inner(50_000_000)); + let next = TargetedFeeAdjustment::::convert(Multiplier::default()); + let truth = fee_multiplier_update(i, Multiplier::default()); + assert_eq_error_rate!(truth, next, Multiplier::from_inner(50_000_000)); }); }); @@ -361,4 +379,4 @@ mod tests { }) }); } -} +} \ No newline at end of file diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0c9c8396d9..1acc4a84ab 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -36,7 +36,7 @@ use node_primitives::{Balance, BlockNumber, Hash, Index, Moment}; pub use pallet_generic_asset::AssetInfo; use sp_api::impl_runtime_apis; use sp_runtime::{ - Permill, Perbill, Percent, ApplyExtrinsicResult, + Permill, Perbill, Percent, Perquintill, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str, }; use sp_runtime::curve::PiecewiseLinear; @@ -227,7 +227,7 @@ parameter_types! { // setting this to zero will disable the weight fee. pub const WeightFeeCoefficient: Balance = 1_000; // for a sane configuration, this should always be less than `AvailableBlockRatio`. - pub const TargetBlockFullness: Perbill = Perbill::from_percent(25); + pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); } impl pallet_transaction_payment::Trait for Runtime { diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index e46256c835..1dd590eb16 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -21,7 +21,7 @@ macro_rules! decl_tests { ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { use crate::*; - use sp_runtime::{FixedPointNumber, Fixed128, traits::{SignedExtension, BadOrigin}}; + use sp_runtime::{FixedPointNumber, FixedI128, traits::{SignedExtension, BadOrigin}}; use frame_support::{ assert_noop, assert_ok, assert_err, traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, @@ -127,7 +127,7 @@ macro_rules! decl_tests { .monied(true) .build() .execute_with(|| { - pallet_transaction_payment::NextFeeMultiplier::put(Fixed128::saturating_from_integer(1)); + pallet_transaction_payment::NextFeeMultiplier::put(FixedI128::saturating_from_integer(1)); Balances::set_lock(ID_1, &1, 10, WithdrawReason::Reserve.into()); assert_noop!( >::transfer(&1, &2, 1, AllowDeath), diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index a434173752..d19fbfcfbb 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -39,7 +39,7 @@ use frame_support::{ weights::{Weight, DispatchInfo, GetDispatchInfo}, }; use sp_runtime::{ - Fixed128, FixedPointNumber, + FixedI128, FixedPointNumber, FixedPointOperand, transaction_validity::{ TransactionPriority, ValidTransaction, InvalidTransaction, TransactionValidityError, TransactionValidity, @@ -48,7 +48,9 @@ use sp_runtime::{ }; use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; -type Multiplier = Fixed128; +/// Fee multiplier. +pub type Multiplier = FixedI128; + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = @@ -98,7 +100,9 @@ decl_module! { } } -impl Module { +impl Module where + BalanceOf: FixedPointOperand +{ /// Query the data that we know about the fee of a given `call`. /// /// As this module is not and cannot be aware of the internals of a signed extension, it only @@ -524,7 +528,7 @@ mod tests { .execute_with(|| { // all fees should be x1.5 - NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 2)); + NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2)); let len = 10; assert!( @@ -552,7 +556,7 @@ mod tests { .execute_with(|| { // all fees should be x1.5 - NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 2)); + NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2)); assert_eq!( TransactionPayment::query_info(xt, len), @@ -581,7 +585,7 @@ mod tests { .execute_with(|| { // Next fee multiplier is zero - assert_eq!(NextFeeMultiplier::get(), Fixed128::saturating_from_integer(0)); + assert_eq!(NextFeeMultiplier::get(), Multiplier::saturating_from_integer(0)); // Tip only, no fees works let dispatch_info = DispatchInfo { @@ -621,7 +625,7 @@ mod tests { .execute_with(|| { // Add a next fee multiplier - NextFeeMultiplier::put(Fixed128::saturating_from_rational(1, 2)); // = 1/2 = .5 + NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2)); // = 1/2 = .5 // Base fee is unaffected by multiplier let dispatch_info = DispatchInfo { weight: 0, diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index b5a3918cdd..88b1bfc41c 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -25,5 +25,5 @@ name = "rational128" path = "src/rational128.rs" [[bin]] -name = "fixed" -path = "src/fixed.rs" \ No newline at end of file +name = "fixed_point" +path = "src/fixed_point.rs" diff --git a/primitives/arithmetic/fuzzer/src/fixed.rs b/primitives/arithmetic/fuzzer/src/fixed_point.rs similarity index 62% rename from primitives/arithmetic/fuzzer/src/fixed.rs rename to primitives/arithmetic/fuzzer/src/fixed_point.rs index 115d7dbbdb..9a88197ac3 100644 --- a/primitives/arithmetic/fuzzer/src/fixed.rs +++ b/primitives/arithmetic/fuzzer/src/fixed_point.rs @@ -16,19 +16,19 @@ // limitations under the License. //! # Running -//! Running this fuzzer can be done with `cargo hfuzz run fixed`. `honggfuzz` CLI options can +//! Running this fuzzer can be done with `cargo hfuzz run fixed_point`. `honggfuzz` CLI options can //! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. //! //! # Debugging a panic //! Once a panic is found, it can be debugged with -//! `cargo hfuzz run-debug fixed hfuzz_workspace/fixed/*.fuzz`. +//! `cargo hfuzz run-debug fixed_point hfuzz_workspace/fixed_point/*.fuzz`. //! //! # More information //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). use honggfuzz::fuzz; -use sp_arithmetic::{FixedPointNumber, Fixed64, traits::Saturating}; +use sp_arithmetic::{FixedPointNumber, FixedI64, traits::Saturating}; fn main() { loop { @@ -38,21 +38,21 @@ fn main() { // Check `from_rational` and division are consistent. if y != 0 { - let f1 = Fixed64::saturating_from_integer(x) / Fixed64::saturating_from_integer(y); - let f2 = Fixed64::saturating_from_rational(x, y); + let f1 = FixedI64::saturating_from_integer(x) / FixedI64::saturating_from_integer(y); + let f2 = FixedI64::saturating_from_rational(x, y); assert_eq!(f1.into_inner(), f2.into_inner()); } // Check `saturating_mul`. - let a = Fixed64::saturating_from_rational(2, 5); - let b = a.saturating_mul(Fixed64::saturating_from_integer(x)); + let a = FixedI64::saturating_from_rational(2, 5); + let b = a.saturating_mul(FixedI64::saturating_from_integer(x)); let n = b.into_inner() as i128; - let m = 2i128 * x * Fixed64::accuracy() as i128 / 5i128; + let m = 2i128 * x * FixedI64::accuracy() as i128 / 5i128; assert_eq!(n, m); // Check `saturating_mul` and division are inverse. if x != 0 { - assert_eq!(a, b / Fixed64::saturating_from_integer(x)); + assert_eq!(a, b / FixedI64::saturating_from_integer(x)); } // Check `reciprocal`. @@ -60,22 +60,22 @@ fn main() { assert_eq!(a, r); // Check addition. - let a = Fixed64::saturating_from_integer(x); - let b = Fixed64::saturating_from_integer(y); - let c = Fixed64::saturating_from_integer(x.saturating_add(y)); + let a = FixedI64::saturating_from_integer(x); + let b = FixedI64::saturating_from_integer(y); + let c = FixedI64::saturating_from_integer(x.saturating_add(y)); assert_eq!(a.saturating_add(b), c); // Check substraction. - let a = Fixed64::saturating_from_integer(x); - let b = Fixed64::saturating_from_integer(y); - let c = Fixed64::saturating_from_integer(x.saturating_sub(y)); + let a = FixedI64::saturating_from_integer(x); + let b = FixedI64::saturating_from_integer(y); + let c = FixedI64::saturating_from_integer(x.saturating_sub(y)); assert_eq!(a.saturating_sub(b), c); // Check `saturating_mul_acc_int`. - let a = Fixed64::saturating_from_rational(2, 5); + let a = FixedI64::saturating_from_rational(2, 5); let b = a.saturating_mul_acc_int(x); - let xx = Fixed64::saturating_from_integer(x); - let d = a.saturating_mul(xx).saturating_add(xx).into_inner() as i128 / Fixed64::accuracy() as i128; + let xx = FixedI64::saturating_from_integer(x); + let d = a.saturating_mul(xx).saturating_add(xx).into_inner() as i128 / FixedI64::accuracy() as i128; assert_eq!(b, d); }); } diff --git a/primitives/arithmetic/src/fixed.rs b/primitives/arithmetic/src/fixed_point.rs similarity index 74% rename from primitives/arithmetic/src/fixed.rs rename to primitives/arithmetic/src/fixed_point.rs index 1e3ebe2e25..b5cfad4642 100644 --- a/primitives/arithmetic/src/fixed.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -22,7 +22,7 @@ use crate::{ helpers_128bit::multiply_by_rational, PerThing, traits::{ SaturatedConversion, CheckedSub, CheckedAdd, CheckedMul, CheckedDiv, CheckedNeg, - Bounded, Saturating, UniqueSaturatedInto, Zero, One, Signed + Bounded, Saturating, UniqueSaturatedInto, Zero, One }, }; @@ -59,11 +59,14 @@ pub trait FixedPointNumber: + Add + Sub + Div + Mul { /// The underlying data type used for this fixed point number. - type Inner: Debug + One + CheckedMul + CheckedDiv + CheckedNeg + Signed + FixedPointOperand; + type Inner: Debug + One + CheckedMul + CheckedDiv + FixedPointOperand; /// Precision of this fixed point implementation. It should be a power of `10`. const DIV: Self::Inner; + /// Indicates if this fixed point implementation is signed or not. + const SIGNED: bool; + /// Precision of this fixed point implementation. fn accuracy() -> Self::Inner { Self::DIV @@ -78,8 +81,10 @@ pub trait FixedPointNumber: /// Creates self from an integer number `int`. /// /// Returns `Self::max` or `Self::min` if `int` exceeds accuracy. - fn saturating_from_integer>(int: N) -> Self { - Self::from_inner(int.unique_saturated_into().saturating_mul(Self::DIV)) + fn saturating_from_integer(int: N) -> Self { + let mut n: I129 = int.into(); + n.value = n.value.saturating_mul(Self::DIV.saturated_into()); + Self::from_inner(from_i129(n).unwrap_or(to_bound(int, 0))) } /// Creates `self` from an integer number `int`. @@ -163,7 +168,11 @@ pub trait FixedPointNumber: /// /// Returns `N::min` or `N::max` if the multiplication or final result does not fit in `N`. fn saturating_mul_acc_int(self, n: N) -> N { - self.saturating_mul_int(n).saturating_add(n) + if self.is_negative() && n > N::zero() { + n.saturating_sub(Self::zero().saturating_sub(self).saturating_mul_int(n)) + } else { + self.saturating_mul_int(n).saturating_add(n) + } } /// Saturating absolute value. @@ -171,7 +180,7 @@ pub trait FixedPointNumber: /// Returns `Self::max` if `self == Self::min`. fn saturating_abs(self) -> Self { let inner = self.into_inner(); - if inner.is_positive() { + if inner >= Self::Inner::zero() { self } else { Self::from_inner(inner.checked_neg().unwrap_or(Self::Inner::max_value())) @@ -245,7 +254,11 @@ pub trait FixedPointNumber: if self.is_negative() { self.trunc() } else { - self.saturating_add(Self::one()).trunc() + if self.frac() == Self::zero() { + self + } else { + self.saturating_add(Self::one()).trunc() + } } } @@ -268,8 +281,11 @@ pub trait FixedPointNumber: if n < Self::saturating_from_integer(5) { self.trunc() } else { - let extra = Self::saturating_from_integer(self.into_inner().signum()); - (self.saturating_add(extra)).trunc() + if self.is_positive() { + self.saturating_add(Self::one()).trunc() + } else { + self.saturating_sub(Self::one()).trunc() + } } } } @@ -319,6 +335,7 @@ macro_rules! implement_fixed { $name:ident, $test_mod:ident, $inner_type:ty, + $signed:tt, $div:tt, $title:expr $(,)? ) => { @@ -344,6 +361,7 @@ macro_rules! implement_fixed { type Inner = $inner_type; const DIV: Self::Inner = $div; + const SIGNED: bool = $signed; fn from_inner(inner: Self::Inner) -> Self { Self(inner) @@ -372,7 +390,7 @@ macro_rules! implement_fixed { type Output = Self; fn neg(self) -> Self::Output { - Self(-self.0) + Self(::Inner::zero() - self.0) } } @@ -472,7 +490,7 @@ macro_rules! implement_fixed { format!("{}{}", signum_for_zero, int) }; let precision = (Self::accuracy() as f64).log10() as usize; - let fractional = format!("{:0>weight$}", (self.0 % Self::accuracy()).abs(), weight=precision); + let fractional = format!("{:0>weight$}", ((self.0 % Self::accuracy()) as i128).abs(), weight=precision); write!(f, "{}({}.{})", stringify!($name), integral, fractional) } @@ -649,35 +667,38 @@ macro_rules! implement_fixed { #[test] fn op_neg_works() { - let a = $name::saturating_from_integer(5); + let a = $name::zero(); let b = -a; - // Positive. - assert_eq!($name::saturating_from_integer(-5), b); + // Zero. + assert_eq!(a, b); - let a = $name::saturating_from_integer(-5); - let b = -a; + if $name::SIGNED { + let a = $name::saturating_from_integer(5); + let b = -a; - // Negative - assert_eq!($name::saturating_from_integer(5), b); + // Positive. + assert_eq!($name::saturating_from_integer(-5), b); - let a = $name::max_value(); - let b = -a; + let a = $name::saturating_from_integer(-5); + let b = -a; - // Max. - assert_eq!($name::min_value() + $name::from_inner(1), b); + // Negative + assert_eq!($name::saturating_from_integer(5), b); - let a = $name::min_value() + $name::from_inner(1); - let b = -a; + let a = $name::max_value(); + let b = -a; - // Min. - assert_eq!($name::max_value(), b); + // Max. + assert_eq!($name::min_value() + $name::from_inner(1), b); - let a = $name::zero(); - let b = -a; + let a = $name::min_value() + $name::from_inner(1); + let b = -a; - // Zero. - assert_eq!(a, b); + // Min. + assert_eq!($name::max_value(), b); + + } } #[test] @@ -696,10 +717,11 @@ macro_rules! implement_fixed { // Positive case: 6/2 = 3. assert_eq!($name::saturating_from_integer(3), a + b); - let b = $name::saturating_from_rational(1, -2); - - // Negative case: 4/2 = 2. - assert_eq!($name::saturating_from_integer(2), a + b); + if $name::SIGNED { + // Negative case: 4/2 = 2. + let b = $name::saturating_from_rational(1, -2); + assert_eq!($name::saturating_from_integer(2), a + b); + } } #[test] @@ -715,13 +737,8 @@ macro_rules! implement_fixed { let a = $name::saturating_from_rational(5, 2); let b = $name::saturating_from_rational(1, 2); - // Negative case: 4/2 = 2. assert_eq!($name::saturating_from_integer(2), a - b); - - let b = $name::saturating_from_rational(1, -2); - - // Positive case: 6/2 = 3. - assert_eq!($name::saturating_from_integer(3), a - b); + assert_eq!($name::saturating_from_integer(-2), b.saturating_sub(a)); } #[test] @@ -752,11 +769,12 @@ macro_rules! implement_fixed { } #[test] - #[should_panic(expected = "attempt to divide with overflow")] - fn op_div_panics_on_overflow() { - let a = $name::min_value(); - let b = (-1).into(); - let _c = a / b; + fn op_checked_div_overflow_works() { + if $name::SIGNED { + let a = $name::min_value(); + let b = $name::zero().saturating_sub($name::one()); + assert!(a.checked_div(&b).is_none()); + } } #[test] @@ -765,13 +783,15 @@ macro_rules! implement_fixed { let b = $name::saturating_from_integer(2); assert_eq!($name::saturating_from_integer(21), a / b); - let a = $name::saturating_from_integer(42); - let b = $name::saturating_from_integer(-2); - assert_eq!($name::saturating_from_integer(-21), a / b); + if $name::SIGNED { + let a = $name::saturating_from_integer(42); + let b = $name::saturating_from_integer(-2); + assert_eq!($name::saturating_from_integer(-21), a / b); + } } #[test] - fn from_integer_works() { + fn saturating_from_integer_works() { let inner_max = <$name as FixedPointNumber>::Inner::max_value(); let inner_min = <$name as FixedPointNumber>::Inner::min_value(); let accuracy = $name::accuracy(); @@ -781,7 +801,7 @@ macro_rules! implement_fixed { assert_eq!(a.into_inner(), 42 * accuracy); let a = $name::saturating_from_integer(-42); - assert_eq!(a.into_inner(), -42 * accuracy); + assert_eq!(a.into_inner(), 0.saturating_sub(42 * accuracy)); // Max/min integers that fit. let a = $name::saturating_from_integer(inner_max / accuracy); @@ -794,7 +814,7 @@ macro_rules! implement_fixed { let a = $name::saturating_from_integer(inner_max / accuracy + 1); assert_eq!(a.into_inner(), inner_max); - let a = $name::saturating_from_integer(inner_min / accuracy - 1); + let a = $name::saturating_from_integer((inner_min / accuracy).saturating_sub(1)); assert_eq!(a.into_inner(), inner_min); } @@ -804,30 +824,35 @@ macro_rules! implement_fixed { let inner_min = <$name as FixedPointNumber>::Inner::min_value(); let accuracy = $name::accuracy(); - // Cases where integer fits. + // Case where integer fits. let a = $name::checked_from_integer(42) .expect("42 * accuracy <= inner_max; qed"); assert_eq!(a.into_inner(), 42 * accuracy); - let a = $name::checked_from_integer(-42) - .expect("-42 * accuracy >= inner_min; qed"); - assert_eq!(a.into_inner(), -42 * accuracy); - - // Max/min integers that fit. + // Max integer that fit. let a = $name::checked_from_integer(inner_max / accuracy) .expect("(inner_max / accuracy) * accuracy <= inner_max; qed"); assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); - let a = $name::checked_from_integer(inner_min / accuracy) - .expect("(inner_min / accuracy) * accuracy <= inner_min; qed"); - assert_eq!(a.into_inner(), (inner_min / accuracy) * accuracy); - - // Cases where integer doesn't fit, so it returns `None`. + // Case where integer doesn't fit, so it returns `None`. let a = $name::checked_from_integer(inner_max / accuracy + 1); assert_eq!(a, None); - let a = $name::checked_from_integer(inner_min / accuracy - 1); - assert_eq!(a, None); + if $name::SIGNED { + // Case where integer fits. + let a = $name::checked_from_integer(0.saturating_sub(42)) + .expect("-42 * accuracy >= inner_min; qed"); + assert_eq!(a.into_inner(), 0 - 42 * accuracy); + + // Min integer that fit. + let a = $name::checked_from_integer(inner_min / accuracy) + .expect("(inner_min / accuracy) * accuracy <= inner_min; qed"); + assert_eq!(a.into_inner(), (inner_min / accuracy) * accuracy); + + // Case where integer doesn't fit, so it returns `None`. + let a = $name::checked_from_integer(inner_min / accuracy - 1); + assert_eq!(a, None); + } } #[test] @@ -856,21 +881,6 @@ macro_rules! implement_fixed { // Positive case: 2.5 assert_eq!(a.into_inner(), 25 * accuracy / 10); - let a = $name::saturating_from_rational(-5, 2); - - // Negative case: -2.5 - assert_eq!(a.into_inner(), -25 * accuracy / 10); - - let a = $name::saturating_from_rational(5, -2); - - // Other negative case: -2.5 - assert_eq!(a.into_inner(), -25 * accuracy / 10); - - let a = $name::saturating_from_rational(-5, -2); - - // Other positive case: 2.5 - assert_eq!(a.into_inner(), 25 * accuracy / 10); - // Max - 1. let a = $name::saturating_from_rational(inner_max - 1, accuracy); assert_eq!(a.into_inner(), inner_max - 1); @@ -887,26 +897,68 @@ macro_rules! implement_fixed { let a = $name::saturating_from_rational(inner_min, accuracy); assert_eq!(a.into_inner(), inner_min); - // Max + 1, saturates. - let a = $name::saturating_from_rational(inner_max as u128 + 1, accuracy); - assert_eq!(a.into_inner(), inner_max); - - // Min - 1, saturates. - let a = $name::saturating_from_rational(inner_max as u128 + 2, -accuracy); - assert_eq!(a.into_inner(), inner_min); - // Zero. let a = $name::saturating_from_rational(0, 1); assert_eq!(a.into_inner(), 0); - let a = $name::saturating_from_rational(inner_max, -accuracy); - assert_eq!(a.into_inner(), -inner_max); + if $name::SIGNED { + // Negative case: -2.5 + let a = $name::saturating_from_rational(-5, 2); + assert_eq!(a.into_inner(), 0 - 25 * accuracy / 10); - let a = $name::saturating_from_rational(inner_min, -accuracy); - assert_eq!(a.into_inner(), inner_max); + // Other negative case: -2.5 + let a = $name::saturating_from_rational(5, -2); + assert_eq!(a.into_inner(), 0 - 25 * accuracy / 10); - let a = $name::saturating_from_rational(inner_min + 1, -accuracy); - assert_eq!(a.into_inner(), inner_max); + // Other positive case: 2.5 + let a = $name::saturating_from_rational(-5, -2); + assert_eq!(a.into_inner(), 25 * accuracy / 10); + + // Max + 1, saturates. + let a = $name::saturating_from_rational(inner_max as u128 + 1, accuracy); + assert_eq!(a.into_inner(), inner_max); + + // Min - 1, saturates. + let a = $name::saturating_from_rational(inner_max as u128 + 2, 0 - accuracy); + assert_eq!(a.into_inner(), inner_min); + + let a = $name::saturating_from_rational(inner_max, 0 - accuracy); + assert_eq!(a.into_inner(), 0 - inner_max); + + let a = $name::saturating_from_rational(inner_min, 0 - accuracy); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_min + 1, 0 - accuracy); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_min, 0 - 1); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(inner_max, 0 - 1); + assert_eq!(a.into_inner(), inner_min); + + let a = $name::saturating_from_rational(inner_max, 0 - inner_max); + assert_eq!(a.into_inner(), 0 - accuracy); + + let a = $name::saturating_from_rational(0 - inner_max, inner_max); + assert_eq!(a.into_inner(), 0 - accuracy); + + let a = $name::saturating_from_rational(inner_max, 0 - 3 * accuracy); + assert_eq!(a.into_inner(), 0 - inner_max / 3); + + let a = $name::saturating_from_rational(inner_min, 0 - accuracy / 3); + assert_eq!(a.into_inner(), inner_max); + + let a = $name::saturating_from_rational(1, 0 - accuracy); + assert_eq!(a.into_inner(), 0.saturating_sub(1)); + + let a = $name::saturating_from_rational(inner_min, inner_min); + assert_eq!(a.into_inner(), accuracy); + + // Out of accuracy. + let a = $name::saturating_from_rational(1, 0 - accuracy - 1); + assert_eq!(a.into_inner(), 0); + } let a = $name::saturating_from_rational(inner_max - 1, accuracy); assert_eq!(a.into_inner(), inner_max - 1); @@ -920,51 +972,24 @@ macro_rules! implement_fixed { let a = $name::saturating_from_rational(inner_min, 1); assert_eq!(a.into_inner(), inner_min); - let a = $name::saturating_from_rational(inner_min, -1); - assert_eq!(a.into_inner(), inner_max); - - let a = $name::saturating_from_rational(inner_max, -1); - assert_eq!(a.into_inner(), inner_min); - let a = $name::saturating_from_rational(inner_max, inner_max); assert_eq!(a.into_inner(), accuracy); - let a = $name::saturating_from_rational(inner_min, inner_min); - assert_eq!(a.into_inner(), accuracy); - - let a = $name::saturating_from_rational(inner_max, -inner_max); - assert_eq!(a.into_inner(), -accuracy); - - let a = $name::saturating_from_rational(-inner_max, inner_max); - assert_eq!(a.into_inner(), -accuracy); - let a = $name::saturating_from_rational(inner_max, 3 * accuracy); assert_eq!(a.into_inner(), inner_max / 3); - let a = $name::saturating_from_rational(inner_max, -3 * accuracy); - assert_eq!(a.into_inner(), -inner_max / 3); - let a = $name::saturating_from_rational(inner_min, 2 * accuracy); assert_eq!(a.into_inner(), inner_min / 2); - let a = $name::saturating_from_rational(inner_min, accuracy / -3); - assert_eq!(a.into_inner(), inner_max); - let a = $name::saturating_from_rational(inner_min, accuracy / 3); assert_eq!(a.into_inner(), inner_min); let a = $name::saturating_from_rational(1, accuracy); assert_eq!(a.into_inner(), 1); - let a = $name::saturating_from_rational(1, -accuracy); - assert_eq!(a.into_inner(), -1); - // Out of accuracy. let a = $name::saturating_from_rational(1, accuracy + 1); assert_eq!(a.into_inner(), 0); - - let a = $name::saturating_from_rational(1, -accuracy - 1); - assert_eq!(a.into_inner(), 0); } #[test] @@ -994,39 +1019,41 @@ macro_rules! implement_fixed { assert_eq!(a.into_inner(), inner_min); // Max + 1 => Overflow => None. - let a = $name::checked_from_rational(inner_min, -accuracy); + let a = $name::checked_from_rational(inner_min, 0.saturating_sub(accuracy)); assert_eq!(a, None); - // Min - 1 => Underflow => None. - let a = $name::checked_from_rational(inner_max as u128 + 2, -accuracy); - assert_eq!(a, None); + if $name::SIGNED { + // Min - 1 => Underflow => None. + let a = $name::checked_from_rational(inner_max as u128 + 2, 0.saturating_sub(accuracy)); + assert_eq!(a, None); + + let a = $name::checked_from_rational(inner_max, 0 - 3 * accuracy).unwrap(); + assert_eq!(a.into_inner(), 0 - inner_max / 3); + + let a = $name::checked_from_rational(inner_min, 0 - accuracy / 3); + assert_eq!(a, None); + + let a = $name::checked_from_rational(1, 0 - accuracy).unwrap(); + assert_eq!(a.into_inner(), 0.saturating_sub(1)); + + let a = $name::checked_from_rational(1, 0 - accuracy - 1).unwrap(); + assert_eq!(a.into_inner(), 0); + + let a = $name::checked_from_rational(inner_min, accuracy / 3); + assert_eq!(a, None); + } let a = $name::checked_from_rational(inner_max, 3 * accuracy).unwrap(); assert_eq!(a.into_inner(), inner_max / 3); - let a = $name::checked_from_rational(inner_max, -3 * accuracy).unwrap(); - assert_eq!(a.into_inner(), -inner_max / 3); - let a = $name::checked_from_rational(inner_min, 2 * accuracy).unwrap(); assert_eq!(a.into_inner(), inner_min / 2); - let a = $name::checked_from_rational(inner_min, accuracy / -3); - assert_eq!(a, None); - - let a = $name::checked_from_rational(inner_min, accuracy / 3); - assert_eq!(a, None); - let a = $name::checked_from_rational(1, accuracy).unwrap(); assert_eq!(a.into_inner(), 1); - let a = $name::checked_from_rational(1, -accuracy).unwrap(); - assert_eq!(a.into_inner(), -1); - let a = $name::checked_from_rational(1, accuracy + 1).unwrap(); assert_eq!(a.into_inner(), 0); - - let a = $name::checked_from_rational(1, -accuracy - 1).unwrap(); - assert_eq!(a.into_inner(), 0); } #[test] @@ -1039,24 +1066,26 @@ macro_rules! implement_fixed { // Max + 1 => None. assert_eq!(a.checked_mul_int(i128::max_value() / 2 + 1), None); - // Min - 1. - assert_eq!(a.checked_mul_int((i128::min_value() + 1) / 2), Some(i128::min_value() + 2)); - // Min. - assert_eq!(a.checked_mul_int(i128::min_value() / 2), Some(i128::min_value())); - // Min + 1 => None. - assert_eq!(a.checked_mul_int(i128::min_value() / 2 - 1), None); + if $name::SIGNED { + // Min - 1. + assert_eq!(a.checked_mul_int((i128::min_value() + 1) / 2), Some(i128::min_value() + 2)); + // Min. + assert_eq!(a.checked_mul_int(i128::min_value() / 2), Some(i128::min_value())); + // Min + 1 => None. + assert_eq!(a.checked_mul_int(i128::min_value() / 2 - 1), None); + + let b = $name::saturating_from_rational(1, -2); + assert_eq!(b.checked_mul_int(42i128), Some(-21)); + assert_eq!(b.checked_mul_int(u128::max_value()), None); + assert_eq!(b.checked_mul_int(i128::max_value()), Some(i128::max_value() / -2)); + assert_eq!(b.checked_mul_int(i128::min_value()), Some(i128::min_value() / -2)); + } let a = $name::saturating_from_rational(1, 2); assert_eq!(a.checked_mul_int(42i128), Some(21)); assert_eq!(a.checked_mul_int(i128::max_value()), Some(i128::max_value() / 2)); assert_eq!(a.checked_mul_int(i128::min_value()), Some(i128::min_value() / 2)); - let b = $name::saturating_from_rational(1, -2); - assert_eq!(b.checked_mul_int(42i128), Some(-21)); - assert_eq!(b.checked_mul_int(u128::max_value()), None); - assert_eq!(b.checked_mul_int(i128::max_value()), Some(i128::max_value() / -2)); - assert_eq!(b.checked_mul_int(i128::min_value()), Some(i128::min_value() / -2)); - let c = $name::saturating_from_integer(255); assert_eq!(c.checked_mul_int(2i8), None); assert_eq!(c.checked_mul_int(2i128), Some(510)); @@ -1081,17 +1110,19 @@ macro_rules! implement_fixed { // Min + 1 => saturates to min. assert_eq!(a.saturating_mul_int(i128::min_value() / 2 - 1), i128::min_value()); + if $name::SIGNED { + let b = $name::saturating_from_rational(1, -2); + assert_eq!(b.saturating_mul_int(42i32), -21); + assert_eq!(b.saturating_mul_int(i128::max_value()), i128::max_value() / -2); + assert_eq!(b.saturating_mul_int(i128::min_value()), i128::min_value() / -2); + assert_eq!(b.saturating_mul_int(u128::max_value()), u128::min_value()); + } + let a = $name::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); assert_eq!(a.saturating_mul_int(i128::min_value()), i128::min_value() / 2); - let b = $name::saturating_from_rational(1, -2); - assert_eq!(b.saturating_mul_int(42i32), -21); - assert_eq!(b.saturating_mul_int(i128::max_value()), i128::max_value() / -2); - assert_eq!(b.saturating_mul_int(i128::min_value()), i128::min_value() / -2); - assert_eq!(b.saturating_mul_int(u128::max_value()), u128::min_value()); - let c = $name::saturating_from_integer(255); assert_eq!(c.saturating_mul_int(2i8), i8::max_value()); assert_eq!(c.saturating_mul_int(-2i8), i8::min_value()); @@ -1118,34 +1149,36 @@ macro_rules! implement_fixed { let e = $name::from_inner(1); assert_eq!(a.checked_mul(&(c/2.into()+e)), None); - // Min + 1. - let b = $name::from_inner(inner_min + 1) / 2.into(); - let c = $name::from_inner(inner_min + 2); - assert_eq!(a.checked_mul(&b), Some(c)); + if $name::SIGNED { + // Min + 1. + let b = $name::from_inner(inner_min + 1) / 2.into(); + let c = $name::from_inner(inner_min + 2); + assert_eq!(a.checked_mul(&b), Some(c)); - // Min. - let b = $name::from_inner(inner_min) / 2.into(); - let c = $name::from_inner(inner_min); - assert_eq!(a.checked_mul(&b), Some(c)); + // Min. + let b = $name::from_inner(inner_min) / 2.into(); + let c = $name::from_inner(inner_min); + assert_eq!(a.checked_mul(&b), Some(c)); + + // Min - 1 => None. + let b = $name::from_inner(inner_min) / 2.into() - $name::from_inner(1); + assert_eq!(a.checked_mul(&b), None); - // Min - 1 => None. - let b = $name::from_inner(inner_min) / 2.into() - $name::from_inner(1); - assert_eq!(a.checked_mul(&b), None); + let c = $name::saturating_from_integer(255); + let b = $name::saturating_from_rational(1, -2); + + assert_eq!(b.checked_mul(&42.into()), Some(0.saturating_sub(21).into())); + assert_eq!(b.checked_mul(&$name::max_value()), $name::max_value().checked_div(&0.saturating_sub(2).into())); + assert_eq!(b.checked_mul(&$name::min_value()), $name::min_value().checked_div(&0.saturating_sub(2).into())); + assert_eq!(c.checked_mul(&$name::min_value()), None); + } let a = $name::saturating_from_rational(1, 2); - let b = $name::saturating_from_rational(1, -2); let c = $name::saturating_from_integer(255); assert_eq!(a.checked_mul(&42.into()), Some(21.into())); - assert_eq!(b.checked_mul(&42.into()), Some((-21).into())); assert_eq!(c.checked_mul(&2.into()), Some(510.into())); - - assert_eq!(b.checked_mul(&$name::max_value()), $name::max_value().checked_div(&(-2).into())); - assert_eq!(b.checked_mul(&$name::min_value()), $name::min_value().checked_div(&(-2).into())); - assert_eq!(c.checked_mul(&$name::max_value()), None); - assert_eq!(c.checked_mul(&$name::min_value()), None); - assert_eq!(a.checked_mul(&$name::max_value()), $name::max_value().checked_div(&2.into())); assert_eq!(a.checked_mul(&$name::min_value()), $name::min_value().checked_div(&2.into())); } @@ -1171,25 +1204,27 @@ macro_rules! implement_fixed { assert_eq!(a.checked_div_int(inner_max / accuracy), Some(1)); assert_eq!(a.checked_div_int(1i8), None); - assert_eq!(a.checked_div_int(-2), Some(-inner_max / (2 * accuracy))); - assert_eq!(a.checked_div_int(inner_max / -accuracy), Some(-1)); + if b < c { + // Not executed by unsigned inners. + assert_eq!(a.checked_div_int(0.saturating_sub(2)), Some(0.saturating_sub(inner_max / (2 * accuracy)))); + assert_eq!(a.checked_div_int(0.saturating_sub(inner_max / accuracy)), Some(0.saturating_sub(1))); + assert_eq!(b.checked_div_int(i128::min_value()), Some(0)); + assert_eq!(b.checked_div_int(inner_min / accuracy), Some(1)); + assert_eq!(b.checked_div_int(1i8), None); + assert_eq!(b.checked_div_int(0.saturating_sub(2)), Some(0.saturating_sub(inner_min / (2 * accuracy)))); + assert_eq!(b.checked_div_int(0.saturating_sub(inner_min / accuracy)), Some(0.saturating_sub(1))); + assert_eq!(c.checked_div_int(i128::min_value()), Some(0)); + assert_eq!(d.checked_div_int(i32::min_value()), Some(0)); + } - assert_eq!(b.checked_div_int(i128::min_value()), Some(0)); assert_eq!(b.checked_div_int(2), Some(inner_min / (2 * accuracy))); - assert_eq!(b.checked_div_int(inner_min / accuracy), Some(1)); - assert_eq!(b.checked_div_int(1i8), None); - - assert_eq!(b.checked_div_int(-2), Some(-(inner_min / (2 * accuracy)))); - assert_eq!(b.checked_div_int(-(inner_min / accuracy)), Some(-1)); 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(i128::min_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(i32::min_value()), Some(0)); assert_eq!(d.checked_div_int(1i8), Some(1)); assert_eq!(a.checked_div_int(0), None); @@ -1213,14 +1248,16 @@ macro_rules! implement_fixed { let a = $name::saturating_from_integer(5); assert_eq!(a.saturating_div_int(2), 2); - let a = $name::saturating_from_integer(5); - assert_eq!(a.saturating_div_int(-2), -2); - - let a = $name::min_value(); - assert_eq!(a.saturating_div_int(-1i128), (inner_max / accuracy) as i128); - let a = $name::min_value(); assert_eq!(a.saturating_div_int(1i128), (inner_min / accuracy) as i128); + + if $name::SIGNED { + let a = $name::saturating_from_integer(5); + assert_eq!(a.saturating_div_int(-2), -2); + + let a = $name::min_value(); + assert_eq!(a.saturating_div_int(-1i128), (inner_max / accuracy) as i128); + } } #[test] @@ -1228,10 +1265,13 @@ macro_rules! implement_fixed { let inner_max = <$name as FixedPointNumber>::Inner::max_value(); let inner_min = <$name as FixedPointNumber>::Inner::min_value(); - assert_eq!($name::from_inner(inner_min).saturating_abs(), $name::max_value()); assert_eq!($name::from_inner(inner_max).saturating_abs(), $name::max_value()); assert_eq!($name::zero().saturating_abs(), 0.into()); - assert_eq!($name::saturating_from_rational(-1, 2).saturating_abs(), (1, 2).into()); + + if $name::SIGNED { + assert_eq!($name::from_inner(inner_min).saturating_abs(), $name::max_value()); + assert_eq!($name::saturating_from_rational(-1, 2).saturating_abs(), (1, 2).into()); + } } #[test] @@ -1245,9 +1285,12 @@ macro_rules! implement_fixed { assert_eq!($name::one().saturating_mul_acc_int(u128::max_value() / 2), u128::max_value() - 1); assert_eq!($name::one().saturating_mul_acc_int(u128::min_value()), u128::min_value()); - let a = $name::saturating_from_rational(-1, 2); - assert_eq!(a.saturating_mul_acc_int(42i8), 21i8); - assert_eq!(a.saturating_mul_acc_int(u128::max_value() - 1), u128::max_value() - 1); + if $name::SIGNED { + let a = $name::saturating_from_rational(-1, 2); + assert_eq!(a.saturating_mul_acc_int(42i8), 21i8); + assert_eq!(a.saturating_mul_acc_int(42u8), 21u8); + assert_eq!(a.saturating_mul_acc_int(u128::max_value() - 1), u128::max_value() / 2); + } } #[test] @@ -1270,19 +1313,18 @@ macro_rules! implement_fixed { assert_eq!(a.checked_div(&$name::max_value()), Some(1.into())); assert_eq!(a.checked_div(&d), Some(a)); - assert_eq!(a.checked_div(&(-2).into()), Some($name::from_inner(-inner_max / 2))); - assert_eq!(a.checked_div(&-$name::max_value()), Some((-1).into())); + if b < c { + // Not executed by unsigned inners. + assert_eq!(a.checked_div(&0.saturating_sub(2).into()), Some($name::from_inner(0.saturating_sub(inner_max / 2)))); + assert_eq!(a.checked_div(&-$name::max_value()), Some(0.saturating_sub(1).into())); + assert_eq!(b.checked_div(&0.saturating_sub(2).into()), Some($name::from_inner(0.saturating_sub(inner_min / 2)))); + assert_eq!(c.checked_div(&$name::max_value()), Some(0.into())); + assert_eq!(b.checked_div(&b), Some($name::one())); + } - assert_eq!(b.checked_div(&b), Some($name::one())); assert_eq!(b.checked_div(&2.into()), Some($name::from_inner(inner_min / 2))); - - assert_eq!(b.checked_div(&(-2).into()), Some($name::from_inner(inner_min / -2))); - assert_eq!(b.checked_div(&a), Some((-1).into())); - + assert_eq!(b.checked_div(&a), Some(0.saturating_sub(1).into())); assert_eq!(c.checked_div(&1.into()), Some(0.into())); - assert_eq!(c.checked_div(&$name::max_value()), Some(0.into())); - assert_eq!(c.checked_div(&$name::min_value()), Some(0.into())); - assert_eq!(d.checked_div(&1.into()), Some(1.into())); assert_eq!(a.checked_div(&$name::one()), Some(a)); @@ -1301,8 +1343,10 @@ macro_rules! implement_fixed { let n = $name::saturating_from_rational(5, 2).trunc(); assert_eq!(n, $name::saturating_from_integer(2)); - let n = $name::saturating_from_rational(-5, 2).trunc(); - assert_eq!(n, $name::saturating_from_integer(-2)); + if $name::SIGNED { + let n = $name::saturating_from_rational(-5, 2).trunc(); + assert_eq!(n, $name::saturating_from_integer(-2)); + } } #[test] @@ -1313,12 +1357,6 @@ macro_rules! implement_fixed { assert_eq!(n, i + f); - let n = $name::saturating_from_rational(-5, 2); - let i = n.trunc(); - let f = n.frac(); - - assert_eq!(n, i - f); - let n = $name::saturating_from_rational(5, 2) .frac() .saturating_mul(10.into()); @@ -1329,16 +1367,23 @@ macro_rules! implement_fixed { .saturating_mul(10.into()); assert_eq!(n, 5.into()); - // The sign is attached to the integer part unless it is zero. - let n = $name::saturating_from_rational(-5, 2) - .frac() - .saturating_mul(10.into()); - assert_eq!(n, 5.into()); - - let n = $name::saturating_from_rational(-1, 2) - .frac() - .saturating_mul(10.into()); - assert_eq!(n, (-5).into()); + if $name::SIGNED { + let n = $name::saturating_from_rational(-5, 2); + let i = n.trunc(); + let f = n.frac(); + assert_eq!(n, i - f); + + // The sign is attached to the integer part unless it is zero. + let n = $name::saturating_from_rational(-5, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 5.into()); + + let n = $name::saturating_from_rational(-1, 2) + .frac() + .saturating_mul(10.into()); + assert_eq!(n, 0.saturating_sub(5).into()); + } } #[test] @@ -1347,7 +1392,7 @@ macro_rules! implement_fixed { assert_eq!(n.ceil(), 3.into()); let n = $name::saturating_from_rational(-5, 2); - assert_eq!(n.ceil(), (-2).into()); + assert_eq!(n.ceil(), 0.saturating_sub(2).into()); // On the limits: let n = $name::max_value(); @@ -1363,7 +1408,7 @@ macro_rules! implement_fixed { assert_eq!(n.floor(), 2.into()); let n = $name::saturating_from_rational(-5, 2); - assert_eq!(n.floor(), (-3).into()); + assert_eq!(n.floor(), 0.saturating_sub(3).into()); // On the limits: let n = $name::max_value(); @@ -1385,7 +1430,7 @@ macro_rules! implement_fixed { assert_eq!(n.round(), 3.into()); let n = $name::saturating_from_rational(-5, 2); - assert_eq!(n.round(), (-3).into()); + assert_eq!(n.round(), 0.saturating_sub(3).into()); // Saturating: let n = $name::max_value(); @@ -1404,14 +1449,6 @@ macro_rules! implement_fixed { assert_eq!(n.round(), ($name::max_value() - 1.into()).trunc()); - // floor(min + 1) - 0.33.. - let n = $name::min_value() - .saturating_add(1.into()) - .trunc() - .saturating_sub((1, 3).into()); - - assert_eq!(n.round(), ($name::min_value() + 1.into()).trunc()); - // floor(max - 1) + 0.5 let n = $name::max_value() .saturating_sub(1.into()) @@ -1420,13 +1457,23 @@ macro_rules! implement_fixed { assert_eq!(n.round(), $name::max_value().trunc()); - // floor(min + 1) - 0.5 - let n = $name::min_value() - .saturating_add(1.into()) - .trunc() - .saturating_sub((1, 2).into()); + if $name::SIGNED { + // floor(min + 1) - 0.33.. + let n = $name::min_value() + .saturating_add(1.into()) + .trunc() + .saturating_sub((1, 3).into()); + + assert_eq!(n.round(), ($name::min_value() + 1.into()).trunc()); - assert_eq!(n.round(), $name::min_value().trunc()); + // floor(min + 1) - 0.5 + let n = $name::min_value() + .saturating_add(1.into()) + .trunc() + .saturating_sub((1, 2).into()); + + assert_eq!(n.round(), $name::min_value().trunc()); + } } #[test] @@ -1452,9 +1499,6 @@ macro_rules! implement_fixed { let one = $name::one(); assert_eq!(format!("{:?}", one), format!("{}(1.{:0>weight$})", stringify!($name), 0, weight=precision())); - let neg = -$name::one(); - assert_eq!(format!("{:?}", neg), format!("{}(-1.{:0>weight$})", stringify!($name), 0, weight=precision())); - let frac = $name::saturating_from_rational(1, 2); assert_eq!(format!("{:?}", frac), format!("{}(0.{:0weight$})", stringify!($name), 0, weight=precision())); + + let frac = $name::saturating_from_rational(-314, 100); + assert_eq!(format!("{:?}", frac), format!("{}(-3.{:0 { assert!( @@ -36,16 +36,95 @@ pub mod biguint; pub mod helpers_128bit; pub mod traits; mod per_things; -mod fixed; +mod fixed_point; mod rational128; -pub use fixed::{FixedPointNumber, Fixed64, Fixed128}; +pub use fixed_point::{FixedPointNumber, FixedPointOperand, FixedI64, FixedI128, FixedU128}; pub use per_things::{PerThing, Percent, PerU16, Permill, Perbill, Perquintill}; pub use rational128::Rational128; +use sp_std::cmp::Ordering; + +/// Trait for comparing two numbers with an threshold. +/// +/// Returns: +/// - `Ordering::Greater` if `self` is greater than `other + threshold`. +/// - `Ordering::Less` if `self` is less than `other - threshold`. +/// - `Ordering::Equal` otherwise. +pub trait ThresholdOrd { + /// Compare if `self` is `threshold` greater or less than `other`. + fn tcmp(&self, other: &T, epsilon: T) -> Ordering; +} + +impl ThresholdOrd for T +where + T: Ord + PartialOrd + Copy + Clone + traits::Zero + traits::Saturating, +{ + fn tcmp(&self, other: &T, threshold: T) -> Ordering { + // early exit. + if threshold.is_zero() { + return self.cmp(&other) + } + + let upper_bound = other.saturating_add(threshold); + let lower_bound = other.saturating_sub(threshold); + + if upper_bound <= lower_bound { + // defensive only. Can never happen. + self.cmp(&other) + } else { + // upper_bound is guaranteed now to be bigger than lower. + match (self.cmp(&lower_bound), self.cmp(&upper_bound)) { + (Ordering::Greater, Ordering::Greater) => Ordering::Greater, + (Ordering::Less, Ordering::Less) => Ordering::Less, + _ => Ordering::Equal, + } + } + + } +} + #[cfg(test)] mod tests { use super::*; + use crate::traits::Saturating; + use sp_std::cmp::Ordering; + + #[test] + fn epsilon_ord_works() { + let b = 115u32; + + let e = Perbill::from_percent(10).mul_ceil(b); + + // [115 - 11,5 (103,5), 115 + 11,5 (126,5)] is all equal + assert_eq!(103u32.tcmp(&b, e), Ordering::Equal); + assert_eq!(104u32.tcmp(&b, e), Ordering::Equal); + assert_eq!(115u32.tcmp(&b, e), Ordering::Equal); + assert_eq!(120u32.tcmp(&b, e), Ordering::Equal); + assert_eq!(126u32.tcmp(&b, e), Ordering::Equal); + assert_eq!(127u32.tcmp(&b, e), Ordering::Equal); + + assert_eq!(128u32.tcmp(&b, e), Ordering::Greater); + assert_eq!(102u32.tcmp(&b, e), Ordering::Less); + } + + #[test] + fn epsilon_ord_works_with_small_epc() { + let b = 115u32; + // way less than 1 percent. threshold will be zero. Result should be same as normal ord. + let e = Perbill::from_parts(100) * b; + + // [115 - 11,5 (103,5), 115 + 11,5 (126,5)] is all equal + assert_eq!(103u32.tcmp(&b, e), 103u32.cmp(&b)); + assert_eq!(104u32.tcmp(&b, e), 104u32.cmp(&b)); + assert_eq!(115u32.tcmp(&b, e), 115u32.cmp(&b)); + assert_eq!(120u32.tcmp(&b, e), 120u32.cmp(&b)); + assert_eq!(126u32.tcmp(&b, e), 126u32.cmp(&b)); + assert_eq!(127u32.tcmp(&b, e), 127u32.cmp(&b)); + + assert_eq!(128u32.tcmp(&b, e), 128u32.cmp(&b)); + assert_eq!(102u32.tcmp(&b, e), 102u32.cmp(&b)); + } #[test] fn peru16_rational_does_not_overflow() { @@ -53,4 +132,10 @@ mod tests { // maximum capacity of their type, e.g. PerU16. let _ = PerU16::from_rational_approximation(17424870u32, 17424870); } + + #[test] + fn saturating_mul_works() { + assert_eq!(Saturating::saturating_mul(2, i32::min_value()), i32::min_value()); + assert_eq!(Saturating::saturating_mul(2, i32::max_value()), i32::max_value()); + } } diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index de295e9df2..2ee16bdd11 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -22,7 +22,9 @@ use serde::{Serialize, Deserialize}; use sp_std::{ops, fmt, prelude::*, convert::TryInto}; use codec::{Encode, Decode, CompactAs}; -use crate::traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, Bounded}; +use crate::traits::{ + SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, Bounded, Zero, +}; use sp_debug_derive::RuntimeDebug; /// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per @@ -33,33 +35,145 @@ pub trait PerThing: /// The data type used to build this per-thingy. type Inner: BaseArithmetic + Copy + fmt::Debug; - /// The data type that is used to store values bigger than the maximum of this type. This must - /// at least be able to store `Self::ACCURACY * Self::ACCURACY`. - type Upper: BaseArithmetic + Copy + fmt::Debug; + /// A data type larger than `Self::Inner`, used to avoid overflow in some computations. + /// It must be able to compute `ACCURACY^2`. + type Upper: BaseArithmetic + Copy + From + TryInto + fmt::Debug; - /// accuracy of this type + /// The accuracy of this type. const ACCURACY: Self::Inner; - /// NoThing - fn zero() -> Self; + /// Equivalent to `Self::from_parts(0)`. + fn zero() -> Self { Self::from_parts(Self::Inner::zero()) } - /// `true` if this is nothing. - fn is_zero(&self) -> bool; + /// Return `true` if this is nothing. + fn is_zero(&self) -> bool { self.deconstruct() == Self::Inner::zero() } - /// Everything. - fn one() -> Self; + /// Equivalent to `Self::from_parts(Self::ACCURACY)`. + fn one() -> Self { Self::from_parts(Self::ACCURACY) } - /// Consume self and deconstruct into a raw numeric type. - fn deconstruct(self) -> Self::Inner; - - /// From an explicitly defined number of parts per maximum of the type. - fn from_parts(parts: Self::Inner) -> Self; + /// Return `true` if this is one. + fn is_one(&self) -> bool { self.deconstruct() == Self::ACCURACY } - /// Converts a percent into `Self`. Equal to `x / 100`. - fn from_percent(x: Self::Inner) -> Self; + /// Build this type from a percent. Equivalent to `Self::from_parts(x * Self::ACCURACY / 100)` + /// but more accurate. + fn from_percent(x: Self::Inner) -> Self { + let a = x.min(100.into()); + let b = Self::ACCURACY; + // if Self::ACCURACY % 100 > 0 then we need the correction for accuracy + let c = rational_mul_correction::(b, a, 100.into(), Rounding::Nearest); + Self::from_parts(a / 100.into() * b + c) + } /// Return the product of multiplication of this value by itself. - fn square(self) -> Self; + fn square(self) -> Self { + let p = Self::Upper::from(self.deconstruct()); + let q = Self::Upper::from(Self::ACCURACY); + Self::from_rational_approximation(p * p, q * q) + } + + /// Multiplication that always rounds down to a whole number. The standard `Mul` rounds to the + /// nearest whole number. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(34) * 10u64, 3); + /// assert_eq!(Percent::from_percent(36) * 10u64, 4); + /// + /// // round down + /// assert_eq!(Percent::from_percent(34).mul_floor(10u64), 3); + /// assert_eq!(Percent::from_percent(36).mul_floor(10u64), 3); + /// # } + /// ``` + fn mul_floor(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + { + overflow_prune_mul::(b, self.deconstruct(), Rounding::Down) + } + + /// Multiplication that always rounds the result up to a whole number. The standard `Mul` + /// rounds to the nearest whole number. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(34) * 10u64, 3); + /// assert_eq!(Percent::from_percent(36) * 10u64, 4); + /// + /// // round up + /// assert_eq!(Percent::from_percent(34).mul_ceil(10u64), 4); + /// assert_eq!(Percent::from_percent(36).mul_ceil(10u64), 4); + /// # } + /// ``` + fn mul_ceil(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + { + overflow_prune_mul::(b, self.deconstruct(), Rounding::Up) + } + + /// Saturating multiplication by the reciprocal of `self`. The result is rounded to the + /// nearest whole number and saturates at the numeric bounds instead of overflowing. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// assert_eq!(Percent::from_percent(50).saturating_reciprocal_mul(10u64), 20); + /// # } + /// ``` + fn saturating_reciprocal_mul(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + Saturating + { + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Nearest) + } + + /// Saturating multiplication by the reciprocal of `self`. The result is rounded down to the + /// nearest whole number and saturates at the numeric bounds instead of overflowing. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(60).saturating_reciprocal_mul(10u64), 17); + /// // round down + /// assert_eq!(Percent::from_percent(60).saturating_reciprocal_mul_floor(10u64), 16); + /// # } + /// ``` + fn saturating_reciprocal_mul_floor(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + Saturating + { + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Down) + } + + /// Saturating multiplication by the reciprocal of `self`. The result is rounded up to the + /// nearest whole number and saturates at the numeric bounds instead of overflowing. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // round to nearest + /// assert_eq!(Percent::from_percent(61).saturating_reciprocal_mul(10u64), 16); + /// // round up + /// assert_eq!(Percent::from_percent(61).saturating_reciprocal_mul_ceil(10u64), 17); + /// # } + /// ``` + fn saturating_reciprocal_mul_ceil(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add + Saturating + { + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Up) + } + + /// Consume self and return the number of parts per thing. + fn deconstruct(self) -> Self::Inner; + + /// Build this type from a number of parts per thing. + fn from_parts(parts: Self::Inner) -> Self; /// Converts a fraction into `Self`. #[cfg(feature = "std")] @@ -84,28 +198,106 @@ pub trait PerThing: /// # } /// ``` fn from_rational_approximation(p: N, q: N) -> Self - where N: - Clone + Ord + From + TryInto + TryInto + - ops::Div + ops::Rem + ops::Add; + where N: Clone + Ord + From + TryInto + TryInto + + ops::Div + ops::Rem + ops::Add; +} - /// A mul implementation that always rounds down, whilst the standard `Mul` implementation - /// rounds to the nearest numbers - /// - /// ```rust - /// # use sp_arithmetic::{Percent, PerThing}; - /// # fn main () { - /// // rounds to closest - /// assert_eq!(Percent::from_percent(34) * 10u64, 3); - /// assert_eq!(Percent::from_percent(36) * 10u64, 4); - /// - /// // collapse down - /// assert_eq!(Percent::from_percent(34).mul_collapse(10u64), 3); - /// assert_eq!(Percent::from_percent(36).mul_collapse(10u64), 3); - /// # } - /// ``` - fn mul_collapse(self, b: N) -> N - where N: Clone + From + UniqueSaturatedInto + ops::Rem - + ops::Div + ops::Mul + ops::Add; +/// The rounding method to use. +/// +/// `Perthing`s are unsigned so `Up` means towards infinity and `Down` means towards zero. +/// `Nearest` will round an exact half down. +enum Rounding { + Up, + Down, + Nearest, +} + +/// Saturating reciprocal multiplication. Compute `x / self`, saturating at the numeric +/// bounds instead of overflowing. +fn saturating_reciprocal_mul( + x: N, + part: P::Inner, + rounding: Rounding, +) -> N +where + N: Clone + From + UniqueSaturatedInto + ops::Div + ops::Mul + ops::Add + ops::Rem + Saturating, + P: PerThing, +{ + let maximum: N = P::ACCURACY.into(); + let c = rational_mul_correction::( + x.clone(), + P::ACCURACY, + part, + rounding, + ); + (x / part.into()).saturating_mul(maximum).saturating_add(c) +} + +/// Overflow-prune multiplication. Accurately multiply a value by `self` without overflowing. +fn overflow_prune_mul( + x: N, + part: P::Inner, + rounding: Rounding, +) -> N +where + N: Clone + From + UniqueSaturatedInto + ops::Div + ops::Mul + ops::Add + ops::Rem, + P: PerThing, +{ + let maximum: N = P::ACCURACY.into(); + let part_n: N = part.into(); + let c = rational_mul_correction::( + x.clone(), + part, + P::ACCURACY, + rounding, + ); + (x / maximum) * part_n + c +} + +/// Compute the error due to integer division in the expression `x / denom * numer`. +/// +/// Take the remainder of `x / denom` and multiply by `numer / denom`. The result can be added +/// to `x / denom * numer` for an accurate result. +fn rational_mul_correction( + x: N, + numer: P::Inner, + denom: P::Inner, + rounding: Rounding, +) -> N +where + N: From + UniqueSaturatedInto + ops::Div + ops::Mul + ops::Add + ops::Rem, + P: PerThing, +{ + let numer_upper = P::Upper::from(numer); + let denom_n = N::from(denom); + let denom_upper = P::Upper::from(denom); + let rem = x.rem(denom_n); + // `rem` is less than `denom`, which fits in `P::Inner`. + let rem_inner = rem.saturated_into::(); + // `P::Upper` always fits `P::Inner::max_value().pow(2)`, thus it fits `rem * numer`. + let rem_mul_upper = P::Upper::from(rem_inner) * numer_upper; + // `rem` is less than `denom`, so `rem * numer / denom` is less than `numer`, which fits in + // `P::Inner`. + let mut rem_mul_div_inner = (rem_mul_upper / denom_upper).saturated_into::(); + match rounding { + // Already rounded down + Rounding::Down => {}, + // Round up if the fractional part of the result is non-zero. + Rounding::Up => if rem_mul_upper % denom_upper > 0.into() { + // `rem * numer / denom` is less than `numer`, so this will not overflow. + rem_mul_div_inner = rem_mul_div_inner + 1.into(); + }, + // Round up if the fractional part of the result is greater than a half. An exact half is + // rounded down. + Rounding::Nearest => if rem_mul_upper % denom_upper > denom_upper / 2.into() { + // `rem * numer / denom` is less than `numer`, so this will not overflow. + rem_mul_div_inner = rem_mul_div_inner + 1.into(); + }, + } + rem_mul_div_inner.into() } macro_rules! implement_per_thing { @@ -113,15 +305,17 @@ macro_rules! implement_per_thing { $name:ident, $test_mod:ident, [$($test_units:tt),+], - $max:tt, $type:ty, + $max:tt, + $type:ty, $upper_type:ty, $title:expr $(,)? ) => { - /// A fixed point representation of a number between in the range [0, 1]. + /// A fixed point representation of a number in the range [0, 1]. /// #[doc = $title] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Encode, Decode, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, CompactAs)] + #[derive(Encode, Decode, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, + RuntimeDebug, CompactAs)] pub struct $name($type); impl PerThing for $name { @@ -130,40 +324,20 @@ macro_rules! implement_per_thing { const ACCURACY: Self::Inner = $max; - fn zero() -> Self { Self(0) } - - fn is_zero(&self) -> bool { self.0 == 0 } - - fn one() -> Self { Self($max) } - + /// Consume self and return the number of parts per thing. fn deconstruct(self) -> Self::Inner { self.0 } - // needed only for peru16. Since peru16 is the only type in which $max == - // $type::max_value(), rustc is being a smart-a** here by warning that the comparison - // is not needed. - #[allow(unused_comparisons)] - fn from_parts(parts: Self::Inner) -> Self { - Self([parts, $max][(parts > $max) as usize]) - } - - fn from_percent(x: Self::Inner) -> Self { - Self::from_rational_approximation([x, 100][(x > 100) as usize] as $upper_type, 100) - } - - fn square(self) -> Self { - // both can be safely casted and multiplied. - let p: $upper_type = self.0 as $upper_type * self.0 as $upper_type; - let q: $upper_type = <$upper_type>::from($max) * <$upper_type>::from($max); - Self::from_rational_approximation(p, q) - } + /// Build this type from a number of parts per thing. + fn from_parts(parts: Self::Inner) -> Self { Self(parts.min($max)) } #[cfg(feature = "std")] - fn from_fraction(x: f64) -> Self { Self((x * ($max as f64)) as Self::Inner) } + fn from_fraction(x: f64) -> Self { + Self::from_parts((x * $max as f64) as Self::Inner) + } fn from_rational_approximation(p: N, q: N) -> Self - where N: - Clone + Ord + From + TryInto + TryInto + - ops::Div + ops::Rem + ops::Add + where N: Clone + Ord + From + TryInto + TryInto + + ops::Div + ops::Rem + ops::Add { let div_ceil = |x: N, f: N| -> N { let mut o = x.clone() / f.clone(); @@ -206,39 +380,6 @@ macro_rules! implement_per_thing { $name(part as Self::Inner) } - - fn mul_collapse(self, b: N) -> N - where - N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem - + ops::Div + ops::Mul + ops::Add - { - let maximum: N = $max.into(); - let upper_max: $upper_type = $max.into(); - let part: N = self.0.into(); - - let rem_multiplied_divided = { - let rem = b.clone().rem(maximum.clone()); - - // `rem_sized` is inferior to $max, thus it fits into $type. This is assured by - // a test. - let rem_sized = rem.saturated_into::<$type>(); - - // `self` and `rem_sized` are inferior to $max, thus the product is less than - // $max^2 and fits into $upper_type. This is assured by a test. - let rem_multiplied_upper = rem_sized as $upper_type * self.0 as $upper_type; - - // `rem_multiplied_upper` is less than $max^2 therefore divided by $max it fits - // in $type. remember that $type always fits $max. - let rem_multiplied_divided_sized = - (rem_multiplied_upper / upper_max) as $type; - - // `rem_multiplied_divided_sized` is inferior to b, thus it can be converted - // back to N type - rem_multiplied_divided_sized.into() - }; - - (b / maximum) * part + rem_multiplied_divided - } } impl $name { @@ -257,7 +398,7 @@ macro_rules! implement_per_thing { /// /// This can be created at compile time. pub const fn from_percent(x: $type) -> Self { - Self([x, 100][(x > 100) as usize] * ($max / 100)) + Self(([x, 100][(x > 100) as usize] as $upper_type * $max as $upper_type / 100) as $type) } /// See [`PerThing::one`]. @@ -265,6 +406,11 @@ macro_rules! implement_per_thing { ::one() } + /// See [`PerThing::is_one`]. + pub fn is_one(&self) -> bool { + PerThing::is_one(self) + } + /// See [`PerThing::zero`]. pub fn zero() -> Self { ::zero() @@ -299,23 +445,63 @@ macro_rules! implement_per_thing { ::from_rational_approximation(p, q) } - /// See [`PerThing::mul_collapse`]. - pub fn mul_collapse(self, b: N) -> N + /// See [`PerThing::mul_floor`]. + pub fn mul_floor(self, b: N) -> N where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + ops::Div + ops::Mul + ops::Add { - PerThing::mul_collapse(self, b) + PerThing::mul_floor(self, b) + } + + /// See [`PerThing::mul_ceil`]. + pub fn mul_ceil(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + + ops::Rem + ops::Div + ops::Mul + + ops::Add { + PerThing::mul_ceil(self, b) + } + + /// See [`PerThing::saturating_reciprocal_mul`]. + fn saturating_reciprocal_mul(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + + Saturating { + PerThing::saturating_reciprocal_mul(self, b) + } + + /// See [`PerThing::saturating_reciprocal_mul_floor`]. + fn saturating_reciprocal_mul_floor(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + + Saturating { + PerThing::saturating_reciprocal_mul_floor(self, b) + } + + /// See [`PerThing::saturating_reciprocal_mul_ceil`]. + fn saturating_reciprocal_mul_ceil(self, b: N) -> N + where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + + Saturating { + PerThing::saturating_reciprocal_mul_ceil(self, b) } } impl Saturating for $name { + /// Saturating addition. Compute `self + rhs`, saturating at the numeric bounds instead of + /// overflowing. This operation is lossless if it does not saturate. fn saturating_add(self, rhs: Self) -> Self { // defensive-only: since `$max * 2 < $type::max_value()`, this can never overflow. Self::from_parts(self.0.saturating_add(rhs.0)) } + + /// Saturating subtraction. Compute `self - rhs`, saturating at the numeric bounds instead of + /// overflowing. This operation is lossless if it does not saturate. fn saturating_sub(self, rhs: Self) -> Self { Self::from_parts(self.0.saturating_sub(rhs.0)) } + + /// Saturating multiply. Compute `self * rhs`, saturating at the numeric bounds instead of + /// overflowing. This operation is lossy. fn saturating_mul(self, rhs: Self) -> Self { let a = self.0 as $upper_type; let b = rhs.0 as $upper_type; @@ -348,8 +534,7 @@ macro_rules! implement_per_thing { /// Non-overflow multiplication. /// - /// tailored to be used with a balance type. - /// + /// This is tailored to be used with a balance type. impl ops::Mul for $name where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem @@ -357,37 +542,7 @@ macro_rules! implement_per_thing { { type Output = N; fn mul(self, b: N) -> Self::Output { - let maximum: N = $max.into(); - let upper_max: $upper_type = $max.into(); - let part: N = self.0.into(); - - let rem_multiplied_divided = { - let rem = b.clone().rem(maximum.clone()); - - // `rem_sized` is inferior to $max, thus it fits into $type. This is assured by - // a test. - let rem_sized = rem.saturated_into::<$type>(); - - // `self` and `rem_sized` are inferior to $max, thus the product is less than - // $max^2 and fits into $upper_type. This is assured by a test. - let rem_multiplied_upper = rem_sized as $upper_type * self.0 as $upper_type; - - // `rem_multiplied_upper` is less than $max^2 therefore divided by $max it fits - // in $type. remember that $type always fits $max. - let mut rem_multiplied_divided_sized = - (rem_multiplied_upper / upper_max) as $type; - - // fix a tiny rounding error - if rem_multiplied_upper % upper_max > upper_max / 2 { - rem_multiplied_divided_sized += 1; - } - - // `rem_multiplied_divided_sized` is inferior to b, thus it can be converted - // back to N type - rem_multiplied_divided_sized.into() - }; - - (b / maximum) * part + rem_multiplied_divided + overflow_prune_mul::(b, self.deconstruct(), Rounding::Nearest) } } @@ -413,6 +568,9 @@ macro_rules! implement_per_thing { // for something like percent they can be the same. assert!((<$type>::max_value() as $upper_type) <= <$upper_type>::max_value()); assert!(<$upper_type>::from($max).checked_mul($max.into()).is_some()); + + // make sure saturating_pow won't overflow the upper type + assert!(<$upper_type>::from($max) * <$upper_type>::from($max) < <$upper_type>::max_value()); } #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)] @@ -455,6 +613,12 @@ macro_rules! implement_per_thing { assert_eq!($name::zero(), $name::from_parts(Zero::zero())); assert_eq!($name::one(), $name::from_parts($max)); assert_eq!($name::ACCURACY, $max); + + assert_eq!($name::from_percent(0), $name::from_parts(Zero::zero())); + assert_eq!($name::from_percent(10), $name::from_parts($max / 10)); + assert_eq!($name::from_percent(100), $name::from_parts($max)); + assert_eq!($name::from_percent(200), $name::from_parts($max)); + assert_eq!($name::from_fraction(0.0), $name::from_parts(Zero::zero())); assert_eq!($name::from_fraction(0.1), $name::from_parts($max / 10)); assert_eq!($name::from_fraction(1.0), $name::from_parts($max)); @@ -745,6 +909,132 @@ macro_rules! implement_per_thing { 2, ); } + + #[test] + fn saturating_reciprocal_mul_works() { + // divide by 1 + assert_eq!( + $name::from_parts($max).saturating_reciprocal_mul(<$type>::from(10u8)), + 10, + ); + // divide by 1/2 + assert_eq!( + $name::from_parts($max / 2).saturating_reciprocal_mul(<$type>::from(10u8)), + 20, + ); + // saturate + assert_eq!( + $name::from_parts(1).saturating_reciprocal_mul($max), + <$type>::max_value(), + ); + // round to nearest + assert_eq!( + $name::from_percent(60).saturating_reciprocal_mul(<$type>::from(10u8)), + 17, + ); + // round down + assert_eq!( + $name::from_percent(60).saturating_reciprocal_mul_floor(<$type>::from(10u8)), + 16, + ); + // round to nearest + assert_eq!( + $name::from_percent(61).saturating_reciprocal_mul(<$type>::from(10u8)), + 16, + ); + // round up + assert_eq!( + $name::from_percent(61).saturating_reciprocal_mul_ceil(<$type>::from(10u8)), + 17, + ); + } + + #[test] + fn saturating_truncating_mul_works() { + assert_eq!( + $name::from_percent(49).mul_floor(10 as $type), + 4, + ); + let a: $upper_type = $name::from_percent(50).mul_floor(($max as $upper_type).pow(2)); + let b: $upper_type = ($max as $upper_type).pow(2) / 2; + if $max % 2 == 0 { + assert_eq!(a, b); + } else { + // difference should be less that 1%, IE less than the error in `from_percent` + assert!(b - a < ($max as $upper_type).pow(2) / 100 as $upper_type); + } + } + + #[test] + fn rational_mul_correction_works() { + assert_eq!( + super::rational_mul_correction::<$type, $name>( + <$type>::max_value(), + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + 0, + ); + assert_eq!( + super::rational_mul_correction::<$type, $name>( + <$type>::max_value() - 1, + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + <$type>::max_value() - 1, + ); + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + ((<$type>::max_value() - 1) as $upper_type).pow(2), + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + 1, + ); + // ((max^2 - 1) % max) * max / max == max - 1 + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2) - 1, + <$type>::max_value(), + <$type>::max_value(), + super::Rounding::Nearest, + ), + (<$type>::max_value() - 1).into(), + ); + // (max % 2) * max / 2 == max / 2 + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2), + <$type>::max_value(), + 2 as $type, + super::Rounding::Nearest, + ), + <$type>::max_value() as $upper_type / 2, + ); + // ((max^2 - 1) % max) * 2 / max == 2 (rounded up) + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2) - 1, + 2 as $type, + <$type>::max_value(), + super::Rounding::Nearest, + ), + 2, + ); + // ((max^2 - 1) % max) * 2 / max == 1 (rounded down) + assert_eq!( + super::rational_mul_correction::<$upper_type, $name>( + (<$type>::max_value() as $upper_type).pow(2) - 1, + 2 as $type, + <$type>::max_value(), + super::Rounding::Down, + ), + 1, + ); + } } }; } diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 4c3fd6f25a..09f8c78910 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -107,17 +107,20 @@ impl + Sized> UniqueSaturatedInto for S { /// Simple trait to use checked mul and max value to give a saturated mul operation over /// supported types. pub trait Saturating { - /// Saturated addition - if the product can't fit in the type then just use max-value. - fn saturating_add(self, o: Self) -> Self; + /// Saturating addition. Compute `self + rhs`, saturating at the numeric bounds instead of + /// overflowing. + fn saturating_add(self, rhs: Self) -> Self; - /// Saturated subtraction - if the product can't fit in the type then just use max-value. - fn saturating_sub(self, o: Self) -> Self; + /// Saturating subtraction. Compute `self - rhs`, saturating at the numeric bounds instead of + /// overflowing. + fn saturating_sub(self, rhs: Self) -> Self; - /// Saturated multiply - if the product can't fit in the type then just use max-value. - fn saturating_mul(self, o: Self) -> Self; + /// Saturating multiply. Compute `self * rhs`, saturating at the numeric bounds instead of + /// overflowing. + fn saturating_mul(self, rhs: Self) -> Self; } -impl Saturating for T { +impl Saturating for T { fn saturating_add(self, o: Self) -> Self { ::saturating_add(self, o) } @@ -125,7 +128,14 @@ impl Saturating for T { ::saturating_sub(self, o) } fn saturating_mul(self, o: Self) -> Self { - self.checked_mul(&o).unwrap_or_else(Bounded::max_value) + self.checked_mul(&o) + .unwrap_or_else(|| + if (self < T::zero()) != (o < T::zero()) { + Bounded::min_value() + } else { + Bounded::max_value() + } + ) } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 25e591e4c8..533857008a 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -68,7 +68,10 @@ pub use sp_application_crypto::{RuntimeAppPublic, BoundToRuntimeAppPublic}; pub use sp_core::RuntimeDebug; /// Re-export top-level arithmetic stuff. -pub use sp_arithmetic::{Perquintill, Perbill, Permill, Percent, Rational128, Fixed64, FixedPointNumber, Fixed128, PerThing}; +pub use sp_arithmetic::{ + PerThing, traits::SaturatedConversion, Perquintill, Perbill, Permill, Percent, PerU16, + Rational128, FixedI64, FixedI128, FixedU128, FixedPointNumber, FixedPointOperand, +}; /// Re-export 128 bit helpers. pub use sp_arithmetic::helpers_128bit; /// Re-export big_uint stuff. From c0f1764c09cbd1240d144f3112513d5ca6785859 Mon Sep 17 00:00:00 2001 From: AlexSedNZ Date: Thu, 1 Oct 2020 12:04:38 +1300 Subject: [PATCH 3/3] Ignore fragile tests --- primitives/arithmetic/src/fixed_point.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index b5cfad4642..37c24aad96 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -659,6 +659,7 @@ macro_rules! implement_fixed { } #[test] + #[ignore] #[should_panic(expected = "attempt to negate with overflow")] fn op_neg_panics() { let a = $name::min_value(); @@ -702,6 +703,7 @@ macro_rules! implement_fixed { } #[test] + #[ignore] #[should_panic(expected = "attempt to add with overflow")] fn op_add_panics() { let a = $name::max_value(); @@ -725,6 +727,7 @@ macro_rules! implement_fixed { } #[test] + #[ignore] #[should_panic(expected = "attempt to subtract with overflow")] fn op_sub_panics() { let a = $name::min_value();