Skip to content

Commit d78b5da

Browse files
committed
Implement accelerated computation of (x << e) % y in unsigned integers
1 parent 85f056a commit d78b5da

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

libm/src/math/support/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub(crate) mod feature_detect;
88
mod float_traits;
99
pub mod hex_float;
1010
mod int_traits;
11+
mod modular;
1112

1213
#[allow(unused_imports)]
1314
pub use big::{i256, u256};
@@ -30,6 +31,8 @@ pub use hex_float::hf128;
3031
pub use hex_float::{hf32, hf64};
3132
#[allow(unused_imports)]
3233
pub use int_traits::{CastFrom, CastInto, DInt, HInt, Int, MinInt, NarrowingDiv};
34+
#[allow(unused_imports)]
35+
pub use modular::linear_mul_reduction;
3336

3437
/// Hint to the compiler that the current path is cold.
3538
pub fn cold_path() {

libm/src/math/support/modular.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use crate::support::int_traits::NarrowingDiv;
2+
use crate::support::{DInt, HInt, Int};
3+
4+
/// Contains:
5+
/// n in (R/8, R/4)
6+
/// x in [0, 2n)
7+
#[derive(Debug, Clone, PartialEq, Eq)]
8+
struct Reducer<U: HInt> {
9+
// let m = 2n
10+
m: U,
11+
// RR/2 = qm + r
12+
r: U,
13+
xq2: U::D,
14+
}
15+
16+
impl<U> Reducer<U>
17+
where
18+
U: HInt,
19+
U: Int<Unsigned = U>,
20+
{
21+
/// Construct a reducer for `(x << _) mod n`.
22+
///
23+
/// Requires `R/8 < n < R/4` and `x < 2n`.
24+
fn new(x: U, n: U) -> Self
25+
where
26+
U::D: NarrowingDiv,
27+
{
28+
let _1 = U::ONE;
29+
assert!(n > (_1 << (U::BITS - 3)));
30+
assert!(n < (_1 << (U::BITS - 2)));
31+
let m = n << 1;
32+
assert!(x < m);
33+
34+
// We need q and r s.t. RR/2 = qm + r
35+
// As R/4 < m < R/2,
36+
// we have R <= q < 2R
37+
// so let q = R + f
38+
// RR/2 = (R + f)m + r
39+
// R(R/2 - m) = fm + r
40+
41+
// v = R/2 - m < R/4 < m
42+
let v = (_1 << (U::BITS - 1)) - m;
43+
let (f, r) = v.widen_hi().checked_narrowing_div_rem(m).unwrap();
44+
45+
// xq < qm <= RR/2
46+
// 2xq < RR
47+
// 2xq = 2xR + 2xf;
48+
let x2: U = x << 1;
49+
let xq2 = x2.widen_hi() + x2.widen_mul(f);
50+
Self { m, r, xq2 }
51+
}
52+
53+
/// Extract the current remainder in the range `[0, 2n)`
54+
fn partial_remainder(&self) -> U {
55+
// RR/2 = qm + r, 0 <= r < m
56+
// 2xq = uR + v, 0 <= v < R
57+
// muR = 2mxq - mv
58+
// = xRR - 2xr - mv
59+
// mu + (2xr + mv)/R == xR
60+
61+
// 0 <= 2xq < RR
62+
// R <= q < 2R
63+
// 0 <= x < R/2
64+
// R/4 < m < R/2
65+
// 0 <= r < m
66+
// 0 <= mv < mR
67+
// 0 <= 2xr < rR < mR
68+
69+
// 0 <= (2xr + mv)/R < 2m
70+
// Add `mu` to each term to obtain:
71+
// mu <= xR < mu + 2m
72+
73+
// Since `0 <= 2m < R`, `xR` is the only multiple of `R` between
74+
// `mu` and `m(u+2)`, so we can truncate the latter to find `x`.
75+
let _1 = U::ONE;
76+
self.m.widen_mul(self.xq2.hi() + (_1 + _1)).hi()
77+
}
78+
79+
/// Maps the remainder `x` to `(x << k) - un`,
80+
/// for a suitable quotient `u`, which is returned.
81+
fn shift_reduce(&mut self, k: u32) -> U {
82+
assert!(k < U::BITS);
83+
// 2xq << k = aRR/2 + b;
84+
let a = self.xq2.hi() >> (U::BITS - 1 - k);
85+
let (lo, hi) = (self.xq2 << k).lo_hi();
86+
let b = U::D::from_lo_hi(lo, hi & (U::MAX >> 1));
87+
88+
// (2xq << k) - aqm
89+
// = aRR/2 + b - aqm
90+
// = a(RR/2 - qm) + b
91+
// = ar + b
92+
self.xq2 = a.widen_mul(self.r) + b;
93+
a
94+
}
95+
96+
/// Maps the remainder `x` to `x(R/2) - un`,
97+
/// for a suitable quotient `u`, which is returned.
98+
fn word_reduce(&mut self) -> U {
99+
// 2xq = uR + v
100+
let (v, u) = self.xq2.lo_hi();
101+
// xqR - uqm
102+
// = uRR/2 + vR/2 - uRR/2 + ur
103+
// = ur + (v/2)R
104+
self.xq2 = u.widen_mul(self.r) + U::widen_hi(v >> 1);
105+
u
106+
}
107+
}
108+
109+
/// Compute the remainder `(x << e) % y` with unbounded integers.
110+
/// Requires `x < 2y` and `y.leading_zeros() >= 2`
111+
#[allow(dead_code)]
112+
pub fn linear_mul_reduction<U>(x: U, mut e: u32, y: U) -> U
113+
where
114+
U: HInt + Int<Unsigned = U>,
115+
U::D: NarrowingDiv,
116+
{
117+
assert!(y <= U::MAX >> 2);
118+
assert!(x < (y << 1));
119+
let _0 = U::ZERO;
120+
let _1 = U::ONE;
121+
122+
// power of two divisor
123+
if (y & (y - _1)).is_zero() {
124+
if e < U::BITS {
125+
return (x << e) & (y - _1);
126+
} else {
127+
return _0;
128+
}
129+
}
130+
131+
// shift the divisor so it has exactly two leading zeros
132+
let y_shift = y.leading_zeros() - 2;
133+
let mut m = Reducer::new(x, y << y_shift);
134+
e += y_shift;
135+
136+
while e >= U::BITS - 1 {
137+
m.word_reduce();
138+
e -= U::BITS - 1;
139+
}
140+
m.shift_reduce(e);
141+
142+
let rem = m.partial_remainder() >> y_shift;
143+
rem.checked_sub(y).unwrap_or(rem)
144+
}
145+
146+
#[cfg(test)]
147+
mod test {
148+
use crate::support::linear_mul_reduction;
149+
use crate::support::modular::Reducer;
150+
151+
#[test]
152+
fn reducer_ops() {
153+
for n in 33..=63_u8 {
154+
for x in 0..2 * n {
155+
let temp = Reducer::new(x, n);
156+
let n = n as u32;
157+
let x0 = temp.partial_remainder() as u32;
158+
assert_eq!(x as u32, x0);
159+
for k in 0..=7 {
160+
let mut red = temp.clone();
161+
let u = red.shift_reduce(k) as u32;
162+
let x1 = red.partial_remainder() as u32;
163+
assert_eq!(x1, (x0 << k) - u * n);
164+
assert!(x1 < 2 * n);
165+
assert!((red.xq2 as u32).is_multiple_of(2 * x1));
166+
167+
// `word_reduce` is equivalent to
168+
// `shift_reduce(U::BITS - 1)`
169+
if k == 7 {
170+
let mut alt = temp.clone();
171+
let w = alt.word_reduce();
172+
assert_eq!(u, w as u32);
173+
assert_eq!(alt, red);
174+
}
175+
}
176+
}
177+
}
178+
}
179+
#[test]
180+
fn reduction() {
181+
for y in 1..64u8 {
182+
for x in 0..2 * y {
183+
let mut r = x % y;
184+
for e in 0..100 {
185+
assert_eq!(r, linear_mul_reduction(x, e, y));
186+
// maintain the correct expected remainder
187+
r <<= 1;
188+
if r >= y {
189+
r -= y;
190+
}
191+
}
192+
}
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)