referrerpolicy=no-referrer-when-downgrade

per_thing_from_rational/
per_thing_from_rational.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Running
19//! Running this fuzzer can be done with `cargo hfuzz run per_thing_from_rational`. `honggfuzz` CLI
20//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
21//!
22//! # Debugging a panic
23//! Once a panic is found, it can be debugged with
24//! `cargo hfuzz run-debug per_thing_from_rational hfuzz_workspace/per_thing_from_rational/*.fuzz`.
25
26use fraction::prelude::BigFraction as Fraction;
27use honggfuzz::fuzz;
28use sp_arithmetic::{
29	traits::SaturatedConversion, PerThing, Perbill, Percent, Perquintill, Rounding::*, *,
30};
31
32/// Tries to demonstrate that `from_rational` is incorrect for any rounding modes.
33///
34/// NOTE: This `Fraction` library is really slow. Using f128/f256 does not work for the large
35/// numbers. But an optimization could be done do use either floats or Fraction depending on the
36/// size of the inputs.
37fn main() {
38	loop {
39		fuzz!(|data: (u128, u128, ArbitraryRounding)| {
40			let (n, d, r) = (data.0.min(data.1), data.0.max(data.1).max(1), data.2);
41
42			check::<PerU16>(n, d, r.0);
43			check::<Percent>(n, d, r.0);
44			check::<Permill>(n, d, r.0);
45			check::<Perbill>(n, d, r.0);
46			check::<Perquintill>(n, d, r.0);
47		})
48	}
49}
50
51/// Assert that the parts of `from_rational` are correct for the given rounding mode.
52fn check<Per: PerThing>(a: u128, b: u128, r: Rounding)
53where
54	Per::Inner: Into<u128>,
55{
56	let approx_ratio = Per::from_rational_with_rounding(a, b, r).unwrap();
57	let approx_parts = Fraction::from(approx_ratio.deconstruct().saturated_into::<u128>());
58
59	let perfect_ratio = if a == 0 && b == 0 {
60		Fraction::from(1)
61	} else {
62		Fraction::from(a) / Fraction::from(b.max(1))
63	};
64	let perfect_parts = round(perfect_ratio * Fraction::from(Per::ACCURACY.into()), r);
65
66	assert_eq!(
67		approx_parts, perfect_parts,
68		"approx_parts: {}, perfect_parts: {}, a: {}, b: {}",
69		approx_parts, perfect_parts, a, b
70	);
71}
72
73/// Round a `Fraction` according to the given mode.
74fn round(f: Fraction, r: Rounding) -> Fraction {
75	match r {
76		Up => f.ceil(),
77		NearestPrefUp =>
78			if f.fract() < Fraction::from(0.5) {
79				f.floor()
80			} else {
81				f.ceil()
82			},
83		Down => f.floor(),
84		NearestPrefDown =>
85			if f.fract() > Fraction::from(0.5) {
86				f.ceil()
87			} else {
88				f.floor()
89			},
90	}
91}
92
93/// An [`arbitrary::Arbitrary`] [`Rounding`] mode.
94struct ArbitraryRounding(Rounding);
95impl arbitrary::Arbitrary<'_> for ArbitraryRounding {
96	fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
97		Ok(Self(match u.int_in_range(0..=3).unwrap() {
98			0 => Up,
99			1 => NearestPrefUp,
100			2 => Down,
101			3 => NearestPrefDown,
102			_ => unreachable!(),
103		}))
104	}
105}