Skip to content

Commit f5094eb

Browse files
handle remainder as a wide word in div3by2 (#647)
Signed-off-by: Andrew Whitehead <[email protected]>
1 parent 4c84cbc commit f5094eb

File tree

2 files changed

+51
-15
lines changed

2 files changed

+51
-15
lines changed

src/const_choice.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use subtle::{Choice, CtOption};
22

3-
use crate::{modular::BernsteinYangInverter, Limb, NonZero, Odd, Uint, Word};
3+
use crate::{modular::BernsteinYangInverter, Limb, NonZero, Odd, Uint, WideWord, Word};
44

55
/// A boolean value returned by constant-time `const fn`s.
66
// TODO: should be replaced by `subtle::Choice` or `CtOption`
@@ -49,6 +49,14 @@ impl ConstChoice {
4949
Self(value.wrapping_neg())
5050
}
5151

52+
/// Returns the truthy value if `value == 1`, and the falsy value if `value == 0`.
53+
/// Panics for other values.
54+
#[inline]
55+
pub(crate) const fn from_wide_word_lsb(value: WideWord) -> Self {
56+
debug_assert!(value == 0 || value == 1);
57+
Self(value.wrapping_neg() as Word)
58+
}
59+
5260
#[inline]
5361
pub(crate) const fn from_u32_lsb(value: u32) -> Self {
5462
debug_assert!(value == 0 || value == 1);
@@ -129,6 +137,14 @@ impl ConstChoice {
129137
Self::from_word_lsb(bit)
130138
}
131139

140+
/// Returns the truthy value if `x <= y` and the falsy value otherwise.
141+
#[inline]
142+
pub(crate) const fn from_wide_word_le(x: WideWord, y: WideWord) -> Self {
143+
// See "Hacker's Delight" 2nd ed, section 2-12 (Comparison predicates)
144+
let bit = (((!x) | y) & ((x ^ y) | !(y.wrapping_sub(x)))) >> (WideWord::BITS - 1);
145+
Self::from_wide_word_lsb(bit)
146+
}
147+
132148
/// Returns the truthy value if `x <= y` and the falsy value otherwise.
133149
#[inline]
134150
pub(crate) const fn from_u32_le(x: u32, y: u32) -> Self {
@@ -172,6 +188,13 @@ impl ConstChoice {
172188
a ^ (self.0 & (a ^ b))
173189
}
174190

191+
/// Return `b` if `self` is truthy, otherwise return `a`.
192+
#[inline]
193+
pub(crate) const fn select_wide_word(&self, a: WideWord, b: WideWord) -> WideWord {
194+
let mask = ((self.0 as WideWord) << Word::BITS) | (self.0 as WideWord);
195+
a ^ (mask & (a ^ b))
196+
}
197+
175198
/// Return `b` if `self` is truthy, otherwise return `a`.
176199
#[inline]
177200
pub(crate) const fn select_u32(&self, a: u32, b: u32) -> u32 {
@@ -423,7 +446,7 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
423446
#[cfg(test)]
424447
mod tests {
425448
use super::ConstChoice;
426-
use crate::Word;
449+
use crate::{WideWord, Word};
427450

428451
#[test]
429452
fn from_u64_lsb() {
@@ -445,6 +468,13 @@ mod tests {
445468
assert_eq!(ConstChoice::from_word_gt(6, 5), ConstChoice::TRUE);
446469
}
447470

471+
#[test]
472+
fn from_wide_word_le() {
473+
assert_eq!(ConstChoice::from_wide_word_le(4, 5), ConstChoice::TRUE);
474+
assert_eq!(ConstChoice::from_wide_word_le(5, 5), ConstChoice::TRUE);
475+
assert_eq!(ConstChoice::from_wide_word_le(6, 5), ConstChoice::FALSE);
476+
}
477+
448478
#[test]
449479
fn select_u32() {
450480
let a: u32 = 1;
@@ -468,4 +498,12 @@ mod tests {
468498
assert_eq!(ConstChoice::TRUE.select_word(a, b), b);
469499
assert_eq!(ConstChoice::FALSE.select_word(a, b), a);
470500
}
501+
502+
#[test]
503+
fn select_wide_word() {
504+
let a: WideWord = (1 << Word::BITS) + 1;
505+
let b: WideWord = (3 << Word::BITS) + 4;
506+
assert_eq!(ConstChoice::TRUE.select_wide_word(a, b), b);
507+
assert_eq!(ConstChoice::FALSE.select_wide_word(a, b), a);
508+
}
471509
}

src/uint/div_limb.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,28 +156,26 @@ pub(crate) const fn div3by2(
156156
u0: Word,
157157
reciprocal: &Reciprocal,
158158
v0: Word,
159-
) -> (Word, Word) {
159+
) -> (Word, WideWord) {
160160
// This method corresponds to Algorithm Q:
161161
// https://janmr.com/blog/2014/04/basic-multiple-precision-long-division/
162162

163-
let maxed = ConstChoice::from_word_eq(u2, reciprocal.divisor_normalized);
164-
let (mut quo, mut rem) = div2by1(maxed.select_word(u2, 0), u1, reciprocal);
163+
let q_maxed = ConstChoice::from_word_eq(u2, reciprocal.divisor_normalized);
164+
let (mut quo, rem) = div2by1(q_maxed.select_word(u2, 0), u1, reciprocal);
165165
// When the leading dividend word equals the leading divisor word, cap the quotient
166166
// at Word::MAX and set the remainder to the sum of the top dividend words.
167-
quo = maxed.select_word(quo, Word::MAX);
168-
rem = maxed.select_word(rem, u2.saturating_add(u1));
167+
quo = q_maxed.select_word(quo, Word::MAX);
168+
let mut rem = q_maxed.select_wide_word(rem as WideWord, (u2 as WideWord) + (u1 as WideWord));
169169

170170
let mut i = 0;
171171
while i < 2 {
172172
let qy = (quo as WideWord) * (v0 as WideWord);
173-
let rx = ((rem as WideWord) << Word::BITS) | (u0 as WideWord);
174-
// Constant-time check for q*y[-2] < r*x[-1], based on ConstChoice::from_word_lt
175-
let diff = ConstChoice::from_word_lsb(
176-
((((!rx) & qy) | (((!rx) | qy) & (rx.wrapping_sub(qy)))) >> (WideWord::BITS - 1))
177-
as Word,
178-
);
179-
quo = diff.select_word(quo, quo.saturating_sub(1));
180-
rem = diff.select_word(rem, rem.saturating_add(reciprocal.divisor_normalized));
173+
let rx = (rem << Word::BITS) | (u0 as WideWord);
174+
// If r < b and q*y[-2] > r*x[-1], then set q = q - 1 and r = r + v1
175+
let done = ConstChoice::from_word_nonzero((rem >> Word::BITS) as Word)
176+
.or(ConstChoice::from_wide_word_le(qy, rx));
177+
quo = done.select_word(quo.saturating_sub(1), quo);
178+
rem = done.select_wide_word(rem + (reciprocal.divisor_normalized as WideWord), rem);
181179
i += 1;
182180
}
183181

0 commit comments

Comments
 (0)