Skip to content

Commit

Permalink
Add various methods to Bignum:
Browse files Browse the repository at this point in the history
- Exposing digits and individual bits
- Counting the number of bits
- Add small (digit-sized) values
- Multiplication by power of 5
- Division with remainder

All are necessary for decimal to floating point conversions.
All but the most trivial ones come with tests.
  • Loading branch information
Robin Kruppe committed Aug 8, 2015
1 parent 7ff1020 commit 7ebd7f3
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 5 deletions.
146 changes: 141 additions & 5 deletions src/libcore/num/flt2dec/bignum.rs
Expand Up @@ -13,7 +13,7 @@
//! This is designed to avoid the heap allocation at expense of stack memory.
//! The most used bignum type, `Big32x40`, is limited by 32 × 40 = 1,280 bits
//! and will take at most 160 bytes of stack memory. This is more than enough
//! for formatting and parsing all possible finite `f64` values.
//! for round-tripping all possible finite `f64` values.
//!
//! In principle it is possible to have multiple bignum types for different
//! inputs, but we don't do so to avoid the code bloat. Each bignum is still
Expand Down Expand Up @@ -92,6 +92,14 @@ impl_full_ops! {
// u64: add(intrinsics::u64_add_with_overflow), mul/div(u128); // see RFC #521 for enabling this.
}

/// Table of powers of 5 representable in digits. Specifically, the largest {u8, u16, u32} value
/// that's a power of five, plus the corresponding exponent. Used in `mul_pow5`.
const SMALL_POW5: [(u64, usize); 3] = [
(125, 3),
(15625, 6),
(1_220_703_125, 13),
];

macro_rules! define_bignum {
($name:ident: type=$ty:ty, n=$n:expr) => (
/// Stack-allocated arbitrary-precision (up to certain limit) integer.
Expand Down Expand Up @@ -135,9 +143,53 @@ macro_rules! define_bignum {
$name { size: sz, base: base }
}

/// Return the internal digits as a slice `[a, b, c, ...]` such that the numeric
/// value is `a + b * 2^W + c * 2^(2W) + ...` where `W` is the number of bits in
/// the digit type.
pub fn digits(&self) -> &[$ty] {
&self.base[..self.size]
}

/// Return the `i`-th bit where bit 0 is the least significant one.
/// In other words, the bit with weight `2^i`.
pub fn get_bit(&self, i: usize) -> u8 {
use mem;

let digitbits = mem::size_of::<$ty>() * 8;
let d = i / digitbits;
let b = i % digitbits;
((self.base[d] >> b) & 1) as u8
}

/// Returns true if the bignum is zero.
pub fn is_zero(&self) -> bool {
self.base[..self.size].iter().all(|&v| v == 0)
self.digits().iter().all(|&v| v == 0)
}

/// Returns the number of bits necessary to represent this value. Note that zero
/// is considered to need 0 bits.
pub fn bit_length(&self) -> usize {
use mem;

let digitbits = mem::size_of::<$ty>()* 8;
// Skip over the most significant digits which are zero.
let nonzero = match self.digits().iter().rposition(|&x| x != 0) {
Some(n) => {
let end = self.size - n;
&self.digits()[..end]
}
None => {
// There are no non-zero digits, i.e. the number is zero.
return 0;
}
};
// This could be optimized with leading_zeros() and bit shifts, but that's
// probably not worth the hassle.
let mut i = nonzero.len() * digitbits - 1;
while self.get_bit(i) == 0 {
i -= 1;
}
i + 1
}

/// Adds `other` to itself and returns its own mutable reference.
Expand All @@ -160,6 +212,24 @@ macro_rules! define_bignum {
self
}

pub fn add_small<'a>(&'a mut self, other: $ty) -> &'a mut $name {
use num::flt2dec::bignum::FullOps;

let (mut carry, v) = self.base[0].full_add(other, false);
self.base[0] = v;
let mut i = 1;
while carry {
let (c, v) = self.base[i].full_add(0, carry);
self.base[i] = v;
carry = c;
i += 1;
}
if i > self.size {
self.size = i;
}
self
}

/// Subtracts `other` from itself and returns its own mutable reference.
pub fn sub<'a>(&'a mut self, other: &$name) -> &'a mut $name {
use cmp;
Expand Down Expand Up @@ -238,6 +308,34 @@ macro_rules! define_bignum {
self
}

