-
Notifications
You must be signed in to change notification settings - Fork 243
libm: define and implement trait NarrowingDiv
for unsigned integer division
#1011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
8671beb
to
aa1fadc
Compare
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.
This looks great, thanks for including detailed comments. A few requests, mostly surface level
impl_narrowing_div_primitive!(u32); | ||
impl_narrowing_div_primitive!(u64); | ||
impl_narrowing_div_primitive!(u128); | ||
impl_narrowing_div_recurse!(u256); |
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.
Since this one is only used once, it can probably just be an impl without the macro. Please no f256
😆
unsafe fn unchecked_narrowing_div_rem(self, n: Self::H) -> (Self::H, Self::H); | ||
|
||
/// Returns `Some((self / n, self % n))` when `self.hi() < n`. | ||
fn checked_narrowing_div_rem(self, n: Self::H) -> Option<(Self::H, Self::H)> { |
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.
Nit: from std's convention, this can just be narrowing_div_rem
if self.hi() >= n { | ||
unsafe { core::hint::unreachable_unchecked() } | ||
} | ||
((self / n as $D) as Self::H, (self % n as $D) as Self::H) |
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.
Could this use the DInt
/HInt
traits? Bit more clear than as
.
(self / n.widen()).lo(), (self % n.widen()).lo()
It would be good to add a note that we're not doing anything special here since it optimizes well for primitives
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.
With that, I think this could even be a trait impl impl<D> NarrowingDiv for D where D: ops::Div + ops::Rem
. The macro isn't too bad, though
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.
Would it be posisble to add a bench for u256? Similar to
compiler-builtins/libm-test/benches/icount.rs
Lines 114 to 144 in 9c176c2
#[library_benchmark] | |
#[bench::linspace(setup_u256_add())] | |
fn icount_bench_u256_add(cases: Vec<(u256, u256)>) { | |
for (x, y) in cases.iter().copied() { | |
black_box(black_box(x) + black_box(y)); | |
} | |
} | |
#[library_benchmark] | |
#[bench::linspace(setup_u256_add())] | |
fn icount_bench_u256_sub(cases: Vec<(u256, u256)>) { | |
for (x, y) in cases.iter().copied() { | |
black_box(black_box(x) - black_box(y)); | |
} | |
} | |
#[library_benchmark] | |
#[bench::linspace(setup_u256_shift())] | |
fn icount_bench_u256_shl(cases: Vec<(u256, u32)>) { | |
for (x, y) in cases.iter().copied() { | |
black_box(black_box(x) << black_box(y)); | |
} | |
} | |
#[library_benchmark] | |
#[bench::linspace(setup_u256_shift())] | |
fn icount_bench_u256_shr(cases: Vec<(u256, u32)>) { | |
for (x, y) in cases.iter().copied() { | |
black_box(black_box(x) >> black_box(y)); | |
} | |
} |
// Normalize the divisor by shifting the most significant one | ||
// to the leading position. `n != 0` is implied by `self.hi() < n` | ||
let lz = n.leading_zeros(); | ||
let a = self << lz; | ||
let b = n << lz; |
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.
Does it make any sense to check if a.hi() == 0
and do a normal div in a smaller type in that case?
debug_assert!(false, "unsafe preconditions not met"); | ||
unsafe { core::hint::unreachable_unchecked() } |
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.
Mind either adding a debug_assert
to the other unreachable_unchecked
cases or removing the one here, just to be consistent? Technically unreachable_unchecked
has one that works correctly nowadays, but having an explicit case is still nice imo.
#[test] | ||
fn inverse_mul() { | ||
for x in 0..=u8::MAX { | ||
for y in 1..=u8::MAX { | ||
let xy = x.widen_mul(y); | ||
assert_eq!(xy.checked_narrowing_div_rem(y), Some((x, 0))); | ||
} | ||
} | ||
} |
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.
Maybe also check the x.carrying_mul(y, 1)
case?
/// | ||
/// # Safety | ||
/// Requires that `n.leading_zeros() == 0` and `a < n`. | ||
unsafe fn div_three_digits_by_two<U>(a0: U, a: U::D, n: U::D) -> (U, U::D) |
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.
Maybe div_three_3x_by_2x
or so? Just thinking that the inputs aren't really digits
@@ -0,0 +1,163 @@ | |||
use crate::support::{DInt, HInt, Int, MinInt, u256}; |
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.
If this is something you authored, could you add /* SPDX-License-Identifier: MIT OR Apache-2.0 */
?
New utility in
libm::support
:trait NarrowingDiv
for dividingu2N / uN
when the quotient fits inuN
u256 / u128
This is the inverse operation of unsigned widening multiplication:
The trait API is based on x86's
div
-instruction: quotient overflow happens exactly when the high half of the dividend is greater or equal to the divisor, which includes division by zero.Split from #1002