|
| 1 | +//! Implementation of constant-time division via reciprocal precomputation, as described in |
| 2 | +//! "Improved Division by Invariant Integers" by Niels Möller and Torbjorn Granlund |
| 3 | +//! (DOI: 10.1109/TC.2010.143, <https://gmplib.org/~tege/division-paper.pdf>). |
| 4 | +use crate::{BoxedUint, ConstChoice, Limb, Reciprocal, WideWord, Word}; |
| 5 | + |
| 6 | +/// Multiplies `x` and `y`, returning the most significant |
| 7 | +/// and the least significant words as `(hi, lo)`. |
| 8 | +#[inline(always)] |
| 9 | +const fn mulhilo(x: Word, y: Word) -> (Word, Word) { |
| 10 | + let res = (x as WideWord) * (y as WideWord); |
| 11 | + ((res >> Word::BITS) as Word, res as Word) |
| 12 | +} |
| 13 | + |
| 14 | +/// Adds wide numbers represented by pairs of (most significant word, least significant word) |
| 15 | +/// and returns the result in the same format `(hi, lo)`. |
| 16 | +#[inline(always)] |
| 17 | +const fn addhilo(x_hi: Word, x_lo: Word, y_hi: Word, y_lo: Word) -> (Word, Word) { |
| 18 | + let res = (((x_hi as WideWord) << Word::BITS) | (x_lo as WideWord)) |
| 19 | + + (((y_hi as WideWord) << Word::BITS) | (y_lo as WideWord)); |
| 20 | + ((res >> Word::BITS) as Word, res as Word) |
| 21 | +} |
| 22 | + |
| 23 | +/// Calculate the quotient and the remainder of the division of a wide word |
| 24 | +/// (supplied as high and low words) by `d`, with a precalculated reciprocal `v`. |
| 25 | +#[inline(always)] |
| 26 | +const fn div2by1(u1: Word, u0: Word, reciprocal: &Reciprocal) -> (Word, Word) { |
| 27 | + let d = reciprocal.divisor_normalized; |
| 28 | + |
| 29 | + debug_assert!(d >= (1 << (Word::BITS - 1))); |
| 30 | + debug_assert!(u1 < d); |
| 31 | + |
| 32 | + let (q1, q0) = mulhilo(reciprocal.reciprocal, u1); |
| 33 | + let (q1, q0) = addhilo(q1, q0, u1, u0); |
| 34 | + let q1 = q1.wrapping_add(1); |
| 35 | + let r = u0.wrapping_sub(q1.wrapping_mul(d)); |
| 36 | + |
| 37 | + let r_gt_q0 = ConstChoice::from_word_lt(q0, r); |
| 38 | + let q1 = r_gt_q0.select_word(q1, q1.wrapping_sub(1)); |
| 39 | + let r = r_gt_q0.select_word(r, r.wrapping_add(d)); |
| 40 | + |
| 41 | + // If this was a normal `if`, we wouldn't need wrapping ops, because there would be no overflow. |
| 42 | + // But since we calculate both results either way, we have to wrap. |
| 43 | + // Added an assert to still check the lack of overflow in debug mode. |
| 44 | + debug_assert!(r < d || q1 < Word::MAX); |
| 45 | + let r_ge_d = ConstChoice::from_word_le(d, r); |
| 46 | + let q1 = r_ge_d.select_word(q1, q1.wrapping_add(1)); |
| 47 | + let r = r_ge_d.select_word(r, r.wrapping_sub(d)); |
| 48 | + |
| 49 | + (q1, r) |
| 50 | +} |
| 51 | + |
| 52 | +/// Divides `u` by the divisor encoded in the `reciprocal`, and returns |
| 53 | +/// the quotient and the remainder. |
| 54 | +#[inline(always)] |
| 55 | +#[allow(dead_code, unused_variables)] |
| 56 | +pub(crate) fn div_rem_limb_with_reciprocal( |
| 57 | + u: &BoxedUint, |
| 58 | + reciprocal: &Reciprocal, |
| 59 | +) -> (BoxedUint, Limb) { |
| 60 | + let nlimbs = u.as_limbs().len(); |
| 61 | + let (u_shifted, u_hi) = u.shl_limb(reciprocal.shift); |
| 62 | + let mut r = u_hi.0; |
| 63 | + let mut q = vec![Limb::ZERO; nlimbs]; |
| 64 | + |
| 65 | + let mut j = nlimbs; |
| 66 | + while j > 0 { |
| 67 | + j -= 1; |
| 68 | + let (qj, rj) = div2by1(r, u_shifted.as_limbs()[j].0, reciprocal); |
| 69 | + q[j] = Limb(qj); |
| 70 | + r = rj; |
| 71 | + } |
| 72 | + (BoxedUint { limbs: q.into() }, Limb(r >> reciprocal.shift)) |
| 73 | +} |
| 74 | + |
| 75 | +#[cfg(test)] |
| 76 | +mod tests { |
| 77 | + use super::{div2by1, Reciprocal}; |
| 78 | + use crate::{Limb, NonZero, Word}; |
| 79 | + #[test] |
| 80 | + fn div2by1_overflow() { |
| 81 | + // A regression test for a situation when in div2by1() an operation (`q1 + 1`) |
| 82 | + // that is protected from overflowing by a condition in the original paper (`r >= d`) |
| 83 | + // still overflows because we're calculating the results for both branches. |
| 84 | + let r = Reciprocal::new(NonZero::new(Limb(Word::MAX - 1)).unwrap()); |
| 85 | + assert_eq!( |
| 86 | + div2by1(Word::MAX - 2, Word::MAX - 63, &r), |
| 87 | + (Word::MAX, Word::MAX - 65) |
| 88 | + ); |
| 89 | + } |
| 90 | +} |
0 commit comments