/// Multiplies itself by `5^e` and returns its own mutable reference.
pub fn mul_pow5<'a>(&'a mut self, mut e: usize) -> &'a mut $name {
use mem;
use num::flt2dec::bignum::SMALL_POW5;

// There are exactly n trailing zeros on 2^n, and the only relevant digit sizes
// are consecutive powers of two, so this is well suited index for the table.
let table_index = mem::size_of::<$ty>().trailing_zeros() as usize;
let (small_power, small_e) = SMALL_POW5[table_index];
let small_power = small_power as $ty;

// Multiply with the largest single-digit power as long as possible ...
while e >= small_e {
self.mul_small(small_power);
e -= small_e;
}

// ... then finish off the remainder.
let mut rest_power = 1;
for _ in 0..e {
rest_power *= 5;
}
self.mul_small(rest_power);

self
}


/// Multiplies itself by a number described by `other[0] + other[1] * 2^W +
/// other[2] * 2^(2W) + ...` (where `W` is the number of bits in the digit type)
/// and returns its own mutable reference.
Expand Down Expand Up @@ -269,9 +367,9 @@ macro_rules! define_bignum {

let mut ret = [0; $n];
let retsz = if self.size < other.len() {
mul_inner(&mut ret, &self.base[..self.size], other)
mul_inner(&mut ret, &self.digits(), other)
} else {
mul_inner(&mut ret, other, &self.base[..self.size])
mul_inner(&mut ret, other, &self.digits())
};
self.base = ret;
self.size = retsz;
Expand All @@ -294,6 +392,45 @@ macro_rules! define_bignum {
}
(self, borrow)
}

/// Divide self by another bignum, overwriting `q` with the quotient and `r` with the
/// remainder.
pub fn div_rem(&self, d: &$name, q: &mut $name, r: &mut $name) {
use mem;

// Stupid slow base-2 long division taken from
// https://en.wikipedia.org/wiki/Division_algorithm
// FIXME use a greater base ($ty) for the long division.
assert!(!d.is_zero());
let digitbits = mem::size_of::<$ty>() * 8;
for digit in &mut q.base[..] {
*digit = 0;
}
for digit in &mut r.base[..] {
*digit = 0;
}
r.size = d.size;
q.size = 1;
let mut q_is_zero = true;
let end = self.bit_length();
for i in (0..end).rev() {
r.mul_pow2(1);
r.base[0] |= self.get_bit(i) as $ty;
if &*r >= d {
r.sub(d);
// Set bit `i` of q to 1.
let digit_idx = i / digitbits;
let bit_idx = i % digitbits;
if q_is_zero {
q.size = digit_idx + 1;
q_is_zero = false;
}
q.base[digit_idx] |= 1 << bit_idx;
}
}
debug_assert!(q.base[q.size..].iter().all(|&d| d == 0));
debug_assert!(r.base[r.size..].iter().all(|&d| d == 0));
}
}

