dashu-int 0.6.0

Arbitrary-precision integer math library for Rust, balancing ergonomics and efficiency. Provides UBig and IBig (unsigned and signed), with small values inlined on the stack. Efficient arithmetic, modular arithmetic, and number theory (pow, ilog, gcd, gcd_ext); two's-complement bit operations; parsing and formatting in base 2-36; optional serde, rand, num-traits, rkyv, and zeroize.
Documentation
//! Information about radixes.

use crate::{
    arch::word::Word,
    math::{max_exp_in_dword, max_exp_in_word},
};
use static_assertions::const_assert;

type FastDivideSmall = num_modular::PreMulInv1by1<Word>;
type FastDivideNormalized = num_modular::Normalized2by1Divisor<Word>;

/// Digit and radix type, it's always u8.
pub type Digit = u8;

/// Minimum supported radix.
pub const MIN_RADIX: Digit = 2;

/// Maximum supported radix.
pub const MAX_RADIX: Digit = 36;

/// Is a radix in valid range?
#[inline]
pub const fn is_radix_valid(radix: Digit) -> bool {
    MIN_RADIX <= radix && radix <= MAX_RADIX
}

const_assert!(b'a' > b'0' + 10 && b'A' > b'0' + 10);

/// u8 representation is: how much digits >= 10 should be offset by in ASCII.
#[derive(Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub enum DigitCase {
    NoLetters = 0,
    Lower = b'a' - b'0' - 10,
    Upper = b'A' - b'0' - 10,
}

/// Converts a byte (ASCII) representation of a digit to its value.
///
/// The radix has to be in range 2..36 (inclusive), and the digit will
/// be parsed with insensitive case.
#[inline]
pub const fn digit_from_ascii_byte(byte: u8, radix: Digit) -> Option<Digit> {
    assert!(is_radix_valid(radix));

    let res = match byte {
        c @ b'0'..=b'9' => (c - b'0') as Digit,
        c @ b'a'..=b'z' => (c - b'a') as Digit + 10,
        c @ b'A'..=b'Z' => (c - b'A') as Digit + 10,
        _ => return None,
    };
    if res < radix {
        Some(res)
    } else {
        None
    }
}

/// Properties of a given radix.
#[derive(Clone, Copy)]
pub struct RadixInfo {
    /// The number of digits that can always fit in a `Word`.
    pub(crate) digits_per_word: usize,

    /// Radix to the power of `max_digits`.
    /// Only for non-power-of-2 radixes.
    pub(crate) range_per_word: Word,

    /// Faster division by `radix`.
    pub(crate) fast_div_radix: FastDivideSmall,

    /// Faster division by normalized range_per_word.
    /// Only for non-power-of-2 radixes.
    pub(crate) fast_div_range_per_word: FastDivideNormalized,
}

/// Radix info for base 10
pub const RADIX10_INFO: RadixInfo = RadixInfo::for_radix(10);

/// Maximum number of digits that a `Word` can ever have for any non-power-of-2 radix.
pub const MAX_WORD_DIGITS_NON_POW_2: usize = max_exp_in_word(3).0 + 1;
/// Maximum number of digits that a `DoubleWord` can ever have for any non-power-of-2 radix.
pub const MAX_DWORD_DIGITS_NON_POW_2: usize = max_exp_in_dword(3).0 + 1;

/// Get [RadixInfo] for a given radix.
///
/// This method is not specialized for power of two.
#[inline]
pub fn radix_info(radix: Digit) -> RadixInfo {
    debug_assert!(is_radix_valid(radix));

    match radix {
        10 => RADIX10_INFO,
        _ => RadixInfo::for_radix(radix),
    }
}

impl RadixInfo {
    const fn for_radix(radix: Digit) -> RadixInfo {
        let (digits_per_word, range_per_word) = max_exp_in_word(radix as Word);
        let shift = range_per_word.leading_zeros();
        let fast_div_radix = FastDivideSmall::new(radix as Word);
        let fast_div_range_per_word = FastDivideNormalized::new(range_per_word << shift);
        RadixInfo {
            digits_per_word,
            range_per_word,
            fast_div_radix,
            fast_div_range_per_word,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_digit_from_utf8_byte() {
        assert_eq!(digit_from_ascii_byte(b'7', 10), Some(7));
        assert_eq!(digit_from_ascii_byte(b'a', 16), Some(10));
        assert_eq!(digit_from_ascii_byte(b'z', 36), Some(35));
        assert_eq!(digit_from_ascii_byte(b'Z', 36), Some(35));
        assert_eq!(digit_from_ascii_byte(b'?', 10), None);
        assert_eq!(digit_from_ascii_byte(b'a', 10), None);
        assert_eq!(digit_from_ascii_byte(b'z', 35), None);
        assert_eq!(digit_from_ascii_byte(255, 35), None);
    }
}