|
3 | 3 | use crate::{
|
4 | 4 | uint::{boxed, div_limb::div3by2},
|
5 | 5 | BoxedUint, CheckedDiv, ConstChoice, ConstantTimeSelect, DivRemLimb, Limb, NonZero, Reciprocal,
|
6 |
| - RemLimb, Wrapping, |
| 6 | + RemLimb, Word, Wrapping, |
7 | 7 | };
|
8 | 8 | use core::ops::{Div, DivAssign, Rem, RemAssign};
|
9 |
| -use subtle::{Choice, ConstantTimeLess, CtOption}; |
| 9 | +use subtle::CtOption; |
10 | 10 |
|
11 | 11 | impl BoxedUint {
|
12 | 12 | /// Computes `self / rhs` using a pre-made reciprocal,
|
@@ -118,41 +118,113 @@ impl BoxedUint {
|
118 | 118 | /// Perform checked division, returning a [`CtOption`] which `is_some`
|
119 | 119 | /// only if the rhs != 0
|
120 | 120 | pub fn checked_div(&self, rhs: &Self) -> CtOption<Self> {
|
121 |
| - let q = self.div_rem_unchecked(rhs).0; |
122 |
| - CtOption::new(q, !rhs.is_zero()) |
| 121 | + let is_nz = rhs.is_nonzero(); |
| 122 | + let nz = NonZero(Self::ct_select( |
| 123 | + &Self::one_with_precision(self.bits_precision()), |
| 124 | + rhs, |
| 125 | + is_nz, |
| 126 | + )); |
| 127 | + let q = self.div_rem_unchecked(&nz).0; |
| 128 | + CtOption::new(q, is_nz) |
123 | 129 | }
|
124 | 130 |
|
125 |
| - /// Computes `self` / `rhs`, returns the quotient (q), remainder (r) without checking if `rhs` |
126 |
| - /// is zero. |
127 |
| - /// |
128 |
| - /// This function is constant-time with respect to both `self` and `rhs`. |
129 | 131 | fn div_rem_unchecked(&self, rhs: &Self) -> (Self, Self) {
|
130 |
| - debug_assert_eq!(self.bits_precision(), rhs.bits_precision()); |
131 |
| - let mb = rhs.bits(); |
132 |
| - let bits_precision = self.bits_precision(); |
133 |
| - let mut rem = self.clone(); |
134 |
| - let mut quo = Self::zero_with_precision(bits_precision); |
135 |
| - let (mut c, _overflow) = rhs.overflowing_shl(bits_precision - mb); |
136 |
| - let mut i = bits_precision; |
137 |
| - let mut done = Choice::from(0u8); |
138 |
| - |
139 |
| - loop { |
140 |
| - let (mut r, borrow) = rem.sbb(&c, Limb::ZERO); |
141 |
| - rem.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done)); |
142 |
| - r = quo.bitor(&Self::one()); |
143 |
| - quo.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done)); |
144 |
| - if i == 0 { |
145 |
| - break; |
| 132 | + // Based on Section 4.3.1, of The Art of Computer Programming, Volume 2, by Donald E. Knuth. |
| 133 | + // Further explanation at https://janmr.com/blog/2014/04/basic-multiple-precision-long-division/ |
| 134 | + |
| 135 | + let size = self.limbs.len(); |
| 136 | + assert_eq!( |
| 137 | + size, |
| 138 | + rhs.limbs.len(), |
| 139 | + "the precision of the divisor must match the dividend" |
| 140 | + ); |
| 141 | + |
| 142 | + // Short circuit for single-word precision |
| 143 | + if size == 1 { |
| 144 | + let (quo, rem_limb) = self.div_rem_limb(rhs.limbs[0].to_nz().expect("zero divisor")); |
| 145 | + let mut rem = Self::zero_with_precision(self.bits_precision()); |
| 146 | + rem.limbs[0] = rem_limb; |
| 147 | + return (quo, rem); |
| 148 | + } |
| 149 | + |
| 150 | + let dbits = rhs.bits(); |
| 151 | + assert!(dbits > 0, "zero divisor"); |
| 152 | + let dwords = (dbits + Limb::BITS - 1) / Limb::BITS; |
| 153 | + let lshift = (Limb::BITS - (dbits % Limb::BITS)) % Limb::BITS; |
| 154 | + |
| 155 | + // Shift entire divisor such that the high bit is set |
| 156 | + let mut y = rhs.shl((size as u32) * Limb::BITS - dbits).to_limbs(); |
| 157 | + // Shift the dividend to align the words |
| 158 | + let (x, mut x_hi) = self.shl_limb(lshift); |
| 159 | + let mut x = x.to_limbs(); |
| 160 | + let mut xi = size - 1; |
| 161 | + let mut x_lo = x[size - 1]; |
| 162 | + let mut i; |
| 163 | + let mut carry; |
| 164 | + |
| 165 | + let reciprocal = Reciprocal::new(y[size - 1].to_nz().expect("zero divisor")); |
| 166 | + |
| 167 | + while xi > 0 { |
| 168 | + // Divide high dividend words by the high divisor word to estimate the quotient word |
| 169 | + let (mut quo, _) = div3by2(x_hi.0, x_lo.0, x[xi - 1].0, &reciprocal, y[size - 2].0); |
| 170 | + |
| 171 | + // This loop is a no-op once xi is smaller than the number of words in the divisor |
| 172 | + let done = ConstChoice::from_u32_lt(xi as u32, dwords - 1); |
| 173 | + quo = done.select_word(quo, 0); |
| 174 | + |
| 175 | + // Subtract q*divisor from the dividend |
| 176 | + carry = Limb::ZERO; |
| 177 | + let mut borrow = Limb::ZERO; |
| 178 | + let mut tmp; |
| 179 | + i = 0; |
| 180 | + while i <= xi { |
| 181 | + (tmp, carry) = Limb::ZERO.mac(y[size - xi + i - 1], Limb(quo), carry); |
| 182 | + (x[i], borrow) = x[i].sbb(tmp, borrow); |
| 183 | + i += 1; |
146 | 184 | }
|
147 |
| - i -= 1; |
148 |
| - // when `i < mb`, the computation is actually done, so we ensure `quo` and `rem` |
149 |
| - // aren't modified further (but do the remaining iterations anyway to be constant-time) |
150 |
| - done = i.ct_lt(&mb); |
151 |
| - c.shr1_assign(); |
152 |
| - quo.ct_assign(&quo.shl1(), !done); |
| 185 | + (_, borrow) = x_hi.sbb(carry, borrow); |
| 186 | + |
| 187 | + // If the subtraction borrowed, then decrement q and add back the divisor |
| 188 | + // The probability of this being needed is very low, about 2/(Limb::MAX+1) |
| 189 | + let ct_borrow = ConstChoice::from_word_mask(borrow.0); |
| 190 | + carry = Limb::ZERO; |
| 191 | + i = 0; |
| 192 | + while i <= xi { |
| 193 | + (x[i], carry) = x[i].adc( |
| 194 | + Limb::select(Limb::ZERO, y[size - xi + i - 1], ct_borrow), |
| 195 | + carry, |
| 196 | + ); |
| 197 | + i += 1; |
| 198 | + } |
| 199 | + quo = ct_borrow.select_word(quo, quo.saturating_sub(1)); |
| 200 | + |
| 201 | + // Store the quotient within dividend and set x_hi to the current highest word |
| 202 | + x_hi = Limb::select(x[xi], x_hi, done); |
| 203 | + x[xi] = Limb::select(Limb(quo), x[xi], done); |
| 204 | + x_lo = Limb::select(x[xi - 1], x_lo, done); |
| 205 | + xi -= 1; |
| 206 | + } |
| 207 | + |
| 208 | + let limb_div = ConstChoice::from_u32_eq(1, dwords); |
| 209 | + // Calculate quotient and remainder for the case where the divisor is a single word |
| 210 | + let (quo2, rem2) = div3by2(x_hi.0, x_lo.0, 0, &reciprocal, 0); |
| 211 | + |
| 212 | + // Adjust the quotient for single limb division |
| 213 | + x[0] = Limb::select(x[0], Limb(quo2), limb_div); |
| 214 | + |
| 215 | + // Copy out the remainder |
| 216 | + y[0] = Limb::select(x[0], Limb(rem2 as Word), limb_div); |
| 217 | + i = 1; |
| 218 | + while i < size { |
| 219 | + y[i] = Limb::select(Limb::ZERO, x[i], ConstChoice::from_u32_lt(i as u32, dwords)); |
| 220 | + y[i] = Limb::select(y[i], x_hi, ConstChoice::from_u32_eq(i as u32, dwords - 1)); |
| 221 | + i += 1; |
153 | 222 | }
|
154 | 223 |
|
155 |
| - (quo, rem) |
| 224 | + ( |
| 225 | + Self { limbs: x }.shr((dwords - 1) * Limb::BITS), |
| 226 | + Self { limbs: y }.shr(lshift), |
| 227 | + ) |
156 | 228 | }
|
157 | 229 | }
|
158 | 230 |
|
|
0 commit comments