Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate the radix bases in const instead of build.rs #299

Merged
merged 1 commit into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 0 additions & 57 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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"),
}
}