use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::ops::{Div, DivAssign};
use traiter::numbers::{RemEuclid, Signed, Zeroable};
use super::constants::MAX_REPRESENTABLE_BASE;
use super::digits::BaseFromBinaryDigits;
use super::types::BigInt;
impl<Digit, const DIGIT_BITNESS: usize> Display
for BigInt<Digit, DIGIT_BITNESS>
where
Self: ToBaseString,
{
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.to_base_string(10))
}
}
trait ToBaseString {
const DIGIT_VALUES_ASCII_CODES: [char; MAX_REPRESENTABLE_BASE as usize] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];
fn to_base_string(&self, base: usize) -> String;
}
impl<
Digit: Copy
+ Div<Output = Digit>
+ DivAssign
+ BaseFromBinaryDigits<Digit>
+ RemEuclid<Output = Digit>
+ TryFrom<usize>,
const DIGIT_BITNESS: usize,
> ToBaseString for BigInt<Digit, DIGIT_BITNESS>
where
for<'a> &'a Digit: Zeroable,
usize: TryFrom<Digit>,
{
fn to_base_string(&self, base: usize) -> String {
let shift = if (1usize << DIGIT_BITNESS)
>= (MAX_REPRESENTABLE_BASE as usize)
|| base < (1usize << DIGIT_BITNESS)
{
unsafe { floor_log(1 << DIGIT_BITNESS, base).unwrap_unchecked() }
} else {
1usize
};
let digits = Digit::base_from_binary_digits(
&self.digits,
DIGIT_BITNESS,
power(base, shift),
);
let characters_count = usize::from(self.is_negative())
+ (digits.len() - 1) * shift
+ floor_log(
unsafe {
usize::try_from(digits[digits.len() - 1])
.unwrap_unchecked()
},
base,
)
.unwrap_or(0usize)
+ 1;
let mut characters: String = String::with_capacity(characters_count);
let target_base = unsafe { Digit::try_from(base).unwrap_unchecked() };
for &(mut remainder) in digits.iter().take(digits.len() - 1) {
for _ in 0..shift {
characters.push(
Self::DIGIT_VALUES_ASCII_CODES[unsafe {
usize::try_from(remainder.rem_euclid(target_base))
.unwrap_unchecked()
}],
);
remainder /= target_base;
}
}
let mut remainder = digits[digits.len() - 1];
while !remainder.is_zero() {
characters.push(
Self::DIGIT_VALUES_ASCII_CODES[unsafe {
usize::try_from(remainder.rem_euclid(target_base))
.unwrap_unchecked()
}],
);
remainder /= target_base;
}
if self.is_zero() {
characters.push('0');
} else if self.is_negative() {
characters.push('-');
}
characters.chars().rev().collect()
}
}
const fn floor_log(value: usize, base: usize) -> Option<usize> {
if value == 0usize {
None
} else if value < base {
Some(0)
} else {
match floor_log(value / base, base) {
Some(value) => Some(value + 1),
None => None,
}
}
}
const fn power(base: usize, exponent: usize) -> usize {
match exponent {
0 => 1,
_ => base * power(base, exponent - 1),
}
}