From 21fae718c478f91bafe132242edc63169211f243 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 10 Oct 2019 09:52:08 +0200 Subject: [PATCH] Multi-limb arithmetic for runtime (#3743) * First working version of all operations. * New and improved version of everything. * Minor cleanup. * Fix build * Finalize nignum * Some final works on refactors and tests. * fix build * Some review comments * Bench, better try into and nits * mutify the API * rename to big_uint * unmutify. * Remove resize * Apply suggestions from code review * Update core/sr-primitives/src/sr_arithmetic.rs Co-Authored-By: thiolliere * BEtter proof * Fix panic doc. * Bump. --- substrate/core/phragmen/src/lib.rs | 12 +- substrate/core/sr-primitives/Cargo.toml | 1 + substrate/core/sr-primitives/src/lib.rs | 10 +- .../core/sr-primitives/src/sr_arithmetic.rs | 1197 ++++++++++++++--- substrate/node/runtime/src/lib.rs | 4 +- 5 files changed, 1014 insertions(+), 210 deletions(-) diff --git a/substrate/core/phragmen/src/lib.rs b/substrate/core/phragmen/src/lib.rs index 5295d0f422..e15a857b5f 100644 --- a/substrate/core/phragmen/src/lib.rs +++ b/substrate/core/phragmen/src/lib.rs @@ -34,8 +34,8 @@ #![cfg_attr(not(feature = "std"), no_std)] use rstd::{prelude::*, collections::btree_map::BTreeMap}; -use sr_primitives::{helpers_128bit::multiply_by_rational_best_effort, Perbill, Rational128}; -use sr_primitives::traits::{Zero, Convert, Member, SimpleArithmetic, Saturating}; +use sr_primitives::{helpers_128bit::multiply_by_rational, Perbill, Rational128}; +use sr_primitives::traits::{Zero, Convert, Member, SimpleArithmetic, Saturating, Bounded}; mod mock; mod tests; @@ -253,11 +253,11 @@ pub fn elect( for e in &n.edges { let c = &mut candidates[e.candidate_index]; if !c.elected && !c.approval_stake.is_zero() { - let temp_n = multiply_by_rational_best_effort( + let temp_n = multiply_by_rational( n.load.n(), n.budget, c.approval_stake, - ); + ).unwrap_or(Bounded::max_value()); let temp_d = n.load.d(); let temp = Rational128::from(temp_n, temp_d); c.score = c.score.lazy_saturating_add(temp); @@ -303,11 +303,11 @@ pub fn elect( if e.load.d() == n.load.d() { // return e.load / n.load. let desired_scale: u128 = Perbill::accuracy().into(); - multiply_by_rational_best_effort( + multiply_by_rational( desired_scale, e.load.n(), n.load.n(), - ) + ).unwrap_or(Bounded::max_value()) } else { // defensive only. Both edge and nominator loads are built from // scores, hence MUST have the same denominator. diff --git a/substrate/core/sr-primitives/Cargo.toml b/substrate/core/sr-primitives/Cargo.toml index 1074e509a6..2ffe8a010d 100644 --- a/substrate/core/sr-primitives/Cargo.toml +++ b/substrate/core/sr-primitives/Cargo.toml @@ -26,6 +26,7 @@ rand = "0.7.2" substrate-offchain = { path = "../offchain" } [features] +bench = [] default = ["std"] std = [ "num-traits/std", diff --git a/substrate/core/sr-primitives/src/lib.rs b/substrate/core/sr-primitives/src/lib.rs index 36d785f9a6..3c75bae849 100644 --- a/substrate/core/sr-primitives/src/lib.rs +++ b/substrate/core/sr-primitives/src/lib.rs @@ -19,6 +19,10 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] +// to allow benchmarking +#![cfg_attr(feature = "bench", feature(test))] +#[cfg(feature = "bench")] extern crate test; + #[doc(hidden)] pub use codec; #[cfg(feature = "std")] @@ -59,13 +63,15 @@ pub use generic::{DigestItem, Digest}; pub use primitives::{TypeId, crypto::{key_types, KeyTypeId, CryptoType}}; pub use app_crypto::RuntimeAppPublic; -/// Re-export arithmetic stuff. +/// Re-export top-level arithmetic stuff. pub use sr_arithmetic::{ Perquintill, Perbill, Permill, Percent, Rational128, Fixed64 }; -/// Re-export 128 bit helpers from sr_arithmetic +/// Re-export 128 bit helpers. pub use sr_arithmetic::helpers_128bit; +/// Re-export big_uint stiff. +pub use sr_arithmetic::biguint; #[cfg(feature = "std")] pub use externalities::set_and_run_with_externalities; diff --git a/substrate/core/sr-primitives/src/sr_arithmetic.rs b/substrate/core/sr-primitives/src/sr_arithmetic.rs index 20a7f51c1f..043a383a33 100644 --- a/substrate/core/sr-primitives/src/sr_arithmetic.rs +++ b/substrate/core/sr-primitives/src/sr_arithmetic.rs @@ -20,9 +20,8 @@ use crate::serde::{Serialize, Deserialize}; use rstd::{ - ops, prelude::*, - convert::{TryInto, TryFrom}, - cmp::Ordering, + ops, cmp::Ordering, prelude::*, + convert::{TryFrom, TryInto}, }; use codec::{Encode, Decode}; use crate::traits::{ @@ -669,16 +668,930 @@ impl CheckedAdd for Fixed64 { } } +/// Infinite precision unsigned integer for substrate runtime. +pub mod biguint { + use super::Zero; + use rstd::{cmp::Ordering, ops, prelude::*, cell::RefCell, convert::TryFrom}; + + // A sensible value for this would be half of the dword size of the host machine. Since the + // runtime is compiled to 32bit webassembly, using 32 and 64 for single and double respectively + // should yield the most performance. TODO #3745 we could benchmark this and verify. + /// Representation of a single limb. + pub type Single = u32; + /// Representation of two limbs. + pub type Double = u64; + /// Difference in the number of bits of [`Single`] and [`Double`]. + const SHIFT: usize = 32; + /// short form of _Base_. Analogous to the value 10 in base-10 decimal numbers. + const B: Double = Single::max_value() as Double + 1; + + /// Splits a [`Double`] limb number into a tuple of two [`Single`] limb numbers. + pub fn split(a: Double) -> (Single, Single) { + let al = a as Single; + let ah = (a >> SHIFT) as Single; + (ah, al) + } + + /// Assumed as a given primitive. + /// + /// Multiplication of two singles, which at most yields 1 double. + pub fn mul_single(a: Single, b: Single) -> Double { + let a: Double = a.into(); + let b: Double = b.into(); + let r = a * b; + r + } + + /// Assumed as a given primitive. + /// + /// Addition of two singles, which at most takes a single limb of result and a carry, + /// returned as a tuple respectively. + pub fn add_single(a: Single, b: Single) -> (Single, Single) { + let a: Double = a.into(); + let b: Double = b.into(); + let q = a + b; + let (carry, r) = split(q); + (r, carry) + } + + /// Assumed as a given primitive. + /// + /// Division of double by a single limb. Always returns a double limb of quotient and a single + /// limb of remainder. + fn div_single(a: Double, b: Single) -> (Double, Single) { + let b: Double = b.into(); + let q = a / b; + let r = a % b; + // both conversions are trivially safe. + (q, r as Single) + } + + /// Simple wrapper around an infinitely large integer, represented as limbs of [`Single`]. + #[derive(Clone, Default)] + pub struct BigUint { + /// digits (limbs) of this number (sorted as msb -> lsd). + pub(crate) digits: Vec, + } + + impl BigUint { + /// Create a new instance with `size` limbs. This prevents any number with zero limbs to be + /// created. + /// + /// The behavior of the type is undefined with zero limbs. + pub fn with_capacity(size: usize) -> Self { + Self { digits: vec![0; size.max(1)] } + } + + /// Raw constructor from custom limbs. If `limbs` is empty, `Zero::zero()` implementation is + /// used. + pub fn from_limbs(limbs: &[Single]) -> Self { + if limbs.len() > 0 { + Self { digits: limbs.to_vec() } + } else { + Zero::zero() + } + } + + /// Number of limbs. + pub fn len(&self) -> usize { self.digits.len() } + + /// A naive getter for limb at `index`. Note that the order is lsb -> msb. + /// + /// #### Panics + /// + /// This panics if index is out of range. + pub fn get(&self, index: usize) -> Single { + self.digits[self.len() - 1 - index] + } + + /// A naive getter for limb at `index`. Note that the order is lsb -> msb. + pub fn checked_get(&self, index: usize) -> Option { + if let Some(i) = self.len().checked_sub(1) { + if let Some(j) = i.checked_sub(index) { + return self.digits.get(j).cloned(); + } + } + return None; + } + + /// A naive setter for limb at `index`. Note that the order is lsb -> msb. + /// + /// #### Panics + /// + /// This panics if index is out of range. + pub fn set(&mut self, index: usize, value: Single) { + let len = self.digits.len(); + self.digits[len - 1 - index] = value; + } + + /// returns the least significant limb of the number. + /// + /// #### Panics + /// + /// While the constructor of the type prevents this, this can panic if `self` has no digits. + pub fn lsb(&self) -> Single { + self.digits[self.len() - 1] + } + + /// returns the most significant limb of the number. + /// + /// #### Panics + /// + /// While the constructor of the type prevents this, this can panic if `self` has no digits. + pub fn msb(&self) -> Single { + self.digits[0] + } + + /// Strips zeros from the left side of `self`, if any. + pub fn lstrip(&mut self) { + // by definition, a big-int number should never have leading zero limbs. This function + // has the ability to cause this. There is nothing to do if the number already has 1 + // limb only. call it a day and return. + if self.len().is_zero() { return; } + let mut index = 0; + for elem in self.digits.iter() { + if *elem != 0 { break } else { index += 1 } + } + if index > 0 { + self.digits = self.digits[index..].to_vec() + } + } + + /// Zero-pad `self` from left to reach `size` limbs. Will not make any difference if `self` + /// is already bigger than `size` limbs. + pub fn lpad(&mut self, size: usize) { + let n = self.len(); + if n >= size { return; } + let pad = size - n; + let mut new_digits = (0..pad).map(|_| 0).collect::>(); + new_digits.extend(self.digits.iter()); + self.digits = new_digits; + } + + /// Adds `self` with `other`. self and other do not have to have any particular size. Given + /// that the `n = max{size(self), size(other)}`, it will produce a number with `n + 1` + /// limbs. + /// + /// This function does not strip the output and returns the original allocated `n + 1` + /// limbs. The caller may strip the output if desired. + /// + /// Taken from "The Art of Computer Programming" by D.E. Knuth, vol 2, chapter 4. + pub fn add(self, other: &Self) -> Self { + let n = self.len().max(other.len()); + let mut k: Double = 0; + let mut w = Self::with_capacity(n + 1); + + for j in 0..n { + let u = Double::from(self.checked_get(j).unwrap_or(0)); + let v = Double::from(other.checked_get(j).unwrap_or(0)); + let s = u + v + k; + w.set(j, (s % B) as Single); + k = s / B; + } + // k is always 0 or 1. + w.set(n, k as Single); + w + } + + /// Subtracts `other` from `self`. self and other do not have to have any particular size. + /// Given that the `n = max{size(self), size(other)}`, it will produce a number of size `n`. + /// + /// If `other` is bigger than `self`, `Err(B - borrow)` is returned. + /// + /// Taken from "The Art of Computer Programming" by D.E. Knuth, vol 2, chapter 4. + pub fn sub(self, other: &Self) -> Result { + let n = self.len().max(other.len()); + let mut k = 0; + let mut w = Self::with_capacity(n); + for j in 0..n { + let s = { + let u = Double::from(self.checked_get(j).unwrap_or(0)); + let v = Double::from(other.checked_get(j).unwrap_or(0)); + let mut needs_borrow = false; + let mut t = 0; + + if let Some(v) = u.checked_sub(v) { + if let Some(v2) = v.checked_sub(k) { + t = v2 % B; + k = 0; + } else { + needs_borrow = true; + } + } else { + needs_borrow = true; + } + if needs_borrow { + t = u + B - v - k; + k = 1; + } + t + }; + // PROOF: t either comes from `v2 % B`, or from `u + B - v - k`. The former is + // trivial. The latter will not overflow this branch will only happen if the sum of + // `u - v - k` part has been negative, hence `u + B - v - k < b`. + w.set(j, s as Single); + } + + if k.is_zero() { + Ok(w) + } else { + Err(w) + } + } + + /// Multiplies n-limb number `self` with m-limb number `other`. + /// + /// The resulting number will always have `n + m` limbs. + /// + /// This function does not strip the output and returns the original allocated `n + m` + /// limbs. The caller may strip the output if desired. + /// + /// Taken from "The Art of Computer Programming" by D.E. Knuth, vol 2, chapter 4. + pub fn mul(self, other: &Self) -> Self { + let n = self.len(); + let m = other.len(); + let mut w = Self::with_capacity(m + n); + + for j in 0..n { + if self.get(j) == 0 { + // Note: `with_capacity` allocates with 0. Explicitly set j + m to zero if + // otherwise. + continue; + } + + let mut k = 0; + for i in 0..m { + // PROOF: (B−1) × (B−1) + (B−1) + (B−1) = B^2 −1 < B^2. addition is safe. + let t = + mul_single(self.get(j), other.get(i)) + + Double::from(w.get(i + j)) + + Double::from(k); + w.set(i + j, (t % B) as Single); + // PROOF: (B^2 - 1) / B < B. conversion is safe. + k = (t / B) as Single; + } + w.set(j + m, k); + } + w + } + + /// Divides `self` by a single limb `other`. This can be used in cases where the original + /// division cannot work due to the divisor (`other`) being just one limb. + /// + /// Invariant: `other` cannot be zero. + pub fn div_unit(self, mut other: Single) -> Self { + other = other.max(1); + let n = self.len(); + let mut out = Self::with_capacity(n); + let mut r: Single = 0; + // PROOF: (B-1) * B + (B-1) still fits in double + let with_r = |x: Double, r: Single| { r as Double * B + x }; + for d in (0..=n-1).rev() { + let (q, rr) = div_single(with_r(self.get(d).into(), r), other) ; + out.set(d, q as Single); + r = rr; + } + out + } + + /// Divides an `n + m` limb self by a `n` limb `other`. The result is a `m + 1` limb + /// quotient and a `n` limb remainder, if enabled by passing `true` in `rem` argument, both + /// in the form of an option's `Ok`. + /// + /// - requires `other` to be stripped and have no leading zeros. + /// - requires `self` to be stripped and have no leading zeros. + /// - requires `other` to have at least two limbs. + /// - requires `self` to have a greater length compared to `other`. + /// + /// All arguments are examined without being stripped for the above conditions. If any of + /// the above fails, `None` is returned.` + /// + /// Taken from "The Art of Computer Programming" by D.E. Knuth, vol 2, chapter 4. + pub fn div(self, other: &Self, rem: bool) -> Option<(Self, Self)> { + if other.len() <= 1 + || other.msb() == 0 + || self.msb() == 0 + || self.len() <= other.len() + { + return None + } + let n = other.len(); + let m = self.len() - n; + + let mut q = Self::with_capacity(m + 1); + let mut r = Self::with_capacity(n); + + debug_assert!(other.msb() != 0); + + // PROOF: 0 <= normalizer_bits < SHIFT 0 <= normalizer < B. all conversions are + // safe. + let normalizer_bits = other.msb().leading_zeros() as Single; + let normalizer = (2 as Single).pow(normalizer_bits as u32) as Single; + + // step D1. + let mut self_norm = self.mul(&Self::from(normalizer)); + let mut other_norm = other.clone().mul(&Self::from(normalizer)); + + // defensive only; the mul implementation should always create this. + self_norm.lpad(n + m + 1); + other_norm.lstrip(); + + // step D2. + for j in (0..=m).rev() { + // step D3.0 Find an estimate of q[j], named qhat. + let (qhat, rhat) = { + // PROOF: this always fits into `Double`. In the context of Single = u8, and + // Double = u16, think of 255 * 256 + 255 which is just u16::max_value(). + let dividend = + Double::from(self_norm.get(j + n)) + * B + + Double::from(self_norm.get(j + n - 1)); + let divisor = other_norm.get(n - 1); + div_single(dividend, divisor.into()) + }; + + // D3.1 test qhat + // replace qhat and rhat with RefCells. This helps share state with the closure + let qhat = RefCell::new(qhat); + let rhat = RefCell::new(rhat as Double); + + let test = || { + // decrease qhat if it is bigger than the base (B) + let qhat_local = *qhat.borrow(); + let rhat_local = *rhat.borrow(); + let predicate_1 = qhat_local >= B; + let predicate_2 = { + let lhs = qhat_local * other_norm.get(n - 2) as Double; + let rhs = B * rhat_local + self_norm.get(j + n - 2) as Double; + lhs > rhs + }; + if predicate_1 || predicate_2 { + *qhat.borrow_mut() -= 1; + *rhat.borrow_mut() += other_norm.get(n - 1) as Double; + true + } else { + false + } + }; + + test(); + while (*rhat.borrow() as Double) < B { + if !test() { break; } + } + + let qhat = qhat.into_inner(); + // we don't need rhat anymore. just let it go out of scope when it does. + + // step D4 + let lhs = Self { digits: (j..=j+n).rev().map(|d| self_norm.get(d)).collect() }; + let rhs = other_norm.clone().mul(&Self::from(qhat)); + + let maybe_sub = lhs.sub(&rhs); + let mut negative = false; + let sub = match maybe_sub { + Ok(t) => t, + Err(t) => { negative = true; t } + }; + (j..=j+n).for_each(|d| { self_norm.set(d, sub.get(d - j)); }); + + // step D5 + // PROOF: the `test()` specifically decreases qhat until it is below `B`. conversion + // is safe. + q.set(j, qhat as Single); + + // step D6: add back if negative happened. + if negative { + q.set(j, q.get(j) - 1); + let u = Self { digits: (j..=j+n).rev().map(|d| self_norm.get(d)).collect() }; + let r = other_norm.clone().add(&u); + (j..=j+n).rev().for_each(|d| { self_norm.set(d, r.get(d - j)); }) + } + } + + // if requested, calculate remainder. + if rem { + // undo the normalization. + if normalizer_bits > 0 { + let s = SHIFT as u32; + let nb = normalizer_bits; + for d in 0..n-1 { + let v = self_norm.get(d) >> nb + | self_norm.get(d + 1).overflowing_shl(s - nb).0; + r.set(d, v); + } + r.set(n - 1, self_norm.get(n - 1) >> normalizer_bits); + } else { + r = self_norm; + } + } + + Some((q, r)) + } + } + + #[cfg(feature = "std")] + impl rstd::fmt::Debug for BigUint { + fn fmt(&self, f: &mut rstd::fmt::Formatter<'_>) -> rstd::fmt::Result { + write!( + f, + "BigUint {{ {:?} ({:?})}}", + self.digits, + u128::try_from(self.clone()).unwrap_or_else(|_| 0), + ) + } + } + + impl PartialEq for BigUint { + fn eq(&self, other: &Self) -> bool { + // sadly, we have to reallocate here as strip mutably uses self. + let mut lhs = self.clone(); + let mut rhs = other.clone(); + lhs.lstrip(); + rhs.lstrip(); + lhs.digits.eq(&rhs.digits) + } + } + + impl Eq for BigUint {} + + impl Ord for BigUint { + fn cmp(&self, other: &Self) -> Ordering { + let lhs_first = self.digits.iter().position(|&e| e != 0); + let rhs_first = other.digits.iter().position(|&e| e != 0); + + match (lhs_first, rhs_first) { + // edge cases that should not happen. This basically means that one or both were + // zero. + (None, None) => Ordering::Equal, + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (Some(lhs_idx), Some(rhs_idx)) => { + let lhs = &self.digits[lhs_idx..]; + let rhs = &other.digits[rhs_idx..]; + let len_cmp = lhs.len().cmp(&rhs.len()); + match len_cmp { + Ordering::Equal => lhs.cmp(rhs), + _ => len_cmp, + } + } + } + } + } + + impl PartialOrd for BigUint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl ops::Add for BigUint { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + self.add(&rhs) + } + } + + impl ops::Sub for BigUint { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + self.sub(&rhs).unwrap_or_else(|e| e) + } + } + + impl ops::Mul for BigUint { + type Output = Self; + fn mul(self, rhs: Self) -> Self::Output { + self.mul(&rhs) + } + } + + impl Zero for BigUint { + fn zero() -> Self { + Self { digits: vec![Zero::zero()] } + } + + fn is_zero(&self) -> bool { + self.digits.iter().all(|d| d.is_zero()) + } + } + + macro_rules! impl_try_from_number_for { + ($([$type:ty, $len:expr]),+) => { + $( + impl TryFrom for $type { + type Error = &'static str; + fn try_from(mut value: BigUint) -> Result<$type, Self::Error> { + value.lstrip(); + let error_message = concat!("cannot fit a number into ", stringify!($type)); + if value.len() * SHIFT > $len { + Err(error_message) + } else { + let mut acc: $type = Zero::zero(); + for (i, d) in value.digits.iter().rev().cloned().enumerate() { + let d: $type = d.into(); + acc += d << (SHIFT * i); + } + Ok(acc) + } + } + } + )* + }; + } + // can only be implemented for sizes bigger than two limb. + impl_try_from_number_for!([u128, 128], [u64, 64]); + + macro_rules! impl_from_for_smaller_than_word { + ($($type:ty),+) => { + $(impl From<$type> for BigUint { + fn from(a: $type) -> Self { + Self { digits: vec! [a.into()] } + } + })* + } + } + impl_from_for_smaller_than_word!(u8, u16, Single); + + impl From for BigUint { + fn from(a: Double) -> Self { + let (ah, al) = split(a); + Self { digits: vec![ah, al] } + } + } + + #[cfg(test)] + pub mod tests_biguint { + use super::*; + use rand::Rng; + #[cfg(feature = "bench")] + use test::Bencher; + + // TODO move into a proper fuzzer #3745 + const FUZZ_COUNT: usize = 100_000; + + pub fn random_big_uint(size: usize) -> BigUint { + let mut rng = rand::thread_rng(); + let digits = (0..size).map(|_| rng.gen_range(0, Single::max_value())).collect(); + BigUint { digits } + } + + fn run_with_data_set( + count: usize, + limbs_ub_1: usize, + limbs_ub_2: usize, + exact: bool, + assertion: F, + ) where + F: Fn(BigUint, BigUint) -> () + { + let mut rng = rand::thread_rng(); + for _ in 0..count { + let digits_len_1 = if exact { limbs_ub_1 } else { rng.gen_range(1, limbs_ub_1) }; + let digits_len_2 = if exact { limbs_ub_2 } else { rng.gen_range(1, limbs_ub_2) }; + + let u = random_big_uint(digits_len_1); + let v = random_big_uint(digits_len_2); + assertion(u, v); + } + } + + fn with_limbs(n: usize) -> BigUint { + BigUint { digits: vec![1; n] } + } + + #[test] + fn split_works() { + let a = SHIFT / 2; + let b = SHIFT * 3 / 2; + let num: Double = 1 << a | 1 << b; + // example when `Single = u8` + // assert_eq!(num, 0b_0001_0000_0001_0000) + assert_eq!(split(num), (1 << a, 1 << a)); + } + + #[test] + fn strip_works() { + let mut a = BigUint::from_limbs(&[0, 1, 0]); + a.lstrip(); + assert_eq!(a, BigUint { digits: vec![1, 0] }); + + let mut a = BigUint::from_limbs(&[0, 0, 1]); + a.lstrip(); + assert_eq!(a, BigUint { digits: vec![1] }); + + let mut a = BigUint::from_limbs(&[0, 0]); + a.lstrip(); + assert_eq!(a, BigUint { digits: vec![0] }); + + let mut a = BigUint::from_limbs(&[0, 0, 0]); + a.lstrip(); + assert_eq!(a, BigUint { digits: vec![0] }); + } + + #[test] + fn lpad_works() { + let mut a = BigUint::from_limbs(&[0, 1, 0]); + a.lpad(2); + assert_eq!(a.digits, vec![0, 1, 0]); + + let mut a = BigUint::from_limbs(&[0, 1, 0]); + a.lpad(3); + assert_eq!(a.digits, vec![0, 1, 0]); + + let mut a = BigUint::from_limbs(&[0, 1, 0]); + a.lpad(4); + assert_eq!(a.digits, vec![0, 0, 1, 0]); + } + + #[test] + fn equality_works() { + assert_eq!( + BigUint { digits: vec![1, 2, 3] } == BigUint { digits: vec![1, 2, 3] }, + true, + ); + assert_eq!( + BigUint { digits: vec![3, 2, 3] } == BigUint { digits: vec![1, 2, 3] }, + false, + ); + assert_eq!( + BigUint { digits: vec![0, 1, 2, 3] } == BigUint { digits: vec![1, 2, 3] }, + true, + ); + } + + #[test] + fn ordering_works() { + assert!(BigUint { digits: vec![0] } < BigUint { digits: vec![1] }); + assert!(BigUint { digits: vec![0] } == BigUint { digits: vec![0] }); + assert!(BigUint { digits: vec![] } == BigUint { digits: vec![0] }); + assert!(BigUint { digits: vec![] } == BigUint { digits: vec![] }); + assert!(BigUint { digits: vec![] } < BigUint { digits: vec![1] }); + + assert!(BigUint { digits: vec![1, 2, 3] } == BigUint { digits: vec![1, 2, 3] }); + assert!(BigUint { digits: vec![0, 1, 2, 3] } == BigUint { digits: vec![1, 2, 3] }); + + assert!(BigUint { digits: vec![1, 2, 4] } > BigUint { digits: vec![1, 2, 3] }); + assert!(BigUint { digits: vec![0, 1, 2, 4] } > BigUint { digits: vec![1, 2, 3] }); + assert!(BigUint { digits: vec![1, 2, 1, 0] } > BigUint { digits: vec![1, 2, 3] }); + + assert!(BigUint { digits: vec![0, 1, 2, 1] } < BigUint { digits: vec![1, 2, 3] }); + } + + #[test] + fn basic_random_ord_eq_works() { + type S = u128; + run_with_data_set(FUZZ_COUNT, 4, 4, false, |u, v| { + let ue = S::try_from(u.clone()).unwrap(); + let ve = S::try_from(v.clone()).unwrap(); + assert_eq!(u.cmp(&v), ue.cmp(&ve)); + assert_eq!(u.eq(&v), ue.eq(&ve)); + }) + } + + #[test] + fn can_try_build_numbers_from_types() { + use rstd::convert::TryFrom; + assert_eq!(u64::try_from(with_limbs(1)).unwrap(), 1); + assert_eq!(u64::try_from(with_limbs(2)).unwrap(), u32::max_value() as u64 + 2); + assert_eq!( + u64::try_from(with_limbs(3)).unwrap_err(), + "cannot fit a number into u64", + ); + assert_eq!( + u128::try_from(with_limbs(3)).unwrap(), + u32::max_value() as u128 + u64::max_value() as u128 + 3 + ); + } + + #[test] + fn zero_works() { + assert_eq!(BigUint::zero(), BigUint { digits: vec![0] }); + assert_eq!(BigUint { digits: vec![0, 1, 0] }.is_zero(), false); + assert_eq!(BigUint { digits: vec![0, 0, 0] }.is_zero(), true); + + let a = BigUint::zero(); + let b = BigUint::zero(); + let c = a * b; + assert_eq!(c.digits, vec![0, 0]); + } + + #[test] + fn basic_random_add_works() { + type S = u128; + run_with_data_set(FUZZ_COUNT, 3, 3, false, |u, v| { + let expected = S::try_from(u.clone()).unwrap() + S::try_from(v.clone()).unwrap(); + let t = u.clone().add(&v); + assert_eq!( + S::try_from(t.clone()).unwrap(), expected, + "{:?} + {:?} ===> {:?} != {:?}", u, v, t, expected, + ); + }) + } + + #[test] + fn sub_negative_works() { + assert_eq!( + BigUint::from(10 as Single).sub(&BigUint::from(5 as Single)).unwrap(), + BigUint::from(5 as Single) + ); + assert_eq!( + BigUint::from(10 as Single).sub(&BigUint::from(10 as Single)).unwrap(), + BigUint::from(0 as Single) + ); + assert_eq!( + BigUint::from(10 as Single).sub(&BigUint::from(13 as Single)).unwrap_err(), + BigUint::from((B - 3) as Single), + ); + } + + #[test] + fn basic_random_sub_works() { + type S = u128; + run_with_data_set(FUZZ_COUNT, 4, 4, false, |u, v| { + let expected = S::try_from(u.clone()).unwrap() + .checked_sub(S::try_from(v.clone()).unwrap()); + let t = u.clone().sub(&v); + if expected.is_none() { + assert!(t.is_err()) + } else { + let t = t.unwrap(); + let expected = expected.unwrap(); + assert_eq!( + S::try_from(t.clone()).unwrap(), expected, + "{:?} - {:?} ===> {:?} != {:?}", u, v, t, expected, + ); + } + }) + } + + #[test] + fn basic_random_mul_works() { + type S = u128; + run_with_data_set(FUZZ_COUNT, 2, 2, false, |u, v| { + let expected = S::try_from(u.clone()).unwrap() * S::try_from(v.clone()).unwrap(); + let t = u.clone().mul(&v); + assert_eq!( + S::try_from(t.clone()).unwrap(), expected, + "{:?} * {:?} ===> {:?} != {:?}", u, v, t, expected, + ); + }) + } + + #[test] + fn mul_always_appends_one_digit() { + let a = BigUint::from(10 as Single); + let b = BigUint::from(4 as Single); + assert_eq!(a.len(), 1); + assert_eq!(b.len(), 1); + + let n = a.mul(&b); + + assert_eq!(n.len(), 2); + assert_eq!(n.digits, vec![0, 40]); + } + + #[test] + fn div_conditions_work() { + let a = BigUint { digits: vec![2] }; + let b = BigUint { digits: vec![1, 2] }; + let c = BigUint { digits: vec![1, 1, 2] }; + let d = BigUint { digits: vec![0, 2] }; + let e = BigUint { digits: vec![0, 1, 1, 2] }; + + assert!(a.clone().div(&b, true).is_none()); + assert!(c.clone().div(&a, true).is_none()); + assert!(c.clone().div(&d, true).is_none()); + assert!(e.clone().div(&a, true).is_none()); + + assert!(c.clone().div(&b, true).is_some()); + } + + #[test] + fn div_unit_works() { + let a = BigUint { digits: vec![100] }; + let b = BigUint { digits: vec![1, 100] }; + + assert_eq!(a.clone().div_unit(1), a); + assert_eq!(a.clone().div_unit(0), a); + assert_eq!(a.clone().div_unit(2), BigUint::from(50 as Single)); + assert_eq!(a.clone().div_unit(7), BigUint::from(14 as Single)); + + assert_eq!(b.clone().div_unit(1), b); + assert_eq!(b.clone().div_unit(0), b); + assert_eq!(b.clone().div_unit(2), BigUint::from(((B + 100) / 2) as Single)); + assert_eq!(b.clone().div_unit(7), BigUint::from(((B + 100) / 7) as Single)); + + } + + #[test] + fn basic_random_div_works() { + type S = u128; + run_with_data_set(FUZZ_COUNT, 4, 4, false, |u, v| { + let ue = S::try_from(u.clone()).unwrap(); + let ve = S::try_from(v.clone()).unwrap(); + let (q, r) = (ue / ve, ue % ve); + if let Some((qq, rr)) = u.clone().div(&v, true) { + assert_eq!( + S::try_from(qq.clone()).unwrap(), q, + "{:?} / {:?} ===> {:?} != {:?}", u, v, qq, q, + ); + assert_eq!( + S::try_from(rr.clone()).unwrap(), r, + "{:?} % {:?} ===> {:?} != {:?}", u, v, rr, r, + ); + } else if v.len() == 1 { + let qq = u.clone().div_unit(ve as Single); + assert_eq!( + S::try_from(qq.clone()).unwrap(), q, + "[single] {:?} / {:?} ===> {:?} != {:?}", u, v, qq, q, + ); + } else { + if v.msb() == 0 || v.msb() == 0 || u.len() <= v.len() {} // nada + else { panic!("div returned none for an unexpected reason"); } + } + }) + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_addition_2_digit(bencher: &mut Bencher) { + let a = random_big_uint(2); + let b = random_big_uint(2); + bencher.iter(|| { + let _ = a.clone().add(&b); + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_addition_4_digit(bencher: &mut Bencher) { + let a = random_big_uint(4); + let b = random_big_uint(4); + bencher.iter(|| { + let _ = a.clone().add(&b); + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_subtraction_2_digit(bencher: &mut Bencher) { + let a = random_big_uint(2); + let b = random_big_uint(2); + bencher.iter(|| { + let _ = a.clone().sub(&b); + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_subtraction_4_digit(bencher: &mut Bencher) { + let a = random_big_uint(4); + let b = random_big_uint(4); + bencher.iter(|| { + let _ = a.clone().sub(&b); + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_multiplication_2_digit(bencher: &mut Bencher) { + let a = random_big_uint(2); + let b = random_big_uint(2); + bencher.iter(|| { + let _ = a.clone().mul(&b); + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_multiplication_4_digit(bencher: &mut Bencher) { + let a = random_big_uint(4); + let b = random_big_uint(4); + bencher.iter(|| { + let _ = a.clone().mul(&b); + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_division_4_digit(bencher: &mut Bencher) { + let a = random_big_uint(4); + let b = random_big_uint(2); + bencher.iter(|| { + let _ = a.clone().div(&b, true); + }); + } + } +} + /// Some helper functions to work with 128bit numbers. Note that the functionality provided here is /// only sensible to use with 128bit numbers because for smaller sizes, you can always rely on /// assumptions of a bigger type (u128) being available, or simply create a per-thing and use the /// multiplication implementation provided there. pub mod helpers_128bit { - use super::Perquintill; + use crate::biguint; use crate::traits::Zero; - use rstd::cmp::{min, max}; - - const ERROR: &'static str = "can not accurately multiply by rational in this type"; + use rstd::{cmp::{min, max}, convert::TryInto}; /// Helper gcd function used in Rational128 implementation. pub fn gcd(a: u128, b: u128) -> u128 { @@ -695,18 +1608,31 @@ pub mod helpers_128bit { } } + /// split a u128 into two u64 limbs + pub fn split(a: u128) -> (u64, u64) { + let al = a as u64; + let ah = (a >> 64) as u64; + (ah, al) + } + + /// Convert a u128 to a u32 based biguint. + pub fn to_big_uint(x: u128) -> biguint::BigUint { + let (xh, xl) = split(x); + let (xhh, xhl) = biguint::split(xh); + let (xlh, xll) = biguint::split(xl); + let mut n = biguint::BigUint::from_limbs(&[xhh, xhl, xlh, xll]); + n.lstrip(); + n + } + /// Safely and accurately compute `a * b / c`. The approach is: /// - Simply try `a * b / c`. - /// - Else, swap the operations (divide first) if `a > c` (division is possible) and `b <= c` - /// (overflow cannot happen) - /// - At any point, given an overflow or accuracy loss, return an Error. + /// - Else, convert them both into big numbers and re-try. `Err` is returned if the result + /// cannot be safely casted back to u128. /// /// Invariant: c must be greater than or equal to 1. - /// This might not return Ok even if `b < c`. pub fn multiply_by_rational(a: u128, b: u128, c: u128) -> Result { if a.is_zero() || b.is_zero() { return Ok(Zero::zero()); } - - // invariant: C cannot be zero. let c = c.max(1); // a and b are interchangeable by definition in this function. It always helps to assume the @@ -719,69 +1645,31 @@ pub mod helpers_128bit { if let Some(x) = a.checked_mul(b) { // This is the safest way to go. Try it. Ok(x / c) - } else if a > c { - // if it can be safely swapped and it is a fraction, then swap. - let q = a / c; - let r = a % c; - let r_additional = multiply_by_rational(r, b, c)?; - - let q_part = q.checked_mul(b) - .ok_or(ERROR)?; - let result = q_part.checked_add(r_additional) - .ok_or(ERROR)?; - Ok(result) } else { - Err(ERROR) - } - } + let a_num = to_big_uint(a); + let b_num = to_big_uint(b); + let c_num = to_big_uint(c); - /// Performs [`multiply_by_rational`]. In case of failure, if `b < c` it tries to approximate - /// the ratio into a perquintillion and return a lossy result. Otherwise, a best effort approach - /// of shifting both b and c is performed until multiply_by_rational can work. - /// - /// This function can very well be lossy and as the name suggests, perform a best effort in the - /// scope of u128 numbers. In case `b > c` and overflow happens, `a` is returned. - /// - /// c must be greater than or equal to 1. - pub fn multiply_by_rational_best_effort(a: u128, b: u128, c: u128) -> u128 { - if a.is_zero() || b.is_zero() { return Zero::zero(); } - let c = c.max(1); - - // unwrap the loop once. This favours performance over readability. - multiply_by_rational(a, b, c).unwrap_or_else(|_| { - if b <= c { - let per_thing = Perquintill::from_rational_approximation(b, c); - per_thing * a + let mut ab = a_num * b_num; + ab.lstrip(); + let mut q = if c_num.len() == 1 { + // PROOF: if `c_num.len() == 1` then `c` fits in one limb. + ab.div_unit(c as biguint::Single) } else { - let mut shift = 1; - let mut shifted_b = b.checked_shr(shift).unwrap_or(0); - let mut shifted_c = c.checked_shr(shift).unwrap_or(0); - - loop { - if shifted_b.is_zero() || shifted_c.is_zero() { - break a - } - match multiply_by_rational(a, shifted_b, shifted_c) { - Ok(r) => break r, - Err(_) => { - shift = shift + 1; - // by the time we reach here, b >= 1 && c >= 1. Before panic, they have - // to be zero which is prevented to happen by the break. - shifted_b = b.checked_shr(shift) - .expect( - "b >= 1 && c >= 1; break prevents them from ever being zero; \ - panic can only happen after either is zero; qed" - ); - shifted_c = c.checked_shr(shift) - .expect( - "b >= 1 && c >= 1; break prevents them from ever being zero; \ - panic can only happen after either is zero; qed" - ); - } - } - } - } - }) + // PROOF: both `ab` and `c` cannot have leading zero limbs; if length of `c` is 1, + // the previous branch would handle. Also, if ab for sure has a bigger size than + // c, because `a.checked_mul(b)` has failed, hence ab must be at least one limb + // bigger than c. In this case, returning zero is defensive-only and div should + // always return Some. + let (mut q, r) = ab.div(&c_num, true).unwrap_or((Zero::zero(), Zero::zero())); + let r: u128 = r.try_into() + .expect("reminder of div by c is always less than c; qed"); + if r > (c / 2) { q = q.add(&to_big_uint(1)); } + q + }; + q.lstrip(); + q.try_into().map_err(|_| "result cannot fit in u128") + } } } @@ -829,13 +1717,7 @@ impl Rational128 { if den == self.1 { Ok(self) } else { - if den > self.1 { - let n = helpers_128bit::multiply_by_rational(self.0, den, self.1)?; - Ok(Self(n, den)) - } else { - let div = self.1 / den; - Ok(Self(self.0 / div.max(1), den)) - } + helpers_128bit::multiply_by_rational(self.0, den, self.1).map(|n| Self(n, den)) } } @@ -872,9 +1754,9 @@ impl Rational128 { /// /// Overflow might happen during any of the steps. Error is returned in such cases. pub fn checked_add(self, other: Self) -> Result { - let lcm = self.lcm(&other)?; - let self_scaled = self.to_den(lcm)?; - let other_scaled = other.to_den(lcm)?; + let lcm = self.lcm(&other).map_err(|_| "failed to scale to denominator")?; + let self_scaled = self.to_den(lcm).map_err(|_| "failed to scale to denominator")?; + let other_scaled = other.to_den(lcm).map_err(|_| "failed to scale to denominator")?; let n = self_scaled.0.checked_add(other_scaled.0) .ok_or("overflow while adding numerators")?; Ok(Self(n, self_scaled.1)) @@ -884,9 +1766,9 @@ impl Rational128 { /// /// Overflow might happen during any of the steps. None is returned in such cases. pub fn checked_sub(self, other: Self) -> Result { - let lcm = self.lcm(&other)?; - let self_scaled = self.to_den(lcm)?; - let other_scaled = other.to_den(lcm)?; + let lcm = self.lcm(&other).map_err(|_| "failed to scale to denominator")?; + let self_scaled = self.to_den(lcm).map_err(|_| "failed to scale to denominator")?; + let other_scaled = other.to_den(lcm).map_err(|_| "failed to scale to denominator")?; let n = self_scaled.0.checked_sub(other_scaled.0) .ok_or("overflow while subtracting numerators")?; @@ -900,7 +1782,6 @@ impl PartialOrd for Rational128 { } } -/// Note that this implementation can very well be lossy. TODO #3670 impl Ord for Rational128 { fn cmp(&self, other: &Self) -> Ordering { // handle some edge cases. @@ -911,49 +1792,31 @@ impl Ord for Rational128 { } else if other.1.is_zero() { Ordering::Less } else { - // general lossy case - let saturated_lcm = helpers_128bit::multiply_by_rational_best_effort( - self.1, - other.1, - helpers_128bit::gcd(self.1, other.1) - ); - let self_scaled = self.to_den(saturated_lcm) - .unwrap_or(Self(Bounded::max_value(), self.1)); - let other_scaled = other.to_den(saturated_lcm) - .unwrap_or(Self(Bounded::max_value(), other.1)); - self_scaled.n().cmp(&other_scaled.n()) + // Don't even compute gcd. + let self_n = helpers_128bit::to_big_uint(self.0) * helpers_128bit::to_big_uint(other.1); + let other_n = helpers_128bit::to_big_uint(other.0) * helpers_128bit::to_big_uint(self.1); + self_n.cmp(&other_n) } } } -/// Note that this implementation can very well be lossy. TODO #3670 impl PartialEq for Rational128 { fn eq(&self, other: &Self) -> bool { // handle some edge cases. if self.1 == other.1 { self.0.eq(&other.0) } else { - // general lossy case - let saturated_lcm = helpers_128bit::multiply_by_rational_best_effort( - self.1, - other.1, - helpers_128bit::gcd(self.1, other.1) - ); - let self_scaled = self.to_den(saturated_lcm) - .unwrap_or(Self(Bounded::max_value(), self.1)); - let other_scaled = other.to_den(saturated_lcm) - .unwrap_or(Self(Bounded::max_value(), other.1)); - self_scaled.n().eq(&other_scaled.n()) + let self_n = helpers_128bit::to_big_uint(self.0) * helpers_128bit::to_big_uint(other.1); + let other_n = helpers_128bit::to_big_uint(other.0) * helpers_128bit::to_big_uint(self.1); + self_n.eq(&other_n) } } } - #[cfg(test)] mod test_rational128 { use super::*; use super::helpers_128bit::*; - use crate::assert_eq_error_rate; use rand::Rng; const MAX128: u128 = u128::max_value(); @@ -1004,7 +1867,7 @@ mod test_rational128 { assert_eq!(r(4, 10).to_den(5), Ok(r(2, 5))); // up and down with large numbers - assert_eq!(r(MAX128 - 10, MAX128).to_den(10), Ok(r(9, 10))); + assert_eq!(r(MAX128 - 10, MAX128).to_den(10), Ok(r(10, 10))); assert_eq!(r(MAX128 / 2, MAX128).to_den(10), Ok(r(5, 10))); // large to perbill. This is very well needed for phragmen. @@ -1033,7 +1896,7 @@ mod test_rational128 { // large numbers assert_eq!( r(1_000_000_000, MAX128).lcm(&r(7_000_000_000, MAX128-1)), - Err("can not accurately multiply by rational in this type"), + Err("result cannot fit in u128"), ); assert_eq!( r(1_000_000_000, MAX64).lcm(&r(7_000_000_000, MAX64-1)), @@ -1052,7 +1915,7 @@ mod test_rational128 { // errors assert_eq!( r(1, MAX128).checked_add(r(1, MAX128-1)), - Err("can not accurately multiply by rational in this type"), + Err("failed to scale to denominator"), ); assert_eq!( r(7, MAX128).checked_add(r(MAX128, MAX128)), @@ -1073,7 +1936,7 @@ mod test_rational128 { // errors assert_eq!( r(2, MAX128).checked_sub(r(1, MAX128-1)), - Err("can not accurately multiply by rational in this type"), + Err("failed to scale to denominator"), ); assert_eq!( r(7, MAX128).checked_sub(r(MAX128, MAX128)), @@ -1105,7 +1968,6 @@ mod test_rational128 { assert_eq!(multiply_by_rational(7, 20, 30).unwrap(), 7 * 2 / 3); assert_eq!(multiply_by_rational(20, 7, 30).unwrap(), 7 * 2 / 3); - // computed with swap assert_eq!( // MAX128 % 3 == 0 multiply_by_rational(MAX128, 2, 3).unwrap(), @@ -1136,7 +1998,6 @@ mod test_rational128 { 2 * MAX64 - 3, ); - assert_eq!( multiply_by_rational(MAX64 + 100, MAX64_2, MAX64_2 / 2).unwrap(), (MAX64 + 100) * 2, @@ -1146,9 +2007,14 @@ mod test_rational128 { (MAX64 + 100) * 2, ); - // fails to compute. have to use the greedy, lossy version here - assert!(multiply_by_rational(2u128.pow(66) - 1, 2u128.pow(65) - 1, 2u128.pow(65)).is_err()); - assert!(multiply_by_rational(1_000_000_000, MAX128 / 8, MAX128 / 2).is_err()); + assert_eq!( + multiply_by_rational(2u128.pow(66) - 1, 2u128.pow(65) - 1, 2u128.pow(65)).unwrap(), + 73786976294838206461, + ); + assert_eq!( + multiply_by_rational(1_000_000_000, MAX128 / 8, MAX128 / 2).unwrap(), + 250000000, + ); } #[test] @@ -1163,40 +2029,6 @@ mod test_rational128 { ); } - #[test] - fn multiply_by_rational_best_effort_works() { - assert_eq_error_rate!( - multiply_by_rational_best_effort(MAX64 + 100, MAX64_2, MAX64_2 / 2), - (MAX64 + 100) * 2, - 10, - ); - assert_eq_error_rate!( - multiply_by_rational_best_effort(MAX64 + 100, MAX64_2 * 100, MAX64_2 * 100 / 2), - (MAX64 + 100) * 2, - 10, - ); - assert_eq_error_rate!( - multiply_by_rational_best_effort(2u128.pow(66) - 1, 2u128.pow(65) - 1, 2u128.pow(65)), - mul_div(2u128.pow(66) - 1, 2u128.pow(65) - 1, 2u128.pow(65)), - 10, - ); - assert_eq_error_rate!( - multiply_by_rational_best_effort(1_000_000_000, MAX128 / 8, MAX128 / 2), - 1_000_000_000 / 4, - 10, - ); - - assert_eq!( - multiply_by_rational_best_effort(MAX128, MAX128 - 1, MAX128), - MAX128, - ); - - assert_eq!( - multiply_by_rational_best_effort(MAX64, MAX128 / 2, MAX128), - MAX64 / 2, - ); - } - fn do_fuzz_multiply_by_rational( iters: usize, bits: u32, @@ -1242,42 +2074,8 @@ mod test_rational128 { ); } - #[test] - fn fuzz_multiply_by_rational_best_effort_32() { - let f = |a, b, c| multiply_by_rational_best_effort(a, b, c); - println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 32, 0, false, true, f); - println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 32, 0, false, false, f); - } - - #[test] - fn fuzz_multiply_by_rational_best_effort_64() { - let f = |a, b, c| multiply_by_rational_best_effort(a, b, c); - println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 64, 0, false, true, f); - println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 64, 0, false, false, f); - } - - #[test] - fn fuzz_multiply_by_rational_best_effort_96() { - let f = |a, b, c| multiply_by_rational_best_effort(a, b, c); - // println!("\nInvariant: b < c"); - // do_fuzz_multiply_by_rational(1_000_000, 96, 0, false, true, f); - println!("every possibility"); - // do_fuzz_multiply_by_rational(1_000_000, 96, 0, false, false, f); - do_fuzz_multiply_by_rational(10, 96, 0, true, false, f); - } - - #[test] - fn fuzz_multiply_by_rational_best_effort_128() { - let f = |a, b, c| multiply_by_rational_best_effort(a, b, c); - println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 127, 0, false, true, f); - println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 127, 0, false, false, f); - } + // TODO $# move into a proper fuzzer #3745 + const FUZZ_COUNT: usize = 100_000; #[test] fn fuzz_multiply_by_rational_32() { @@ -1286,40 +2084,39 @@ mod test_rational128 { // returning `Err` is fine. let f = |a, b, c| multiply_by_rational(a, b, c).unwrap_or(mul_div(a, b, c)); println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 32, 0, false, true, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 32, 0, false, true, f); println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 32, 0, false, false, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 32, 0, false, false, f); } #[test] fn fuzz_multiply_by_rational_64() { let f = |a, b, c| multiply_by_rational(a, b, c).unwrap_or(mul_div(a, b, c)); println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 64, 0, false, true, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 64, 0, false, true, f); println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 64, 0, false, false, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 64, 0, false, false, f); } #[test] fn fuzz_multiply_by_rational_96() { let f = |a, b, c| multiply_by_rational(a, b, c).unwrap_or(mul_div(a, b, c)); println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 96, 0, false, true, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 96, 0, false, true, f); println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 96, 0, false, false, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 96, 0, false, false, f); } #[test] fn fuzz_multiply_by_rational_128() { let f = |a, b, c| multiply_by_rational(a, b, c).unwrap_or(mul_div(a, b, c)); println!("\nInvariant: b < c"); - do_fuzz_multiply_by_rational(1_000_000, 127, 0, false, true, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 127, 0, false, true, f); println!("every possibility"); - do_fuzz_multiply_by_rational(1_000_000, 127, 0, false, false, f); + do_fuzz_multiply_by_rational(FUZZ_COUNT, 127, 0, false, false, f); } } - #[cfg(test)] mod tests_fixed64 { use super::*; diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index ebe7b134e0..c132e2d430 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -84,8 +84,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 173, - impl_version: 173, + spec_version: 174, + impl_version: 174, apis: RUNTIME_API_VERSIONS, };