impl ::cmp::PartialEq for $name {
Expand Down Expand Up @@ -355,4 +492,3 @@ pub mod tests {
use prelude::v1::*;
define_bignum!(Big8x3: type=u8, n=3);
}

89 changes: 89 additions & 0 deletions src/libcoretest/num/flt2dec/bignum.rs
Expand Up @@ -39,6 +39,23 @@ fn test_add_overflow_2() {
Big::from_u64(0xffffff).add(&Big::from_small(1));
}

#[test]
fn test_add_small() {
assert_eq!(*Big::from_small(3).add_small(4), Big::from_small(7));
assert_eq!(*Big::from_small(3).add_small(0), Big::from_small(3));
assert_eq!(*Big::from_small(0).add_small(3), Big::from_small(3));
assert_eq!(*Big::from_small(7).add_small(250), Big::from_u64(257));
assert_eq!(*Big::from_u64(0x7fff).add_small(1), Big::from_u64(0x8000));
assert_eq!(*Big::from_u64(0x2ffe).add_small(0x35), Big::from_u64(0x3033));
assert_eq!(*Big::from_small(0xdc).add_small(0x89), Big::from_u64(0x165));
}

#[test]
#[should_panic]
fn test_add_small_overflow() {
Big::from_u64(0xffffff).add_small(1);
}

#[test]
fn test_sub() {
assert_eq!(*Big::from_small(7).sub(&Big::from_small(4)), Big::from_small(3));
Expand Down Expand Up @@ -97,6 +114,30 @@ fn test_mul_pow2_overflow_2() {
Big::from_u64(0x123).mul_pow2(16);
}

#[test]
fn test_mul_pow5() {
assert_eq!(*Big::from_small(42).mul_pow5(0), Big::from_small(42));
assert_eq!(*Big::from_small(1).mul_pow5(2), Big::from_small(25));
assert_eq!(*Big::from_small(1).mul_pow5(4), Big::from_u64(25 * 25));
assert_eq!(*Big::from_small(4).mul_pow5(3), Big::from_u64(500));
assert_eq!(*Big::from_small(140).mul_pow5(2), Big::from_u64(25 * 140));
assert_eq!(*Big::from_small(25).mul_pow5(1), Big::from_small(125));
assert_eq!(*Big::from_small(125).mul_pow5(7), Big::from_u64(9765625));
assert_eq!(*Big::from_small(0).mul_pow5(127), Big::from_small(0));
}

#[test]
#[should_panic]
fn test_mul_pow5_overflow_1() {
Big::from_small(1).mul_pow5(12);
}

#[test]
#[should_panic]
fn test_mul_pow5_overflow_2() {
Big::from_small(230).mul_pow5(8);
}

#[test]
fn test_mul_digits() {
assert_eq!(*Big::from_small(3).mul_digits(&[5]), Big::from_small(15));
Expand Down Expand Up @@ -132,6 +173,25 @@ fn test_div_rem_small() {
(Big::from_u64(0x10000 / 123), (0x10000u64 % 123) as u8));
}

#[test]
fn test_div_rem() {
fn div_rem(n: u64, d: u64) -> (Big, Big) {
let mut q = Big::from_small(42);
let mut r = Big::from_small(42);
Big::from_u64(n).div_rem(&Big::from_u64(d), &mut q, &mut r);
(q, r)
}
assert_eq!(div_rem(1, 1), (Big::from_small(1), Big::from_small(0)));
assert_eq!(div_rem(4, 3), (Big::from_small(1), Big::from_small(1)));
assert_eq!(div_rem(1, 7), (Big::from_small(0), Big::from_small(1)));
assert_eq!(div_rem(45, 9), (Big::from_small(5), Big::from_small(0)));
assert_eq!(div_rem(103, 9), (Big::from_small(11), Big::from_small(4)));
assert_eq!(div_rem(123456, 77), (Big::from_u64(1603), Big::from_small(25)));
assert_eq!(div_rem(0xffff, 1), (Big::from_u64(0xffff), Big::from_small(0)));
assert_eq!(div_rem(0xeeee, 0xffff), (Big::from_small(0), Big::from_u64(0xeeee)));
assert_eq!(div_rem(2_000_000, 2), (Big::from_u64(1_000_000), Big::from_u64(0)));
}

#[test]
fn test_is_zero() {
assert!(Big::from_small(0).is_zero());
Expand All @@ -141,6 +201,35 @@ fn test_is_zero() {
assert!(Big::from_u64(0xffffff).sub(&Big::from_u64(0xffffff)).is_zero());
}

#[test]
fn test_get_bit() {
let x = Big::from_small(0b1101);
assert_eq!(x.get_bit(0), 1);
assert_eq!(x.get_bit(1), 0);
assert_eq!(x.get_bit(2), 1);
assert_eq!(x.get_bit(3), 1);
let y = Big::from_u64(1 << 15);
assert_eq!(y.get_bit(14), 0);
assert_eq!(y.get_bit(15), 1);
assert_eq!(y.get_bit(16), 0);
}

#[test]
#[should_panic]
fn test_get_bit_out_of_range() {
Big::from_small(42).get_bit(24);
}

#[test]
fn test_bit_length() {
assert_eq!(Big::from_small(0).bit_length(), 0);
assert_eq!(Big::from_small(1).bit_length(), 1);
assert_eq!(Big::from_small(5).bit_length(), 3);
assert_eq!(Big::from_small(0x18).bit_length(), 5);
assert_eq!(Big::from_u64(0x4073).bit_length(), 15);
assert_eq!(Big::from_u64(0xffffff).bit_length(), 24);
}

#[test]
fn test_ord() {
assert!(Big::from_u64(0) < Big::from_u64(0xffffff));
Expand Down

0 comments on commit 7ebd7f3

Please sign in to comment.