pub const fn required_space(base: u128, number: u128, negative: bool) -> Option<usize> {
if base < 2 {
return None;
}
if number == 0 {
return Some(1);
}
Some(number.ilog(base) as usize + 1 + negative as usize)
}
const LOOKUP: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DEC_LOOKUP: &[u8; 200] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
const MAX_SUPPORTED_BASE: u128 = LOOKUP.len() as u128;
macro_rules! copy_2_dec_lut_bytes {
($to:ident,$to_index:expr,$lut_index:expr) => {
$to[$to_index as usize] = DEC_LOOKUP[$lut_index as usize];
$to[$to_index as usize + 1] = DEC_LOOKUP[$lut_index as usize + 1];
};
}
macro_rules! base_10 {
($number:ident, $index:ident, $string:ident) => {
while $number > 9999 {
let rem = ($number % 10000) as u16;
let (frst, scnd) = ((rem / 100) * 2, (rem % 100) * 2);
copy_2_dec_lut_bytes!($string, $index - 3, frst);
copy_2_dec_lut_bytes!($string, $index - 1, scnd);
$index = $index.wrapping_sub(4);
$number /= 10000;
}
if $number > 999 {
let (frst, scnd) = (($number / 100) * 2, ($number % 100) * 2);
copy_2_dec_lut_bytes!($string, $index - 3, frst);
copy_2_dec_lut_bytes!($string, $index - 1, scnd);
$index = $index.wrapping_sub(4);
} else if $number > 99 {
let section = ($number as u16 / 10) * 2;
copy_2_dec_lut_bytes!($string, $index - 2, section);
$string[$index] = LOOKUP[($number % 10) as usize];
$index = $index.wrapping_sub(3);
} else if $number > 9 {
$number *= 2;
copy_2_dec_lut_bytes!($string, $index - 1, $number);
$index = $index.wrapping_sub(2);
} else {
$string[$index] = LOOKUP[$number as usize];
$index = $index.wrapping_sub(1);
}
};
}
macro_rules! impl_unsigned_numtoa_for {
(
$type_name:ty,
$core_function_name:ident,
$str_function_name:ident
) => {
pub const fn $core_function_name(
mut num: $type_name,
base: $type_name,
string: &mut [u8],
) -> &[u8] {
if cfg!(debug_assertions) {
debug_assert!(
base > 1 && base as u128 <= MAX_SUPPORTED_BASE,
"unsupported base"
);
debug_assert!(
string.len() >= required_space(base as u128, <$type_name>::MAX as u128, false).expect("valid base")
);
}
let mut index = string.len() - 1;
if num == 0 {
string[index] = b'0';
return string.split_at(index).1;
}
if base == 10 {
base_10!(num, index, string);
} else {
while num != 0 {
let rem = num % base;
string[index] = LOOKUP[rem as usize];
index = index.wrapping_sub(1);
num /= base;
}
}
string.split_at(index.wrapping_add(1)).1
}
pub const fn $str_function_name(
num: $type_name,
base: $type_name,
string: &mut [u8],
) -> &str {
unsafe { core::str::from_utf8_unchecked($core_function_name(num, base, string)) }
}
};
}
macro_rules! impl_signed_numtoa_for {
(
$type_name:ty,
$core_function_name:ident,
$str_function_name:ident
) => {
pub const fn $core_function_name(
mut num: $type_name,
base: $type_name,
string: &mut [u8],
) -> &[u8] {
if cfg!(debug_assertions) {
debug_assert!(
base > 1 && base as u128 <= MAX_SUPPORTED_BASE,
"unsupported base"
);
debug_assert!(
string.len()
>= required_space(
base as u128,
<$type_name>::MIN.unsigned_abs() as u128,
true
).expect("valid base")
);
}
let mut index = string.len() - 1;
let mut is_negative = false;
if num < 0 {
is_negative = true;
num = match num.checked_abs() {
Some(value) => value,
None => {
let value = <$type_name>::max_value();
string[index] = LOOKUP[((value % base + 1) % base) as usize];
index -= 1;
value / base + ((value % base == base - 1) as $type_name)
}
};
} else if num == 0 {
string[index] = b'0';
return string.split_at(index).1;
}
if base == 10 {
base_10!(num, index, string);
} else {
while num != 0 {
let rem = num % base;
string[index] = LOOKUP[rem as usize];
index = index.wrapping_sub(1);
num /= base;
}
}
if is_negative {
string[index] = b'-';
index = index.wrapping_sub(1);
}
string.split_at(index.wrapping_add(1)).1
}
pub const fn $str_function_name(
num: $type_name,
base: $type_name,
string: &mut [u8],
) -> &str {
unsafe { core::str::from_utf8_unchecked($core_function_name(num, base, string)) }
}
};
}
impl_signed_numtoa_for!(i16, numtoa_i16, numtoa_i16_str);
impl_signed_numtoa_for!(i32, numtoa_i32, numtoa_i32_str);
impl_signed_numtoa_for!(i64, numtoa_i64, numtoa_i64_str);
impl_signed_numtoa_for!(i128, numtoa_i128, numtoa_i128_str);
impl_signed_numtoa_for!(isize, numtoa_isize, numtoa_isize_str);
impl_unsigned_numtoa_for!(u16, numtoa_u16, numtoa_u16_str);
impl_unsigned_numtoa_for!(u32, numtoa_u32, numtoa_u32_str);
impl_unsigned_numtoa_for!(u64, numtoa_u64, numtoa_u64_str);
impl_unsigned_numtoa_for!(u128, numtoa_u128, numtoa_u128_str);
impl_unsigned_numtoa_for!(usize, numtoa_usize, numtoa_usize_str);
pub const fn numtoa_i8(mut num: i8, base: i8, string: &mut [u8]) -> &[u8] {
if cfg!(debug_assertions) {
debug_assert!(
base > 1 && base as u128 <= MAX_SUPPORTED_BASE,
"unsupported base"
);
debug_assert!(
string.len() >= required_space(base as u128, i8::MIN.unsigned_abs() as u128, true).expect("valid base")
);
}
let mut index = string.len() - 1;
let mut is_negative = false;
if num < 0 {
is_negative = true;
num = match num.checked_abs() {
Some(value) => value,
None => {
let value = <i8>::max_value();
string[index] = LOOKUP[((value % base + 1) % base) as usize];
index -= 1;
value / base + ((value % base == base - 1) as i8)
}
};
} else if num == 0 {
string[index] = b'0';
return string.split_at(index).1;
}
if base == 10 {
if num > 99 {
let section = (num / 10) * 2;
copy_2_dec_lut_bytes!(string, index - 2, section);
string[index] = LOOKUP[(num % 10) as usize];
index = index.wrapping_sub(3);
} else if num > 9 {
let idx = num as usize * 2;
copy_2_dec_lut_bytes!(string, index - 1, idx);
index = index.wrapping_sub(2);
} else {
string[index] = LOOKUP[num as usize];
index = index.wrapping_sub(1);
}
} else {
while num != 0 {
let rem = num % base;
string[index] = LOOKUP[rem as usize];
index = index.wrapping_sub(1);
num /= base;
}
}
if is_negative {
string[index] = b'-';
index = index.wrapping_sub(1);
}
string.split_at(index.wrapping_add(1)).1
}
pub const fn numtoa_i8_str(num: i8, base: i8, string: &mut [u8]) -> &str {
unsafe { str::from_utf8_unchecked(numtoa_i8(num, base, string)) }
}
pub const fn numtoa_u8(mut num: u8, base: u8, string: &mut [u8]) -> &[u8] {
if cfg!(debug_assertions) {
debug_assert!(
base > 1 && base as u128 <= MAX_SUPPORTED_BASE,
"unsupported base"
);
debug_assert!(string.len() >= required_space(base as u128, u8::MAX as u128, false).expect("valid base"));
}
let mut index = string.len() - 1;
if num == 0 {
string[index] = b'0';
return string.split_at(index).1;
}
if base == 10 {
if num > 99 {
let section = (num / 10) * 2;
copy_2_dec_lut_bytes!(string, index - 2, section);
string[index] = LOOKUP[(num % 10) as usize];
index = index.wrapping_sub(3);
} else if num > 9 {
num *= 2;
copy_2_dec_lut_bytes!(string, index - 1, num);
index = index.wrapping_sub(2);
} else {
string[index] = LOOKUP[num as usize];
index = index.wrapping_sub(1);
}
} else {
while num != 0 {
let rem = num % base;
string[index] = LOOKUP[rem as usize];
index = index.wrapping_sub(1);
num /= base;
}
}
string.split_at(index.wrapping_add(1)).1
}
pub const fn numtoa_u8_str(num: u8, base: u8, string: &mut [u8]) -> &str {
unsafe { str::from_utf8_unchecked(numtoa_u8(num, base, string)) }
}
#[cfg(test)]
mod core_test {
use super::*;
fn verify_required_sizes_for_base(
base: u128,
expected_space_u8: usize,
expected_space_u16: usize,
expected_space_u32: usize,
expected_space_u64: usize,
expected_space_u128: usize,
expected_space_i8: usize,
expected_space_i16: usize,
expected_space_i32: usize,
expected_space_i64: usize,
expected_space_i128: usize,
) {
assert_eq!(
expected_space_u8,
required_space(base, u8::MAX as u128, false).expect("valid base")
);
assert_eq!(
expected_space_u16,
required_space(base, u16::MAX as u128, false).expect("valid base")
);
assert_eq!(
expected_space_u32,
required_space(base, u32::MAX as u128, false).expect("valid base")
);
assert_eq!(
expected_space_u64,
required_space(base, u64::MAX as u128, false).expect("valid base")
);
assert_eq!(
expected_space_u128,
required_space(base, u128::MAX as u128, false).expect("valid base")
);
assert_eq!(
expected_space_i8,
required_space(base, i8::MIN.unsigned_abs() as u128, true).expect("valid base")
);
assert_eq!(
expected_space_i16,
required_space(base, i16::MIN.unsigned_abs() as u128, true).expect("valid base")
);
assert_eq!(
expected_space_i32,
required_space(base, i32::MIN.unsigned_abs() as u128, true).expect("valid base")
);
assert_eq!(
expected_space_i64,
required_space(base, i64::MIN.unsigned_abs() as u128, true).expect("valid base")
);
assert_eq!(
expected_space_i128,
required_space(base, i128::MIN.unsigned_abs() as u128, true).expect("valid base")
);
}
#[test]
fn sanity_check_required_size() {
assert_eq!(Some(1), required_space(2, 0, false));
assert_eq!(Some(1), required_space(2, 0, true));
assert_eq!(None, required_space(1, 0, false));
assert_eq!(None, required_space(1, 0, true));
assert_eq!(None, required_space(0, 0, false));
assert_eq!(None, required_space(0, 0, true));
assert_eq!(Some(1), required_space(2, 1, false));
assert_eq!(Some(2), required_space(2, 1, true));
assert_eq!(None, required_space(0, 1, false));
assert_eq!(None, required_space(0, 1, true));
assert_eq!(None, required_space(1, 1, false));
assert_eq!(None, required_space(1, 1, true));
verify_required_sizes_for_base(2, 8, 16, 32, 64, 128, 9, 17, 33, 65, 129);
verify_required_sizes_for_base(
8, 3, 6, 11, 22, 43, 4, 7, 12, 23, 44, );
verify_required_sizes_for_base(
10, 3, 5, 10, 20, 39, 4, 6, 11, 20, 40, );
verify_required_sizes_for_base(16, 2, 4, 8, 16, 32, 3, 5, 9, 17, 33);
}
#[test]
fn sanity() {
assert_eq!(b"256123", numtoa_i32(256123_i32, 10, &mut [0u8; 20]));
}
#[test]
#[should_panic]
fn base_too_low() {
numtoa_i32(50, 1, &mut [0u8; 100]);
}
#[test]
#[should_panic]
fn base_too_high() {
numtoa_i32(36, 37, &mut [0u8; 100]);
}
#[test]
fn str_convenience_core() {
assert_eq!("256123", numtoa_i32_str(256123_i32, 10, &mut [0u8; 20]));
}
#[test]
fn str_convenience_core_u8() {
assert_eq!("42", numtoa_u8_str(42u8, 10, &mut [b'X'; 20]));
}
#[test]
fn str_convenience_core_i8() {
assert_eq!("42", numtoa_i8_str(42i8, 10, &mut [b'X'; 20]));
}
#[test]
#[should_panic]
#[cfg(debug_assertions)]
fn base10_u8_array_too_small_core() {
let _ = numtoa_u8(0_u8, 10, &mut [0u8; 2]);
}
#[test]
fn base10_u8_array_just_right_core() {
let _ = numtoa_u8(0, 10, &mut [0u8; 3]);
}
#[test]
fn base16_i8_all_core() {
for i in i8::MIN..i8::MAX {
let _ = numtoa_i8(i, 16, &mut [0u8; 3]);
}
}
#[test]
fn base10_u8_all_core() {
for i in u8::MIN..u8::MAX {
let _ = numtoa_u8(i, 10, &mut [0u8; 3]);
}
}
#[test]
fn base16_u8_all_core() {
for i in u8::MIN..u8::MAX {
let _ = numtoa_u8(i, 16, &mut [0u8; 3]);
}
}
}