Skip to content

Commit

Permalink
Merge pull request #299 from cuviper/const_radix_bases
Browse files Browse the repository at this point in the history
Generate the radix bases in `const` instead of `build.rs`
  • Loading branch information
cuviper committed May 4, 2024
2 parents ba9d00c + 8123302 commit f34e079
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 80 deletions.
57 changes: 0 additions & 57 deletions build.rs
@@ -1,8 +1,4 @@
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::Write;
use std::path::Path;

fn main() {
let ptr_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH");
Expand All @@ -22,57 +18,4 @@ fn main() {
}

println!("cargo:rerun-if-changed=build.rs");

write_radix_bases().unwrap();
}

/// Write tables of the greatest power of each radix for the given bit size. These are returned
/// from `biguint::get_radix_base` to batch the multiplication/division of radix conversions on
/// full `BigUint` values, operating on primitive integers as much as possible.
///
/// e.g. BASES_16[3] = (59049, 10) // 3¹⁰ fits in u16, but 3¹¹ is too big
/// BASES_32[3] = (3486784401, 20)
/// BASES_64[3] = (12157665459056928801, 40)
///
/// Powers of two are not included, just zeroed, as they're implemented with shifts.
fn write_radix_bases() -> Result<(), Box<dyn Error>> {
let out_dir = env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("radix_bases.rs");
let mut f = File::create(dest_path)?;

for &bits in &[16, 32, 64] {
let max = if bits < 64 {
(1 << bits) - 1
} else {
std::u64::MAX
};

writeln!(f, "#[deny(overflowing_literals)]")?;
writeln!(
f,
"pub(crate) static BASES_{bits}: [(u{bits}, usize); 257] = [",
bits = bits
)?;
for radix in 0u64..257 {
let (base, power) = if radix == 0 || radix.is_power_of_two() {
(0, 0)
} else {
let mut power = 1;
let mut base = radix;

while let Some(b) = base.checked_mul(radix) {
if b > max {
break;
}
base = b;
power += 1;
}
(base, power)
};
writeln!(f, " ({}, {}), // {}", base, power, radix)?;
}
writeln!(f, "];")?;
}

Ok(())
}
92 changes: 69 additions & 23 deletions src/biguint/convert.rs
Expand Up @@ -119,7 +119,7 @@ fn from_radix_digits_be(v: &[u8], radix: u32) -> BigUint {

let mut data = Vec::with_capacity(big_digits.to_usize().unwrap_or(0));

let (base, power) = get_radix_base(radix, big_digit::BITS);
let (base, power) = get_radix_base(radix);
let radix = radix as BigDigit;

let r = v.len() % power;
Expand Down Expand Up @@ -688,7 +688,7 @@ pub(super) fn to_radix_digits_le(u: &BigUint, radix: u32) -> Vec<u8> {

let mut digits = u.clone();

let (base, power) = get_radix_base(radix, big_digit::HALF_BITS);
let (base, power) = get_half_radix_base(radix);
let radix = radix as BigDigit;

// For very large numbers, the O(n²) loop of repeated `div_rem_digit` dominates the
Expand Down Expand Up @@ -783,33 +783,79 @@ pub(crate) fn to_str_radix_reversed(u: &BigUint, radix: u32) -> Vec<u8> {
res
}

/// Returns the greatest power of the radix for the given bit size
/// Returns the greatest power of the radix for the `BigDigit` bit size
#[inline]
fn get_radix_base(radix: u32, bits: u8) -> (BigDigit, usize) {
mod gen {
include! { concat!(env!("OUT_DIR"), "/radix_bases.rs") }
}
fn get_radix_base(radix: u32) -> (BigDigit, usize) {
static BASES: [(BigDigit, usize); 257] = generate_radix_bases(big_digit::MAX);
debug_assert!(!radix.is_power_of_two());
debug_assert!((3..256).contains(&radix));
BASES[radix as usize]
}

debug_assert!(
2 <= radix && radix <= 256,
"The radix must be within 2...256"
);
/// Returns the greatest power of the radix for half the `BigDigit` bit size
#[inline]
fn get_half_radix_base(radix: u32) -> (BigDigit, usize) {
static BASES: [(BigDigit, usize); 257] = generate_radix_bases(big_digit::HALF);
debug_assert!(!radix.is_power_of_two());
debug_assert!(bits <= big_digit::BITS);
debug_assert!((3..256).contains(&radix));
BASES[radix as usize]
}

match bits {
16 => {
let (base, power) = gen::BASES_16[radix as usize];
(base as BigDigit, power)
/// Generate tables of the greatest power of each radix that is less that the given maximum. These
/// are returned from `get_radix_base` to batch the multiplication/division of radix conversions on
/// full `BigUint` values, operating on primitive integers as much as possible.
///
/// e.g. BASES_16[3] = (59049, 10) // 3¹⁰ fits in u16, but 3¹¹ is too big
/// BASES_32[3] = (3486784401, 20)
/// BASES_64[3] = (12157665459056928801, 40)
///
/// Powers of two are not included, just zeroed, as they're implemented with shifts.
const fn generate_radix_bases(max: BigDigit) -> [(BigDigit, usize); 257] {
let mut bases = [(0, 0); 257];

let mut radix: BigDigit = 3;
while radix < 256 {
if !radix.is_power_of_two() {
let mut power = 1;
let mut base = radix;

while let Some(b) = base.checked_mul(radix) {
if b > max {
break;
}
base = b;
power += 1;
}
bases[radix as usize] = (base, power)
}
32 => {
let (base, power) = gen::BASES_32[radix as usize];
(base as BigDigit, power)
radix += 1;
}

bases
}

#[test]
fn test_radix_bases() {
for radix in 3u32..256 {
if !radix.is_power_of_two() {
let (base, power) = get_radix_base(radix);
let radix = BigDigit::try_from(radix).unwrap();
let power = u32::try_from(power).unwrap();
assert_eq!(base, radix.pow(power));
assert!(radix.checked_pow(power + 1).is_none());
}
64 => {
let (base, power) = gen::BASES_64[radix as usize];
(base as BigDigit, power)
}
}

#[test]
fn test_half_radix_bases() {
for radix in 3u32..256 {
if !radix.is_power_of_two() {
let (base, power) = get_half_radix_base(radix);
let radix = BigDigit::try_from(radix).unwrap();
let power = u32::try_from(power).unwrap();
assert_eq!(base, radix.pow(power));
assert!(radix.pow(power + 1) > big_digit::HALF);
}
_ => panic!("Invalid bigdigit size"),
}
}

0 comments on commit f34e079

Please sign in to comment.