bigint: greatly improve to_str_radix performance
Before: test fac_to_string ... bench: 18,183 ns/iter (+/- 310) test fib_to_string ... bench: 819 ns/iter (+/- 8) test to_str_radix_02 ... bench: 204,479 ns/iter (+/- 2,826) test to_str_radix_08 ... bench: 68,275 ns/iter (+/- 769) test to_str_radix_10 ... bench: 61,809 ns/iter (+/- 907) test to_str_radix_16 ... bench: 51,438 ns/iter (+/- 539) test to_str_radix_36 ... bench: 39,939 ns/iter (+/- 976) After: test fac_to_string ... bench: 1,204 ns/iter (+/- 16) test fib_to_string ... bench: 269 ns/iter (+/- 3) test to_str_radix_02 ... bench: 2,428 ns/iter (+/- 80) test to_str_radix_08 ... bench: 820 ns/iter (+/- 38) test to_str_radix_10 ... bench: 2,984 ns/iter (+/- 303) test to_str_radix_16 ... bench: 689 ns/iter (+/- 25) test to_str_radix_36 ... bench: 7,995 ns/iter (+/- 100)
This commit is contained in:
parent
84542e6e03
commit
49529895a2
230
src/bigint.rs
230
src/bigint.rs
|
@ -66,7 +66,7 @@ use std::iter::repeat;
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub};
|
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub};
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
use std::{cmp, fmt, hash, mem};
|
use std::{cmp, fmt, hash};
|
||||||
use std::cmp::Ordering::{self, Less, Greater, Equal};
|
use std::cmp::Ordering::{self, Less, Greater, Equal};
|
||||||
use std::{i64, u64};
|
use std::{i64, u64};
|
||||||
|
|
||||||
|
@ -1194,33 +1194,121 @@ impl_to_biguint!(u16, FromPrimitive::from_u16);
|
||||||
impl_to_biguint!(u32, FromPrimitive::from_u32);
|
impl_to_biguint!(u32, FromPrimitive::from_u32);
|
||||||
impl_to_biguint!(u64, FromPrimitive::from_u64);
|
impl_to_biguint!(u64, FromPrimitive::from_u64);
|
||||||
|
|
||||||
fn to_str_radix_reversed(u: &BigUint, radix: u32) -> Vec<u8> {
|
// Extract bitwise digits that evenly divide BigDigit
|
||||||
if radix < 2 || radix > 36 {
|
fn to_bitwise_digits_le(u: &BigUint, bits: usize) -> Vec<u8> {
|
||||||
panic!("invalid radix: {}", radix);
|
debug_assert!(!u.is_zero() && bits <= 8 && big_digit::BITS % bits == 0);
|
||||||
}
|
|
||||||
|
|
||||||
if u.is_zero() {
|
let last_i = u.data.len() - 1;
|
||||||
vec![b'0']
|
let mask: BigDigit = (1 << bits) - 1;
|
||||||
} else {
|
let digits_per_big_digit = big_digit::BITS / bits;
|
||||||
let mut res = Vec::new();
|
let digits = (u.bits() + bits - 1) / bits;
|
||||||
let mut digits = u.clone();
|
let mut res = Vec::with_capacity(digits);
|
||||||
|
|
||||||
while digits != Zero::zero() {
|
for mut r in u.data[..last_i].iter().cloned() {
|
||||||
let (q, r) = div_rem_digit(digits, radix as BigDigit);
|
for _ in 0..digits_per_big_digit {
|
||||||
res.push(to_digit(r as u8));
|
res.push((r & mask) as u8);
|
||||||
digits = q;
|
r >>= bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut r = u.data[last_i];
|
||||||
|
while r != 0 {
|
||||||
|
res.push((r & mask) as u8);
|
||||||
|
r >>= bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_digit(b: u8) -> u8 {
|
// Extract bitwise digits that don't evenly divide BigDigit
|
||||||
match b {
|
fn to_inexact_bitwise_digits_le(u: &BigUint, bits: usize) -> Vec<u8> {
|
||||||
0 ... 9 => b'0' + b,
|
debug_assert!(!u.is_zero() && bits <= 8 && big_digit::BITS % bits != 0);
|
||||||
10 ... 35 => b'a' - 10 + b,
|
|
||||||
_ => panic!("invalid digit: {}", b)
|
let last_i = u.data.len() - 1;
|
||||||
|
let mask: DoubleBigDigit = (1 << bits) - 1;
|
||||||
|
let digits = (u.bits() + bits - 1) / bits;
|
||||||
|
let mut res = Vec::with_capacity(digits);
|
||||||
|
|
||||||
|
let mut r = 0;
|
||||||
|
let mut rbits = 0;
|
||||||
|
for hi in u.data[..last_i].iter().cloned() {
|
||||||
|
r |= (hi as DoubleBigDigit) << rbits;
|
||||||
|
rbits += big_digit::BITS;
|
||||||
|
|
||||||
|
while rbits >= bits {
|
||||||
|
res.push((r & mask) as u8);
|
||||||
|
r >>= bits;
|
||||||
|
rbits -= bits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r |= (u.data[last_i] as DoubleBigDigit) << rbits;
|
||||||
|
while r != 0 {
|
||||||
|
res.push((r & mask) as u8);
|
||||||
|
r >>= bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract little-endian radix digits
|
||||||
|
#[inline(always)] // forced inline to get const-prop for radix=10
|
||||||
|
fn to_radix_digits_le(u: &BigUint, radix: u32) -> Vec<u8> {
|
||||||
|
debug_assert!(!u.is_zero() && !radix.is_power_of_two());
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let mut digits = u.clone();
|
||||||
|
let (base, power) = get_radix_base(radix);
|
||||||
|
debug_assert!(base < (1 << 32));
|
||||||
|
let base = base as BigDigit;
|
||||||
|
|
||||||
|
while digits.data.len() > 1 {
|
||||||
|
let (q, mut r) = div_rem_digit(digits, base);
|
||||||
|
for _ in 0..power {
|
||||||
|
res.push((r % radix) as u8);
|
||||||
|
r /= radix;
|
||||||
|
}
|
||||||
|
digits = q;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut r = digits.data[0];
|
||||||
|
while r != 0 {
|
||||||
|
res.push((r % radix) as u8);
|
||||||
|
r /= radix;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_str_radix_reversed(u: &BigUint, radix: u32) -> Vec<u8> {
|
||||||
|
assert!(2 <= radix && radix <= 36, "The radix must be within 2...36");
|
||||||
|
|
||||||
|
if u.is_zero() {
|
||||||
|
return vec![b'0']
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = if radix.is_power_of_two() {
|
||||||
|
// Powers of two can use bitwise masks and shifting instead of division
|
||||||
|
let bits = radix.trailing_zeros() as usize;
|
||||||
|
if big_digit::BITS % bits == 0 {
|
||||||
|
to_bitwise_digits_le(u, bits)
|
||||||
|
} else {
|
||||||
|
to_inexact_bitwise_digits_le(u, bits)
|
||||||
|
}
|
||||||
|
} else if radix == 10 {
|
||||||
|
// 10 is so common that it's worth separating out for const-propagation.
|
||||||
|
// Optimizers can often turn constant division into a faster multiplication.
|
||||||
|
to_radix_digits_le(u, 10)
|
||||||
|
} else {
|
||||||
|
to_radix_digits_le(u, radix)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now convert everything to ASCII digits.
|
||||||
|
for r in &mut res {
|
||||||
|
const DIGITS: &'static [u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
|
*r = DIGITS[*r as usize];
|
||||||
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BigUint {
|
impl BigUint {
|
||||||
|
@ -1289,24 +1377,10 @@ impl BigUint {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_bytes_le(&self) -> Vec<u8> {
|
pub fn to_bytes_le(&self) -> Vec<u8> {
|
||||||
let mut result = Vec::new();
|
if self.is_zero() {
|
||||||
for word in self.data.iter() {
|
|
||||||
let mut w = *word;
|
|
||||||
for _ in 0..mem::size_of::<BigDigit>() {
|
|
||||||
let b = (w & 0xFF) as u8;
|
|
||||||
w = w >> 8;
|
|
||||||
result.push(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(&0) = result.last() {
|
|
||||||
result.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.is_empty() {
|
|
||||||
vec![0]
|
vec![0]
|
||||||
} else {
|
} else {
|
||||||
result
|
to_bitwise_digits_le(self, 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1431,26 +1505,57 @@ impl BigUint {
|
||||||
}
|
}
|
||||||
|
|
||||||
// `DoubleBigDigit` size dependent
|
// `DoubleBigDigit` size dependent
|
||||||
|
/// Returns the greatest power of the radix <= BigDigit::MAX + 1
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_radix_base(radix: u32) -> (DoubleBigDigit, usize) {
|
fn get_radix_base(radix: u32) -> (DoubleBigDigit, usize) {
|
||||||
match radix {
|
// To generate this table:
|
||||||
2 => (4294967296, 32),
|
// let target = std::u32::max as u64 + 1;
|
||||||
3 => (3486784401, 20),
|
// for radix in 2u64..37 {
|
||||||
4 => (4294967296, 16),
|
// let power = (target as f64).log(radix as f64) as u32;
|
||||||
5 => (1220703125, 13),
|
// let base = radix.pow(power);
|
||||||
6 => (2176782336, 12),
|
// println!("({:10}, {:2}), // {:2}", base, power, radix);
|
||||||
7 => (1977326743, 11),
|
// }
|
||||||
8 => (1073741824, 10),
|
const BASES: [(DoubleBigDigit, usize); 37] = [
|
||||||
9 => (3486784401, 10),
|
(0, 0), (0, 0),
|
||||||
10 => (1000000000, 9),
|
(4294967296, 32), // 2
|
||||||
11 => (2357947691, 9),
|
(3486784401, 20), // 3
|
||||||
12 => (429981696, 8),
|
(4294967296, 16), // 4
|
||||||
13 => (815730721, 8),
|
(1220703125, 13), // 5
|
||||||
14 => (1475789056, 8),
|
(2176782336, 12), // 6
|
||||||
15 => (2562890625, 8),
|
(1977326743, 11), // 7
|
||||||
16 => (4294967296, 8),
|
(1073741824, 10), // 8
|
||||||
_ => panic!("The radix must be within (1, 16]")
|
(3486784401, 10), // 9
|
||||||
}
|
(1000000000, 9), // 10
|
||||||
|
(2357947691, 9), // 11
|
||||||
|
( 429981696, 8), // 12
|
||||||
|
( 815730721, 8), // 13
|
||||||
|
(1475789056, 8), // 14
|
||||||
|
(2562890625, 8), // 15
|
||||||
|
(4294967296, 8), // 16
|
||||||
|
( 410338673, 7), // 17
|
||||||
|
( 612220032, 7), // 18
|
||||||
|
( 893871739, 7), // 19
|
||||||
|
(1280000000, 7), // 20
|
||||||
|
(1801088541, 7), // 21
|
||||||
|
(2494357888, 7), // 22
|
||||||
|
(3404825447, 7), // 23
|
||||||
|
( 191102976, 6), // 24
|
||||||
|
( 244140625, 6), // 25
|
||||||
|
( 308915776, 6), // 26
|
||||||
|
( 387420489, 6), // 27
|
||||||
|
( 481890304, 6), // 28
|
||||||
|
( 594823321, 6), // 29
|
||||||
|
( 729000000, 6), // 30
|
||||||
|
( 887503681, 6), // 31
|
||||||
|
(1073741824, 6), // 32
|
||||||
|
(1291467969, 6), // 33
|
||||||
|
(1544804416, 6), // 34
|
||||||
|
(1838265625, 6), // 35
|
||||||
|
(2176782336, 6), // 36
|
||||||
|
];
|
||||||
|
|
||||||
|
assert!(2 <= radix && radix <= 36, "The radix must be within 2...36");
|
||||||
|
BASES[radix as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Sign is a `BigInt`'s composing element.
|
/// A Sign is a `BigInt`'s composing element.
|
||||||
|
@ -3242,6 +3347,11 @@ mod biguint_tests {
|
||||||
format!("3{}2{}1",
|
format!("3{}2{}1",
|
||||||
repeat("0").take(bits / 2 - 1).collect::<String>(),
|
repeat("0").take(bits / 2 - 1).collect::<String>(),
|
||||||
repeat("0").take(bits / 2 - 1).collect::<String>())),
|
repeat("0").take(bits / 2 - 1).collect::<String>())),
|
||||||
|
(8, match bits {
|
||||||
|
32 => "6000000000100000000001".to_string(),
|
||||||
|
16 => "140000400001".to_string(),
|
||||||
|
_ => panic!()
|
||||||
|
}),
|
||||||
(10, match bits {
|
(10, match bits {
|
||||||
32 => "55340232229718589441".to_string(),
|
32 => "55340232229718589441".to_string(),
|
||||||
16 => "12885032961".to_string(),
|
16 => "12885032961".to_string(),
|
||||||
|
@ -3286,6 +3396,16 @@ mod biguint_tests {
|
||||||
assert_eq!(minus_one, None);
|
assert_eq!(minus_one, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_str_radix() {
|
||||||
|
let n = BigUint::new((0..10).collect());
|
||||||
|
for radix in 2..37 {
|
||||||
|
let s = n.to_str_radix(radix);
|
||||||
|
let x = BigUint::from_str_radix(&s, radix);
|
||||||
|
assert_eq!(x.unwrap(), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_factor() {
|
fn test_factor() {
|
||||||
fn factor(n: usize) -> BigUint {
|
fn factor(n: usize) -> BigUint {
|
||||||
|
|
Loading…
Reference in New Issue