Add add/subtract functions that work in place, on slices

This is needed for the multiply optimizations in the next patch.
This commit is contained in:
Kent Overstreet 2015-12-10 12:33:28 -09:00
parent a1e57a48b2
commit 496ae0337c
1 changed files with 137 additions and 56 deletions

View File

@ -116,6 +116,38 @@ pub mod big_digit {
} }
} }
/*
* Generic functions for add/subtract/multiply with carry/borrow:
*/
// Add with carry:
#[inline]
fn adc(a: BigDigit, b: BigDigit, carry: &mut BigDigit) -> BigDigit {
let (hi, lo) = big_digit::from_doublebigdigit(
(a as DoubleBigDigit) +
(b as DoubleBigDigit) +
(*carry as DoubleBigDigit));
*carry = hi;
lo
}
// Subtract with borrow:
#[inline]
fn sbb(a: BigDigit, b: BigDigit, borrow: &mut BigDigit) -> BigDigit {
let (hi, lo) = big_digit::from_doublebigdigit(
big_digit::BASE
+ (a as DoubleBigDigit)
- (b as DoubleBigDigit)
- (*borrow as DoubleBigDigit));
/*
hi * (base) + lo == 1*(base) + ai - bi - borrow
=> ai - bi - borrow < 0 <=> hi == 0
*/
*borrow = if hi == 0 { 1 } else { 0 };
lo
}
/// A big unsigned integer type. /// A big unsigned integer type.
/// ///
/// A `BigUint`-typed value `BigUint { data: vec!(a, b, c) }` represents a number /// A `BigUint`-typed value `BigUint { data: vec!(a, b, c) }` represents a number
@ -467,69 +499,112 @@ impl Unsigned for BigUint {}
forward_all_binop_to_val_ref_commutative!(impl Add for BigUint, add); forward_all_binop_to_val_ref_commutative!(impl Add for BigUint, add);
// Only for the Add impl:
#[must_use]
#[inline]
fn __add2(a: &mut [BigDigit], b: &[BigDigit]) -> BigDigit {
let mut b_iter = b.iter();
let mut carry = 0;
for ai in a.iter_mut() {
if let Some(bi) = b_iter.next() {
*ai = adc(*ai, *bi, &mut carry);
} else if carry != 0 {
*ai = adc(*ai, 0, &mut carry);
} else {
break;
}
}
debug_assert!(b_iter.next() == None);
carry
}
/// /Two argument addition of raw slices:
/// a += b
///
/// The caller _must_ ensure that a is big enough to store the result - typically this means
/// resizing a to max(a.len(), b.len()) + 1, to fit a possible carry.
fn add2(a: &mut [BigDigit], b: &[BigDigit]) {
let carry = __add2(a, b);
debug_assert!(carry == 0);
}
/*
* We'd really prefer to avoid using add2/sub2 directly as much as possible - since they make the
* caller entirely responsible for ensuring a's vector is big enough, and that the result is
* normalized, they're rather error prone and verbose:
*
* We could implement the Add and Sub traits for BigUint + BigDigit slices, like below - this works
* great, except that then it becomes the module's public interface, which we probably don't want:
*
* I'm keeping the code commented out, because I think this is worth revisiting:
impl<'a> Add<&'a [BigDigit]> for BigUint {
type Output = BigUint;
fn add(mut self, other: &[BigDigit]) -> BigUint {
if self.data.len() < other.len() {
let extra = other.len() - self.data.len();
self.data.extend(repeat(0).take(extra));
}
let carry = __add2(&mut self.data[..], other);
if carry != 0 {
self.data.push(carry);
}
self
}
}
*/
impl<'a> Add<&'a BigUint> for BigUint { impl<'a> Add<&'a BigUint> for BigUint {
type Output = BigUint; type Output = BigUint;
fn add(self, other: &BigUint) -> BigUint { fn add(mut self, other: &BigUint) -> BigUint {
let mut sum = self.data; if self.data.len() < other.data.len() {
let extra = other.data.len() - self.data.len();
if other.data.len() > sum.len() { self.data.extend(repeat(0).take(extra));
let additional = other.data.len() - sum.len();
sum.reserve(additional);
sum.extend(repeat(ZERO_BIG_DIGIT).take(additional));
}
let other_iter = other.data.iter().cloned().chain(repeat(ZERO_BIG_DIGIT));
let mut carry = 0;
for (a, b) in sum.iter_mut().zip(other_iter) {
let d = (*a as DoubleBigDigit)
+ (b as DoubleBigDigit)
+ (carry as DoubleBigDigit);
let (hi, lo) = big_digit::from_doublebigdigit(d);
carry = hi;
*a = lo;
} }
if carry != 0 { sum.push(carry); } let carry = __add2(&mut self.data[..], &other.data[..]);
BigUint::new(sum) if carry != 0 {
self.data.push(carry);
}
self
} }
} }
forward_all_binop_to_val_ref!(impl Sub for BigUint, sub); forward_all_binop_to_val_ref!(impl Sub for BigUint, sub);
fn sub2(a: &mut [BigDigit], b: &[BigDigit]) {
let mut b_iter = b.iter();
let mut borrow = 0;
for ai in a.iter_mut() {
if let Some(bi) = b_iter.next() {
*ai = sbb(*ai, *bi, &mut borrow);
} else if borrow != 0 {
*ai = sbb(*ai, 0, &mut borrow);
} else {
break;
}
}
/* note: we're _required_ to fail on underflow */
assert!(borrow == 0 && b_iter.all(|x| *x == 0),
"Cannot subtract b from a because b is larger than a.");
}
impl<'a> Sub<&'a BigUint> for BigUint { impl<'a> Sub<&'a BigUint> for BigUint {
type Output = BigUint; type Output = BigUint;
fn sub(self, other: &BigUint) -> BigUint { fn sub(mut self, other: &BigUint) -> BigUint {
let mut diff = self.data; sub2(&mut self.data[..], &other.data[..]);
let other = &other.data; self.normalize()
assert!(diff.len() >= other.len(), "arithmetic operation overflowed");
let mut borrow: DoubleBigDigit = 0;
for (a, &b) in diff.iter_mut().zip(other.iter()) {
let d = big_digit::BASE - borrow
+ (*a as DoubleBigDigit)
- (b as DoubleBigDigit);
let (hi, lo) = big_digit::from_doublebigdigit(d);
/*
hi * (base) + lo == 1*(base) + ai - bi - borrow
=> ai - bi - borrow < 0 <=> hi == 0
*/
borrow = if hi == 0 { 1 } else { 0 };
*a = lo;
}
for a in &mut diff[other.len()..] {
if borrow == 0 { break }
let d = big_digit::BASE - borrow
+ (*a as DoubleBigDigit);
let (hi, lo) = big_digit::from_doublebigdigit(d);
borrow = if hi == 0 { 1 } else { 0 };
*a = lo;
}
assert!(borrow == 0, "arithmetic operation overflowed");
BigUint::new(diff)
} }
} }
@ -982,12 +1057,8 @@ impl BigUint {
/// ///
/// The digits are in little-endian base 2^32. /// The digits are in little-endian base 2^32.
#[inline] #[inline]
pub fn new(mut digits: Vec<BigDigit>) -> BigUint { pub fn new(digits: Vec<BigDigit>) -> BigUint {
// omit trailing zeros BigUint { data: digits }.normalize()
while let Some(&0) = digits.last() {
digits.pop();
}
BigUint { data: digits }
} }
/// Creates and initializes a `BigUint`. /// Creates and initializes a `BigUint`.
@ -1176,6 +1247,16 @@ impl BigUint {
let zeros = self.data.last().unwrap().leading_zeros(); let zeros = self.data.last().unwrap().leading_zeros();
return self.data.len()*big_digit::BITS - zeros as usize; return self.data.len()*big_digit::BITS - zeros as usize;
} }
/// Strips off trailing zero bigdigits - comparisons require the last element in the vector to
/// be nonzero.
#[inline]
fn normalize(mut self) -> BigUint {
while let Some(&0) = self.data.last() {
self.data.pop();
}
self
}
} }
// `DoubleBigDigit` size dependent // `DoubleBigDigit` size dependent