use super::int::BigInt;
use super::uint::BigUint;
use crate::OxiNumError;
use crate::OxiNumResult;
use std::fmt;
impl BigUint {
pub fn to_radix(&self, radix: u32) -> OxiNumResult<String> {
if !(2..=36).contains(&radix) {
return Err(OxiNumError::InvalidRadix(radix));
}
if self.is_zero() {
return Ok("0".to_string());
}
if radix.is_power_of_two() {
return Ok(self.to_radix_pow2(radix));
}
Ok(self.to_radix_general(radix))
}
pub fn from_str_radix(src: &str, radix: u32) -> OxiNumResult<BigUint> {
if !(2..=36).contains(&radix) {
return Err(OxiNumError::InvalidRadix(radix));
}
if src.is_empty() {
return Err(OxiNumError::Parse("empty string".into()));
}
let (chunk_size, chunk_base) = chunk_for_radix(radix);
let bytes = src.as_bytes();
let total = bytes.len();
let chunk_base_big = BigUint::from_u64(chunk_base);
let mut idx = 0usize;
let first_chunk_len = if total % chunk_size == 0 {
chunk_size
} else {
total % chunk_size
};
let first_str = std::str::from_utf8(&bytes[idx..idx + first_chunk_len])
.map_err(|_| OxiNumError::Parse("invalid UTF-8".into()))?;
let first_val: u64 = u64::from_str_radix(first_str, radix)
.map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
let mut acc = BigUint::from_u64(first_val);
idx += first_chunk_len;
while idx < total {
let chunk_str = std::str::from_utf8(&bytes[idx..idx + chunk_size])
.map_err(|_| OxiNumError::Parse("invalid UTF-8".into()))?;
let val: u64 = u64::from_str_radix(chunk_str, radix)
.map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
acc = &acc * &chunk_base_big;
acc = &acc + &BigUint::from_u64(val);
idx += chunk_size;
}
Ok(acc)
}
fn to_radix_pow2(&self, radix: u32) -> String {
let bits_per_digit = radix.trailing_zeros() as u64;
let total_bits = self.bit_length();
let n_digits = total_bits.div_ceil(bits_per_digit) as usize;
let mut digits: Vec<u8> = Vec::with_capacity(n_digits);
let mask: u64 = (1u64 << bits_per_digit) - 1;
for i in 0..n_digits {
let bit_pos = (i as u64) * bits_per_digit;
let limb_idx = (bit_pos / 64) as usize;
let bit_in_limb = bit_pos % 64;
let mut val: u64 = (self.limbs[limb_idx] >> bit_in_limb) & mask;
let bits_in_first = (64u64).saturating_sub(bit_in_limb);
if bits_in_first < bits_per_digit && limb_idx + 1 < self.limbs.len() {
let needed = bits_per_digit - bits_in_first;
let upper_mask = (1u64 << needed) - 1;
let extra = self.limbs[limb_idx + 1] & upper_mask;
val |= extra << bits_in_first;
}
digits.push(digit_to_char(val as u32));
}
digits.reverse();
while digits.first() == Some(&b'0') && digits.len() > 1 {
digits.remove(0);
}
String::from_utf8(digits).unwrap_or_else(|_| String::from("<radix-output-utf8-bug>"))
}
fn to_radix_general(&self, radix: u32) -> String {
let (chunk_size, chunk_base) = chunk_for_radix(radix);
let chunk_base_big = BigUint::from_u64(chunk_base);
let mut acc = self.clone();
let mut chunks: Vec<u64> = Vec::new();
while !acc.is_zero() {
let (q, r) = super::div::divrem(&acc, &chunk_base_big);
chunks.push(r.to_u64().unwrap_or(0));
acc = q;
}
let mut out = String::with_capacity(chunks.len() * chunk_size);
if let Some(&top) = chunks.last() {
out.push_str(&format_in_radix(top, radix, 0));
}
for &c in chunks.iter().rev().skip(1) {
out.push_str(&format_in_radix(c, radix, chunk_size));
}
out
}
}
fn chunk_for_radix(radix: u32) -> (usize, u64) {
let r = radix as u64;
let mut k: usize = 1;
let mut acc: u64 = r;
loop {
match acc.checked_mul(r) {
Some(v) => {
acc = v;
k += 1;
}
None => return (k, acc),
}
}
}
fn format_in_radix(value: u64, radix: u32, min_width: usize) -> String {
if value == 0 {
return "0".repeat(min_width.max(1));
}
let mut digits: Vec<u8> = Vec::new();
let mut v = value;
while v != 0 {
digits.push(digit_to_char((v % radix as u64) as u32));
v /= radix as u64;
}
while digits.len() < min_width {
digits.push(b'0');
}
digits.reverse();
String::from_utf8(digits).unwrap_or_else(|_| "0".to_string())
}
fn digit_to_char(d: u32) -> u8 {
debug_assert!(d < 36);
match d {
0..=9 => b'0' + d as u8,
10..=35 => b'a' + (d - 10) as u8,
_ => b'?',
}
}
impl fmt::Display for BigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.to_radix(10) {
Ok(s) => f.write_str(&s),
Err(_) => f.write_str("<BigUint-radix-error>"),
}
}
}
impl fmt::Debug for BigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BigUint({})", self)?;
if !self.limbs.is_empty() {
write!(f, " [{}lm]", self.limbs.len())?;
}
Ok(())
}
}
impl fmt::LowerHex for BigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.to_radix(16).map_err(|_| fmt::Error)?;
f.pad_integral(true, "0x", &digits)
}
}
impl fmt::UpperHex for BigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.to_radix(16).map_err(|_| fmt::Error)?;
f.pad_integral(true, "0x", &digits.to_ascii_uppercase())
}
}
impl fmt::Octal for BigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.to_radix(8).map_err(|_| fmt::Error)?;
f.pad_integral(true, "0o", &digits)
}
}
impl fmt::Binary for BigUint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.to_radix(2).map_err(|_| fmt::Error)?;
f.pad_integral(true, "0b", &digits)
}
}
impl fmt::LowerHex for BigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.magnitude().to_radix(16).map_err(|_| fmt::Error)?;
f.pad_integral(!self.is_negative(), "0x", &digits)
}
}
impl fmt::UpperHex for BigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.magnitude().to_radix(16).map_err(|_| fmt::Error)?;
f.pad_integral(!self.is_negative(), "0x", &digits.to_ascii_uppercase())
}
}
impl fmt::Octal for BigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.magnitude().to_radix(8).map_err(|_| fmt::Error)?;
f.pad_integral(!self.is_negative(), "0o", &digits)
}
}
impl fmt::Binary for BigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let digits = self.magnitude().to_radix(2).map_err(|_| fmt::Error)?;
f.pad_integral(!self.is_negative(), "0b", &digits)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_radix_zero() {
assert_eq!(BigUint::zero().to_radix(10).expect("radix"), "0");
assert_eq!(BigUint::zero().to_radix(2).expect("radix"), "0");
assert_eq!(BigUint::zero().to_radix(16).expect("radix"), "0");
}
#[test]
fn to_radix_decimal_small() {
let n = BigUint::from_u64(12345);
assert_eq!(n.to_radix(10).expect("radix"), "12345");
}
#[test]
fn to_radix_hex() {
let n = BigUint::from_u64(0xDEAD_BEEF);
assert_eq!(n.to_radix(16).expect("radix"), "deadbeef");
}
#[test]
fn to_radix_binary() {
let n = BigUint::from_u64(0b1010_1100);
assert_eq!(n.to_radix(2).expect("radix"), "10101100");
}
#[test]
fn radix_invalid() {
assert!(BigUint::from_u64(10).to_radix(1).is_err());
assert!(BigUint::from_u64(10).to_radix(37).is_err());
assert!(BigUint::from_str_radix("123", 1).is_err());
}
#[test]
fn radix_roundtrip_many() {
let n = BigUint::from_le_limbs(&[0xDEAD_BEEF_CAFE_BABE, 0x1234_5678_9ABC_DEF0, 0x42]);
for r in [2, 8, 10, 16, 36, 7, 13] {
let s = n.to_radix(r).expect("to_radix");
let m = BigUint::from_str_radix(&s, r).expect("from_radix");
assert_eq!(m, n, "roundtrip failed at radix {r}");
}
}
#[test]
fn display_matches_decimal() {
let n = BigUint::from_u64(12_345_678_901_234_567_890u64);
assert_eq!(format!("{n}"), "12345678901234567890");
}
}