-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Introduce checked_div for PerThings and checked_rational_mul_correction
#14673
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,7 @@ mod double128 { | |
| } | ||
|
|
||
| /// Returns 2^128 / a | ||
| /// `checked_div()` could be used here but is not possible to `unwrap()` inside a `const fn` | ||
| const fn div128(a: u128) -> u128 { | ||
| (neg128(a) / a).wrapping_add(1) | ||
| } | ||
|
|
@@ -182,6 +183,19 @@ mod double128 { | |
| } | ||
| } | ||
|
|
||
| pub const fn checked_multiply_by_rational_with_rounding( | ||
| a: u128, | ||
| b: u128, | ||
| c: u128, | ||
| r: Rounding, | ||
| ) -> Result<Option<u128>, &'static str> { | ||
| if c == 0 { | ||
| return Err("Division by zero") | ||
| } | ||
|
|
||
| Ok(multiply_by_rational_with_rounding(a, b, c, r)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a general consideration of design: |
||
| } | ||
|
|
||
| /// Returns `a * b / c` (wrapping to 128 bits) or `None` in the case of | ||
| /// overflow. | ||
| pub const fn multiply_by_rational_with_rounding( | ||
|
|
@@ -191,9 +205,6 @@ pub const fn multiply_by_rational_with_rounding( | |
| r: Rounding, | ||
| ) -> Option<u128> { | ||
| use double128::Double128; | ||
| if c == 0 { | ||
| return None | ||
| } | ||
|
Comment on lines
-194
to
-196
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually making it less secure, which is why i would rather put the logic in the checked variant (basically same comment as above). |
||
| let (result, remainder) = Double128::product_of(a, b).div(c); | ||
| let mut result: u128 = match result.try_into_u128() { | ||
| Ok(v) => v, | ||
|
|
@@ -246,6 +257,7 @@ mod tests { | |
| use super::*; | ||
| use codec::{Decode, Encode}; | ||
| use multiply_by_rational_with_rounding as mulrat; | ||
| use checked_multiply_by_rational_with_rounding as checked_mulrat; | ||
| use Rounding::*; | ||
|
|
||
| const MAX: u128 = u128::max_value(); | ||
|
|
@@ -278,6 +290,36 @@ mod tests { | |
| assert_eq!(mulrat(1, MAX / 2 + 1, MAX, NearestPrefUp), Some(1)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn rational_checked_multiply_basic_rounding_works() { | ||
| assert_eq!(checked_mulrat(1, 1, 1, Up), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(3, 1, 3, Up), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, 1, 3, Up), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, 2, 3, Down), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, 1, 3, NearestPrefDown), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, 1, 2, NearestPrefDown), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, 2, 3, NearestPrefDown), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, 1, 3, NearestPrefUp), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, 1, 2, NearestPrefUp), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, 2, 3, NearestPrefUp), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(3, 1, 0, Up), Err("Division by zero")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn rational_checked_multiply_big_number_works() { | ||
| assert_eq!(checked_mulrat(MAX, MAX - 1, MAX, Down), Ok(Some(MAX - 1))); | ||
| assert_eq!(checked_mulrat(MAX, 1, MAX, Down), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(MAX, MAX - 1, MAX, Up), Ok(Some(MAX - 1))); | ||
| assert_eq!(checked_mulrat(MAX, 1, MAX, Up), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, MAX - 1, MAX, Down), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, 1, MAX, Up), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, MAX / 2, MAX, NearestPrefDown), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, MAX / 2 + 1, MAX, NearestPrefDown), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, MAX / 2, MAX, NearestPrefUp), Ok(Some(0))); | ||
| assert_eq!(checked_mulrat(1, MAX / 2 + 1, MAX, NearestPrefUp), Ok(Some(1))); | ||
| assert_eq!(checked_mulrat(1, MAX / 2 + 1, 0, NearestPrefUp), Err("Division by zero")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn sqrt_works() { | ||
| for i in 0..100_000u32 { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,8 +19,8 @@ | |
| use serde::{Deserialize, Serialize}; | ||
|
|
||
| use crate::traits::{ | ||
| BaseArithmetic, Bounded, CheckedAdd, CheckedMul, CheckedSub, One, SaturatedConversion, | ||
| Saturating, UniqueSaturatedInto, Unsigned, Zero, | ||
| BaseArithmetic, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, | ||
| SaturatedConversion, Saturating, UniqueSaturatedInto, Unsigned, Zero, | ||
| }; | ||
| use codec::{CompactAs, Encode}; | ||
| use num_traits::{Pow, SaturatingAdd, SaturatingSub}; | ||
|
|
@@ -496,11 +496,34 @@ where | |
| (x / maximum) * part_n + c | ||
| } | ||
|
|
||
| /// Checked compute of the error due to integer division in the expression `x / denom * numer`. | ||
| fn checked_rational_mul_correction<N, P>( | ||
| x: N, | ||
| numer: P::Inner, | ||
| denom: P::Inner, | ||
| rounding: Rounding, | ||
| ) -> Result<N, &'static str> | ||
| where | ||
| N: MultiplyArg + UniqueSaturatedInto<P::Inner>, | ||
| P: PerThing, | ||
| P::Inner: Into<N>, | ||
| { | ||
| if P::Inner::is_zero(&denom) { | ||
| return Err("Division by zero") | ||
| } | ||
| Ok(rational_mul_correction::<N, P>(x, numer, denom, rounding)) | ||
|
Comment on lines
+511
to
+514
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now you have the implication that the underlying function can only error iff |
||
| } | ||
|
|
||
| /// 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<N, P>(x: N, numer: P::Inner, denom: P::Inner, rounding: Rounding) -> N | ||
| fn rational_mul_correction<N, P>( | ||
| x: N, | ||
| numer: P::Inner, | ||
| denom: P::Inner, | ||
| rounding: Rounding, | ||
| ) -> N | ||
| where | ||
| N: MultiplyArg + UniqueSaturatedInto<P::Inner>, | ||
| P: PerThing, | ||
|
|
@@ -1021,6 +1044,21 @@ macro_rules! implement_per_thing { | |
| } | ||
| } | ||
|
|
||
| /// # Note | ||
| /// I am not sure if this is the best way to div safely for PerThings | ||
| impl CheckedDiv for $name { | ||
| #[inline] | ||
| fn checked_div(&self, rhs: &Self) -> Option<Self> { | ||
| let q = rhs.0; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there should be an |
||
|
|
||
| if q.is_zero() { | ||
| return None | ||
| } | ||
|
|
||
| Some(*self / *rhs) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably want to use the |
||
| } | ||
| } | ||
|
|
||
| impl $crate::traits::Zero for $name { | ||
| fn zero() -> Self { | ||
| Self::zero() | ||
|
|
@@ -1544,6 +1582,77 @@ macro_rules! implement_per_thing { | |
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn checked_rational_mul_correction_works() { | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$type, $name>( | ||
| <$type>::max_value(), | ||
| <$type>::max_value(), | ||
| <$type>::max_value(), | ||
| super::Rounding::NearestPrefDown, | ||
| ), | ||
| Ok(0), | ||
| ); | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$type, $name>( | ||
| <$type>::max_value() - 1, | ||
| <$type>::max_value(), | ||
| <$type>::max_value(), | ||
| super::Rounding::NearestPrefDown, | ||
| ), | ||
| Ok(<$type>::max_value() - 1), | ||
| ); | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$upper_type, $name>( | ||
| ((<$type>::max_value() - 1) as $upper_type).pow(2), | ||
| <$type>::max_value(), | ||
| <$type>::max_value(), | ||
| super::Rounding::NearestPrefDown, | ||
| ), | ||
| Ok(1), | ||
| ); | ||
| // ((max^2 - 1) % max) * max / max == max - 1 | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$upper_type, $name>( | ||
| (<$type>::max_value() as $upper_type).pow(2) - 1, | ||
| <$type>::max_value(), | ||
| <$type>::max_value(), | ||
| super::Rounding::NearestPrefDown, | ||
| ), | ||
| Ok(<$upper_type>::from((<$type>::max_value() - 1))), | ||
| ); | ||
| // (max % 2) * max / 2 == max / 2 | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$upper_type, $name>( | ||
| (<$type>::max_value() as $upper_type).pow(2), | ||
| <$type>::max_value(), | ||
| 2 as $type, | ||
| super::Rounding::NearestPrefDown, | ||
| ), | ||
| Ok(<$type>::max_value() as $upper_type / 2), | ||
| ); | ||
| // ((max^2 - 1) % max) * 2 / max == 2 (rounded up) | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$upper_type, $name>( | ||
| (<$type>::max_value() as $upper_type).pow(2) - 1, | ||
| 2 as $type, | ||
| <$type>::max_value(), | ||
| super::Rounding::NearestPrefDown, | ||
| ), | ||
| Ok(2), | ||
| ); | ||
| // ((max^2 - 1) % max) * 2 / max == 1 (rounded down) | ||
| assert_eq!( | ||
| super::checked_rational_mul_correction::<$upper_type, $name>( | ||
| (<$type>::max_value() as $upper_type).pow(2) - 1, | ||
| 2 as $type, | ||
| <$type>::max_value(), | ||
| super::Rounding::Down, | ||
| ), | ||
| Ok(1), | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn rational_mul_correction_works() { | ||
| assert_eq!( | ||
|
|
@@ -1737,6 +1846,22 @@ macro_rules! implement_per_thing { | |
| Some($name::from_percent(0)) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_basic_checked_div() { | ||
| assert_eq!( | ||
| $name::from_parts($max).checked_div(&$name::from_parts($max)), | ||
| Some($name::from_percent(100)) | ||
| ); | ||
| assert_eq!( | ||
| $name::from_percent(100).checked_div(&$name::from_percent(100)), | ||
| Some($name::from_percent(100)) | ||
| ); | ||
| assert_eq!( | ||
| $name::from_percent(0).checked_div(&$name::from_percent(0)), | ||
| None | ||
| ); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use a
matchorifin const functions.But for this function i would probably make more sense to add a
checked_div128function instead of changing it.