Offchain Phragmén BREAKING. (#4517)

* Initial skeleton for offchain phragmen

* Basic compact encoding decoding for results

* add compact files

* Bring back Self::ensure_storage_upgraded();

* Make staking use compact stuff.

* First seemingly working version of reduce, full of todos

* Everything phragmen related works again.

* Signing made easier, still issues.

* Signing from offchain compile fine 😎

* make compact work with staked asssignment

* Evaluation basics are in place.

* Move reduce into crate. Document stuff

* move reduce into no_std

* Add files

* Remove other std deps. Runtime compiles

* Seemingly it is al stable; cycle implemented but not integrated.

* Add fuzzing code.

* Cleanup reduce a bit more.

* a metric ton of tests for staking; wip 🔨

* Implement a lot more of the tests.

* wip getting the unsigned stuff to work

* A bit gleanup for unsigned debug

* Clean and finalize compact code.

* Document reduce.

* Still problems with signing

* We officaly duct taped the transaction submission stuff. 🤓

* Deadlock with keys again

* Runtime builds

* Unsigned test works 🙌

* Some cleanups

* Make all the tests compile and stuff

* Minor cleanup

* fix more merge stuff

* Most tests work again.

* a very nasty bug in reduce

* Fix all integrations

* Fix more todos

* Revamp everything and everything

* Remove bogus test

* Some review grumbles.

* Some fixes

* Fix doc test

* loop for submission

* Fix cli, keyring etc.

* some cleanup

* Fix staking tests again

* fix per-things; bring patches from benchmarking

* better score prediction

* Add fuzzer, more patches.

* Some fixes

* More docs

* Remove unused generics

* Remove max-nominator footgun

* Better fuzzer

* Disable it 

* Bump.

* Another round of self-review

* Refactor a lot

* More major fixes in perThing

* Add new fuzz file

* Update lock

* fix fuzzing code.

* Fix nominator retain test

* Add slashing check

* Update frame/staking/src/tests.rs

Co-Authored-By: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>

* Some formatting nits

* Review comments.

* Fix cargo file

* Almost all tests work again

* Update frame/staking/src/tests.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Fix review comments

* More review stuff

* Some nits

* Fix new staking / session / babe relation

* Update primitives/phragmen/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Update primitives/phragmen/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Update primitives/phragmen/compact/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Some doc updates to slashing

* Fix derive

* Remove imports

* Remove unimplemented tests

* nits

* Remove dbg

* Better fuzzing params

* Remove unused pref map

* Deferred Slashing/Offence for offchain Phragmen  (#5151)

* Some boilerplate

* Add test

* One more test

* Review comments

* Fix build

* review comments

* fix more

* fix build

* Some cleanups and self-reviews

* More minor self reviews

* Final nits

* Some merge fixes.

* opt comment

* Fix build

* Fix build again.

* Update frame/staking/fuzz/fuzz_targets/submit_solution.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update frame/staking/src/slashing.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update frame/staking/src/offchain_election.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Fix review comments

* fix test

* === 🔑 Revamp without staking key.

* final round of changes.

* Fix cargo-deny

* Update frame/staking/src/lib.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
Kian Paimani
2020-03-26 15:37:40 +01:00
committed by GitHub
parent 2a67e6c437
commit 970c5f94f2
64 changed files with 11953 additions and 892 deletions
+14 -2
View File
@@ -19,7 +19,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
/// Copied from `sp-runtime` and documented there.
#[cfg(test)]
#[macro_export]
macro_rules! assert_eq_error_rate {
($x:expr, $y:expr, $error:expr $(,)?) => {
assert!(
@@ -40,5 +40,17 @@ mod fixed64;
mod rational128;
pub use fixed64::Fixed64;
pub use per_things::{PerThing, Percent, Permill, Perbill, Perquintill};
pub use per_things::{PerThing, Percent, PerU16, Permill, Perbill, Perquintill};
pub use rational128::Rational128;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peru16_rational_does_not_overflow() {
// A historical example that will panic only for per_thing type that are created with
// maximum capacity of their type, e.g. PerU16.
let _ = PerU16::from_rational_approximation(17424870u32, 17424870);
}
}
+288 -119
View File
@@ -17,18 +17,24 @@
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
use sp_std::{ops, prelude::*, convert::TryInto};
use sp_std::{ops, fmt, prelude::*, convert::TryInto};
use codec::{Encode, Decode, CompactAs};
use crate::traits::{
SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic,
use crate::{
traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, Bounded},
};
use sp_debug_derive::RuntimeDebug;
/// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per
/// `X`_.
pub trait PerThing: Sized + Saturating + Copy {
pub trait PerThing:
Sized + Saturating + Copy + Default + Eq + PartialEq + Ord + PartialOrd + Bounded + fmt::Debug
{
/// The data type used to build this per-thingy.
type Inner: BaseArithmetic + Copy;
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;
/// accuracy of this type
const ACCURACY: Self::Inner;
@@ -63,12 +69,53 @@ pub trait PerThing: Sized + Saturating + Copy {
/// The computation of this approximation is performed in the generic type `N`. Given
/// `M` as the data type that can hold the maximum value of this per-thing (e.g. u32 for
/// perbill), this can only work if `N == M` or `N: From<M> + TryInto<M>`.
///
/// Note that this always rounds _down_, i.e.
///
/// ```rust
/// # use sp_arithmetic::{Percent, PerThing};
/// # fn main () {
/// // 989/100 is technically closer to 99%.
/// assert_eq!(
/// Percent::from_rational_approximation(989, 1000),
/// Percent::from_parts(98),
/// );
/// # }
/// ```
fn from_rational_approximation<N>(p: N, q: N) -> Self
where N: Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + ops::Div<N, Output=N>;
where N:
Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + TryInto<Self::Upper> +
ops::Div<N, Output=N> + ops::Rem<N, Output=N> + ops::Add<N, Output=N>;
/// 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<N>(self, b: N) -> N
where N: Clone + From<Self::Inner> + UniqueSaturatedInto<Self::Inner> + ops::Rem<N, Output=N>
+ ops::Div<N, Output=N> + ops::Mul<N, Output=N> + ops::Add<N, Output=N>;
}
macro_rules! implement_per_thing {
($name:ident, $test_mod:ident, [$($test_units:tt),+], $max:tt, $type:ty, $upper_type:ty, $title:expr $(,)?) => {
(
$name:ident,
$test_mod:ident,
[$($test_units:tt),+],
$max:tt, $type:ty,
$upper_type:ty,
$title:expr $(,)?
) => {
/// A fixed point representation of a number between in the range [0, 1].
///
#[doc = $title]
@@ -78,33 +125,30 @@ macro_rules! implement_per_thing {
impl PerThing for $name {
type Inner = $type;
type Upper = $upper_type;
/// The accuracy of this type.
const ACCURACY: Self::Inner = $max;
/// Nothing.
fn zero() -> Self { Self(0) }
/// `true` if this is nothing.
fn is_zero(&self) -> bool { self.0 == 0 }
/// Everything.
fn one() -> Self { Self($max) }
/// Consume self and deconstruct into a raw numeric type.
fn deconstruct(self) -> Self::Inner { self.0 }
/// From an explicitly defined number of parts per maximum of the type.
// 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])
}
/// Converts a percent into `Self`. Equal to `x / 100`.
fn from_percent(x: Self::Inner) -> Self {
Self([x, 100][(x > 100) as usize] * ($max / 100))
Self::from_rational_approximation([x, 100][(x > 100) as usize] as $upper_type, 100)
}
/// Return the product of multiplication of this value by itself.
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;
@@ -112,39 +156,43 @@ macro_rules! implement_per_thing {
Self::from_rational_approximation(p, q)
}
/// Converts a fraction into `Self`.
#[cfg(feature = "std")]
fn from_fraction(x: f64) -> Self { Self((x * ($max as f64)) as Self::Inner) }
/// Approximate the fraction `p/q` into a per-thing fraction. This will never overflow.
///
/// The computation of this approximation is performed in the generic type `N`. Given
/// `M` as the data type that can hold the maximum value of this per-thing (e.g. u32 for
/// perbill), this can only work if `N == M` or `N: From<M> + TryInto<M>`.
fn from_rational_approximation<N>(p: N, q: N) -> Self
where N: Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + ops::Div<N, Output=N>
where N:
Clone + Ord + From<Self::Inner> + TryInto<Self::Inner> + TryInto<Self::Upper> +
ops::Div<N, Output=N> + ops::Rem<N, Output=N> + ops::Add<N, Output=N>
{
let div_ceil = |x: N, f: N| -> N {
let mut o = x.clone() / f.clone();
let r = x.rem(f.clone());
if r > N::from(0) {
o = o + N::from(1);
}
o
};
// q cannot be zero.
let q = q.max((1 as Self::Inner).into());
let q: N = q.max((1 as Self::Inner).into());
// p should not be bigger than q.
let p = p.min(q.clone());
let p: N = p.min(q.clone());
let factor = (q.clone() / $max.into()).max((1 as Self::Inner).into());
let factor: N = div_ceil(q.clone(), $max.into()).max((1 as Self::Inner).into());
// q cannot overflow: (q / (q/$max)) < 2 * $max. p < q hence p also cannot overflow.
// this implies that Self::Inner must be able to fit 2 * $max.
let q_reduce: Self::Inner = (q / factor.clone())
// q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow.
let q_reduce: $type = (q.clone() / factor.clone())
.try_into()
.map_err(|_| "Failed to convert")
.expect(
"q / (q/$max) < (2 * $max). Macro prevents any type being created that \
"q / ceil(q/$max) < $max. Macro prevents any type being created that \
does not satisfy this; qed"
);
let p_reduce: Self::Inner = (p / factor.clone())
let p_reduce: $type = (p / factor)
.try_into()
.map_err(|_| "Failed to convert")
.expect(
"q / (q/$max) < (2 * $max). Macro prevents any type being created that \
"q / ceil(q/$max) < $max. Macro prevents any type being created that \
does not satisfy this; qed"
);
@@ -157,13 +205,49 @@ macro_rules! implement_per_thing {
$name(part as Self::Inner)
}
fn mul_collapse<N>(self, b: N) -> N
where
N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem<N, Output=N>
+ ops::Div<N, Output=N> + ops::Mul<N, Output=N> + ops::Add<N, Output=N>
{
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
}
}
/// Implement const functions
impl $name {
/// From an explicitly defined number of parts per maximum of the type.
///
/// This can be called at compile time.
// 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)]
pub const fn from_parts(parts: $type) -> Self {
Self([parts, $max][(parts > $max) as usize])
}
@@ -202,6 +286,16 @@ macro_rules! implement_per_thing {
}
}
impl crate::traits::Bounded for $name {
fn min_value() -> Self {
<Self as PerThing>::zero()
}
fn max_value() -> Self {
<Self as PerThing>::one()
}
}
impl ops::Div for $name {
type Output = Self;
@@ -212,9 +306,10 @@ macro_rules! implement_per_thing {
}
}
/// Overflow-prune multiplication.
/// Non-overflow multiplication.
///
/// tailored to be used with a balance type.
///
impl<N> ops::Mul<N> for $name
where
N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem<N, Output=N>
@@ -241,6 +336,7 @@ macro_rules! implement_per_thing {
// 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;
@@ -261,15 +357,17 @@ macro_rules! implement_per_thing {
use super::{$name, Saturating, RuntimeDebug, PerThing};
use crate::traits::Zero;
#[test]
fn macro_expanded_correctly() {
// needed for the `from_percent` to work.
assert!($max >= 100);
assert!($max % 100 == 0);
// needed for the `from_percent` to work. UPDATE: this is no longer needed; yet note
// that tests that use percentage or fractions such as $name::from_fraction(0.2) to
// create values will most likely be inaccurate when used with per_things that are
// not multiples of 100.
// assert!($max >= 100);
// assert!($max % 100 == 0);
// needed for `from_rational_approximation`
assert!(2 * $max < <$type>::max_value());
assert!(2 * ($max as $upper_type) < <$upper_type>::max_value());
assert!(<$upper_type>::from($max) < <$upper_type>::max_value());
// for something like percent they can be the same.
@@ -298,7 +396,7 @@ macro_rules! implement_per_thing {
(63, 1),
(64, 2),
(65, 2),
(<$type>::max_value(), <$type>::max_value().encode().len() + 1)
// (<$type>::max_value(), <$type>::max_value().encode().len() + 1)
];
for &(n, l) in &tests {
let compact: codec::Compact<$name> = $name(n).into();
@@ -317,33 +415,73 @@ 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_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));
}
macro_rules! u256ify {
($val:expr) => {
Into::<U256>::into($val)
};
}
macro_rules! per_thing_mul_test {
($num_type:tt) => {
// multiplication from all sort of from_percent
assert_eq!(
$name::from_percent(100) * $num_type::max_value(),
$name::from_fraction(1.0) * $num_type::max_value(),
$num_type::max_value()
);
assert_eq_error_rate!(
$name::from_percent(99) * $num_type::max_value(),
((Into::<U256>::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type,
1,
);
assert_eq!(
$name::from_percent(50) * $num_type::max_value(),
$num_type::max_value() / 2,
);
assert_eq_error_rate!(
$name::from_percent(1) * $num_type::max_value(),
$num_type::max_value() / 100,
1,
);
assert_eq!($name::from_percent(0) * $num_type::max_value(), 0);
if $max % 100 == 0 {
assert_eq_error_rate!(
$name::from_percent(99) * $num_type::max_value(),
((Into::<U256>::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type,
1,
);
assert_eq!(
$name::from_fraction(0.5) * $num_type::max_value(),
$num_type::max_value() / 2,
);
assert_eq_error_rate!(
$name::from_percent(1) * $num_type::max_value(),
$num_type::max_value() / 100,
1,
);
} else {
assert_eq!(
$name::from_fraction(0.99) * <$num_type>::max_value(),
(
(
u256ify!($name::from_fraction(0.99).0) *
u256ify!(<$num_type>::max_value()) /
u256ify!($max)
).as_u128()
) as $num_type,
);
assert_eq!(
$name::from_fraction(0.50) * <$num_type>::max_value(),
(
(
u256ify!($name::from_fraction(0.50).0) *
u256ify!(<$num_type>::max_value()) /
u256ify!($max)
).as_u128()
) as $num_type,
);
assert_eq!(
$name::from_fraction(0.01) * <$num_type>::max_value(),
(
(
u256ify!($name::from_fraction(0.01).0) *
u256ify!(<$num_type>::max_value()) /
u256ify!($max)
).as_u128()
) as $num_type,
);
}
assert_eq!($name::from_fraction(0.0) * $num_type::max_value(), 0);
// // multiplication with bounds
assert_eq!($name::one() * $num_type::max_value(), $num_type::max_value());
@@ -356,17 +494,20 @@ macro_rules! implement_per_thing {
use primitive_types::U256;
// accuracy test
assert_eq!($name::from_rational_approximation(1 as $type, 3) * 30 as $type, 10);
assert_eq!(
$name::from_rational_approximation(1 as $type, 3) * 30 as $type,
10,
);
$(per_thing_mul_test!($test_units);)*
}
#[test]
fn per_thing_mul_rounds_to_nearest_number() {
assert_eq!($name::from_percent(33) * 10u64, 3);
assert_eq!($name::from_percent(34) * 10u64, 3);
assert_eq!($name::from_percent(35) * 10u64, 3);
assert_eq!($name::from_percent(36) * 10u64, 4);
assert_eq!($name::from_fraction(0.33) * 10u64, 3);
assert_eq!($name::from_fraction(0.34) * 10u64, 3);
assert_eq!($name::from_fraction(0.35) * 10u64, 3);
assert_eq!($name::from_fraction(0.36) * 10u64, 4);
}
#[test]
@@ -398,31 +539,32 @@ macro_rules! implement_per_thing {
);
assert_eq!(
$name::from_rational_approximation(1 as $num_type, 10),
$name::from_percent(10),
$name::from_fraction(0.10),
);
assert_eq!(
$name::from_rational_approximation(1 as $num_type, 4),
$name::from_percent(25),
$name::from_fraction(0.25),
);
assert_eq!(
$name::from_rational_approximation(1 as $num_type, 4),
$name::from_rational_approximation(2 as $num_type, 8),
);
// no accurate anymore but won't overflow.
assert_eq!(
assert_eq_error_rate!(
$name::from_rational_approximation(
$num_type::max_value() - 1,
$num_type::max_value()
),
$name::one(),
).0 as $upper_type,
$name::one().0 as $upper_type,
2,
);
assert_eq_error_rate!(
$name::from_rational_approximation(
$num_type::max_value() / 3,
$num_type::max_value()
).0,
$name::from_parts($max / 3).0,
2
).0 as $upper_type,
$name::from_parts($max / 3).0 as $upper_type,
2,
);
assert_eq!(
$name::from_rational_approximation(1, $num_type::max_value()),
@@ -436,13 +578,14 @@ macro_rules! implement_per_thing {
// This is just to make sure something like Percent which _might_ get built from a
// u8 does not overflow in the context of this test.
let max_value = <$upper_type>::from($max);
// almost at the edge
assert_eq!(
$name::from_rational_approximation($max - 1, $max + 1),
$name::from_rational_approximation(max_value - 1, max_value + 1),
$name::from_parts($max - 2),
);
assert_eq!(
$name::from_rational_approximation(1, $max-1),
$name::from_rational_approximation(1, $max - 1),
$name::from_parts(1),
);
assert_eq!(
@@ -450,76 +593,83 @@ macro_rules! implement_per_thing {
$name::from_parts(1),
);
assert_eq!(
$name::from_rational_approximation(2, 2 * $max - 1),
$name::from_rational_approximation(2, 2 * max_value - 1),
$name::from_parts(1),
);
assert_eq!(
$name::from_rational_approximation(1, $max+1),
$name::from_rational_approximation(1, max_value + 1),
$name::zero(),
);
assert_eq!(
$name::from_rational_approximation(3 * max_value / 2, 3 * max_value),
$name::from_percent(50),
$name::from_fraction(0.5),
);
$(per_thing_from_rationale_approx_test!($test_units);)*
}
#[test]
fn per_things_mul_operates_in_output_type() {
// assert_eq!($name::from_percent(50) * 100u32, 50u32);
assert_eq!($name::from_percent(50) * 100u64, 50u64);
assert_eq!($name::from_percent(50) * 100u128, 50u128);
// assert_eq!($name::from_fraction(0.5) * 100u32, 50u32);
assert_eq!($name::from_fraction(0.5) * 100u64, 50u64);
assert_eq!($name::from_fraction(0.5) * 100u128, 50u128);
}
#[test]
fn per_thing_saturating_op_works() {
assert_eq!(
$name::from_percent(50).saturating_add($name::from_percent(40)),
$name::from_percent(90)
assert_eq_error_rate!(
$name::from_fraction(0.5).saturating_add($name::from_fraction(0.4)).0 as $upper_type,
$name::from_fraction(0.9).0 as $upper_type,
2,
);
assert_eq_error_rate!(
$name::from_fraction(0.5).saturating_add($name::from_fraction(0.5)).0 as $upper_type,
$name::one().0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(50).saturating_add($name::from_percent(50)),
$name::from_percent(100)
);
assert_eq!(
$name::from_percent(60).saturating_add($name::from_percent(50)),
$name::from_percent(100)
$name::from_fraction(0.6).saturating_add($name::from_fraction(0.5)),
$name::one(),
);
assert_eq!(
$name::from_percent(60).saturating_sub($name::from_percent(50)),
$name::from_percent(10)
assert_eq_error_rate!(
$name::from_fraction(0.6).saturating_sub($name::from_fraction(0.5)).0 as $upper_type,
$name::from_fraction(0.1).0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(60).saturating_sub($name::from_percent(60)),
$name::from_percent(0)
$name::from_fraction(0.6).saturating_sub($name::from_fraction(0.6)),
$name::from_fraction(0.0),
);
assert_eq!(
$name::from_percent(60).saturating_sub($name::from_percent(70)),
$name::from_percent(0)
$name::from_fraction(0.6).saturating_sub($name::from_fraction(0.7)),
$name::from_fraction(0.0),
);
assert_eq!(
$name::from_percent(50).saturating_mul($name::from_percent(50)),
$name::from_percent(25)
assert_eq_error_rate!(
$name::from_fraction(0.5).saturating_mul($name::from_fraction(0.5)).0 as $upper_type,
$name::from_fraction(0.25).0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(20).saturating_mul($name::from_percent(20)),
$name::from_percent(4)
assert_eq_error_rate!(
$name::from_fraction(0.2).saturating_mul($name::from_fraction(0.2)).0 as $upper_type,
$name::from_fraction(0.04).0 as $upper_type,
2,
);
assert_eq!(
$name::from_percent(10).saturating_mul($name::from_percent(10)),
$name::from_percent(1)
assert_eq_error_rate!(
$name::from_fraction(0.1).saturating_mul($name::from_fraction(0.1)).0 as $upper_type,
$name::from_fraction(0.01).0 as $upper_type,
1,
);
}
#[test]
fn per_thing_square_works() {
assert_eq!($name::from_percent(100).square(), $name::from_percent(100));
assert_eq!($name::from_percent(50).square(), $name::from_percent(25));
assert_eq!($name::from_percent(10).square(), $name::from_percent(1));
assert_eq!($name::from_fraction(1.0).square(), $name::from_fraction(1.0));
assert_eq!($name::from_fraction(0.5).square(), $name::from_fraction(0.25));
assert_eq!($name::from_fraction(0.1).square(), $name::from_fraction(0.01));
assert_eq!(
$name::from_percent(2).square(),
$name::from_fraction(0.02).square(),
$name::from_parts((4 * <$upper_type>::from($max) / 100 / 100) as $type)
);
}
@@ -527,22 +677,32 @@ macro_rules! implement_per_thing {
#[test]
fn per_things_div_works() {
// normal
assert_eq!($name::from_percent(10) / $name::from_percent(20),
$name::from_percent(50)
assert_eq_error_rate!(
($name::from_fraction(0.1) / $name::from_fraction(0.20)).0 as $upper_type,
$name::from_fraction(0.50).0 as $upper_type,
2,
);
assert_eq!($name::from_percent(10) / $name::from_percent(10),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(0.1) / $name::from_fraction(0.10)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
assert_eq!($name::from_percent(10) / $name::from_percent(0),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(0.1) / $name::from_fraction(0.0)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
// will not overflow
assert_eq!($name::from_percent(10) / $name::from_percent(5),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(0.10) / $name::from_fraction(0.05)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
assert_eq!($name::from_percent(100) / $name::from_percent(50),
$name::from_percent(100)
assert_eq_error_rate!(
($name::from_fraction(1.0) / $name::from_fraction(0.5)).0 as $upper_type,
$name::from_fraction(1.0).0 as $upper_type,
2,
);
}
}
@@ -558,6 +718,15 @@ implement_per_thing!(
u16,
"_Percent_",
);
implement_per_thing!(
PerU16,
test_peru16,
[u32, u64, u128],
65535_u16,
u16,
u32,
"_Parts per 65535_",
);
implement_per_thing!(
Permill,
test_permill,