#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
use core::{convert::TryInto, fmt, num::NonZeroU128};
const BASE: u64 = 62;
const BASE_TO_2: u64 = BASE * BASE;
const BASE_TO_3: u64 = BASE_TO_2 * BASE;
const BASE_TO_4: u64 = BASE_TO_3 * BASE;
const BASE_TO_5: u64 = BASE_TO_4 * BASE;
const BASE_TO_6: u64 = BASE_TO_5 * BASE;
const BASE_TO_7: u64 = BASE_TO_6 * BASE;
const BASE_TO_8: u64 = BASE_TO_7 * BASE;
const BASE_TO_9: u64 = BASE_TO_8 * BASE;
const BASE_TO_10: u128 = (BASE_TO_9 * BASE) as u128;
const BASE_TO_11: u128 = BASE_TO_10 * BASE as u128;
const BASE_TO_12: u128 = BASE_TO_11 * BASE as u128;
const BASE_TO_13: u128 = BASE_TO_12 * BASE as u128;
const BASE_TO_14: u128 = BASE_TO_13 * BASE as u128;
const BASE_TO_15: u128 = BASE_TO_14 * BASE as u128;
const BASE_TO_16: u128 = BASE_TO_15 * BASE as u128;
const BASE_TO_17: u128 = BASE_TO_16 * BASE as u128;
const BASE_TO_18: u128 = BASE_TO_17 * BASE as u128;
const BASE_TO_19: u128 = BASE_TO_18 * BASE as u128;
const BASE_TO_20: u128 = BASE_TO_19 * BASE as u128;
const BASE_TO_21: u128 = BASE_TO_20 * BASE as u128;
const DIV_BASE_TO_10_MULTIPLY: u128 = 233718071534448225491982379416108680074;
const DIV_BASE_TO_10_SHIFT: u8 = 59;
const STANDARD_ALPHABET: &[u8; 62] =
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const ALTERNATIVE_ALPHABET: &[u8; 62] =
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const fn encode_pairs(alphabet: &[u8; 62]) -> [[u8; 2]; BASE_TO_2 as usize] {
let mut pairs = [[0; 2]; BASE_TO_2 as usize];
let mut i = 0;
while i < pairs.len() {
pairs[i] = [alphabet[i / 62], alphabet[i % 62]];
i += 1;
}
pairs
}
const fn decode_table(alphabet: &[u8; 62]) -> [u8; 256] {
let mut table = [255; 256];
let mut i = 0;
while i < alphabet.len() {
table[alphabet[i] as usize] = i as u8;
i += 1;
}
table
}
static STANDARD_ENCODE_PAIRS: [[u8; 2]; BASE_TO_2 as usize] = encode_pairs(STANDARD_ALPHABET);
static ALTERNATIVE_ENCODE_PAIRS: [[u8; 2]; BASE_TO_2 as usize] = encode_pairs(ALTERNATIVE_ALPHABET);
static STANDARD_DECODE: [u8; 256] = decode_table(STANDARD_ALPHABET);
static ALTERNATIVE_DECODE: [u8; 256] = decode_table(ALTERNATIVE_ALPHABET);
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DecodeError {
ArithmeticOverflow,
EmptyInput,
InvalidBase62Byte(u8, usize),
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum EncodeError {
BufferTooSmall,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
DecodeError::ArithmeticOverflow => {
f.write_str("Decoded number cannot fit into a `u128`")
}
DecodeError::EmptyInput => f.write_str("Encoded input is an empty string"),
DecodeError::InvalidBase62Byte(ch, idx) => {
use fmt::Write;
f.write_str("Encoded input contains the invalid byte b'")?;
for char_in_escape in core::ascii::escape_default(ch) {
f.write_char(char::from(char_in_escape))?;
}
write!(f, "' at index {idx}")
}
}
}
}
#[cfg(not(feature = "std"))]
impl core::error::Error for DecodeError {}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {}
#[cfg(not(feature = "std"))]
impl core::error::Error for EncodeError {}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncodeError::BufferTooSmall => write!(f, "Buffer too small to encode the number"),
}
}
}
struct Fmt(u128);
impl fmt::Display for Fmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0u8; 22];
let digits = digit_count(self.0);
unsafe {
let len = _encode_buf(self.0, digits, &mut buf[..digits]);
let s = core::str::from_utf8_unchecked(&buf[..len]);
f.pad(s)
}
}
}
pub fn encode_fmt<T: Into<u128>>(num: T) -> impl fmt::Display {
Fmt(num.into())
}
struct FmtAlternative(u128);
impl fmt::Display for FmtAlternative {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0u8; 22];
let digits = digit_count(self.0);
unsafe {
let len = _encode_alternative_buf(self.0, digits, &mut buf[..digits]);
let s = core::str::from_utf8_unchecked(&buf[..len]);
f.pad(s)
}
}
}
pub fn encode_fmt_alt<T: Into<u128>>(num: T) -> impl fmt::Display {
FmtAlternative(num.into())
}
#[cfg(feature = "std")]
pub fn encode_io<T: Into<u128>, W: std::io::Write + ?Sized>(
num: T,
w: &mut W,
) -> std::io::Result<()> {
let num = num.into();
let digits = digit_count(num);
let mut buf = core::mem::MaybeUninit::<[u8; 22]>::uninit();
unsafe {
let ptr = buf.as_mut_ptr() as *mut u8;
let slice = core::slice::from_raw_parts_mut(ptr, digits);
let len = _encode_buf(num, digits, slice);
debug_assert_eq!(len, digits);
w.write_all(&slice[..len])
}
}
#[cfg(feature = "std")]
pub fn encode_alternative_io<T: Into<u128>, W: std::io::Write + ?Sized>(
num: T,
w: &mut W,
) -> std::io::Result<()> {
let num = num.into();
let digits = digit_count(num);
let mut buf = core::mem::MaybeUninit::<[u8; 22]>::uninit();
unsafe {
let ptr = buf.as_mut_ptr() as *mut u8;
let slice = core::slice::from_raw_parts_mut(ptr, digits);
let len = _encode_alternative_buf(num, digits, slice);
debug_assert_eq!(len, digits);
w.write_all(&slice[..len])
}
}
pub(crate) fn digit_count(n: u128) -> usize {
const THRESHOLDS: [u128; 23] = [
0,
BASE as u128 - 1,
BASE_TO_2 as u128 - 1,
BASE_TO_3 as u128 - 1,
BASE_TO_4 as u128 - 1,
BASE_TO_5 as u128 - 1,
BASE_TO_6 as u128 - 1,
BASE_TO_7 as u128 - 1,
BASE_TO_8 as u128 - 1,
BASE_TO_9 as u128 - 1,
BASE_TO_10 - 1,
BASE_TO_11 - 1,
BASE_TO_12 - 1,
BASE_TO_13 - 1,
BASE_TO_14 - 1,
BASE_TO_15 - 1,
BASE_TO_16 - 1,
BASE_TO_17 - 1,
BASE_TO_18 - 1,
BASE_TO_19 - 1,
BASE_TO_20 - 1,
BASE_TO_21 - 1,
u128::MAX, ];
let Some(n) = NonZeroU128::new(n) else {
return 1;
};
let ilog2 = n.ilog2() as usize;
let estimate = ((ilog2 * 43) >> 8) + 1;
let threshold = unsafe { *THRESHOLDS.get_unchecked(estimate) };
let bump = (n.get() > threshold) as usize;
estimate + bump
}
#[inline(always)]
fn decode_char(
result: &mut u64,
ch: u8,
i: usize,
decode_table: &[u8; 256],
) -> Result<(), DecodeError> {
let char_value = decode_table[ch as usize];
*result = result.wrapping_mul(BASE).wrapping_add(char_value as u64);
if char_value == 255 {
Err(DecodeError::InvalidBase62Byte(ch, i))
} else {
Ok(())
}
}
#[inline]
fn decode_impl(mut input: &[u8], decode_table: &[u8; 256]) -> Result<u128, DecodeError> {
if input.is_empty() {
return Err(DecodeError::EmptyInput);
}
let chopped_count = input.iter().take_while(|&&ch| ch == b'0').count();
input = &input[chopped_count..];
let input_len = input.len();
if input_len <= 22 {
let mut iter = (chopped_count..).zip(input.iter().copied());
let mut result_a = 0_u64;
for (i, ch) in iter.by_ref().take(10) {
decode_char(&mut result_a, ch, i, decode_table)?;
}
if input_len <= 10 {
return Ok(result_a as u128);
}
const MULTIPLIERS: [u64; 12] = [
BASE,
BASE_TO_2,
BASE_TO_3,
BASE_TO_4,
BASE_TO_5,
BASE_TO_6,
BASE_TO_7,
BASE_TO_8,
BASE_TO_9,
BASE_TO_10 as u64,
BASE,
BASE_TO_2,
];
let power = MULTIPLIERS[input_len - 11];
let (a_power, b_power) = if input_len <= 20 {
(power as u128, 1)
} else {
(BASE_TO_10 * power as u128, power)
};
let result_a = (result_a as u128)
.checked_mul(a_power)
.ok_or(DecodeError::ArithmeticOverflow)?;
let mut result_b = 0_u64;
for (i, ch) in iter.by_ref().take(10) {
decode_char(&mut result_b, ch, i, decode_table)?;
}
let result_b = (result_b as u128).wrapping_mul(b_power as u128);
let mut result_c = 0_u64;
for (i, ch) in iter {
decode_char(&mut result_c, ch, i, decode_table)?;
}
let result_c = result_c as u128;
let result = result_a
.checked_add(result_b.wrapping_add(result_c))
.ok_or(DecodeError::ArithmeticOverflow)?;
Ok(result)
} else {
Err(DecodeError::ArithmeticOverflow)
}
}
pub fn encode_bytes<T: Into<u128>>(num: T, buf: &mut [u8]) -> Result<usize, EncodeError> {
let num = num.into();
let digits = digit_count(num);
if buf.len() < digits {
return Err(EncodeError::BufferTooSmall);
}
unsafe {
let len = _encode_buf(num, digits, &mut buf[..digits]);
debug_assert_eq!(len, digits);
}
Ok(digits)
}
pub fn encode_alternative_bytes<T: Into<u128>>(
num: T,
buf: &mut [u8],
) -> Result<usize, EncodeError> {
let num = num.into();
let digits = digit_count(num);
if buf.len() < digits {
return Err(EncodeError::BufferTooSmall);
}
unsafe {
let len = _encode_alternative_buf(num, digits, &mut buf[..digits]);
debug_assert_eq!(len, digits);
}
Ok(digits)
}
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<u128, DecodeError> {
decode_impl(input.as_ref(), &STANDARD_DECODE)
}
pub fn decode_alternative<T: AsRef<[u8]>>(input: T) -> Result<u128, DecodeError> {
decode_impl(input.as_ref(), &ALTERNATIVE_DECODE)
}
#[inline]
unsafe fn encode_impl(
num: u128,
digits: usize,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
unsafe {
if let Ok(num) = TryInto::<u64>::try_into(num) {
encode_impl_u64(num, digits, buf, encode_pairs)
} else if digits > 20 {
encode_impl_over_20_digits(num, digits, buf, encode_pairs)
} else if digits == 20 {
let (first_u64, second_u64) = div_base_to_10(num);
let first_u64 = first_u64 as u64;
encode_impl_20_digits(first_u64, second_u64, buf, encode_pairs)
} else {
encode_impl_over_10_under_20_digits(num, digits, buf, encode_pairs)
}
}
}
unsafe fn encode_impl_over_20_digits(
num: u128,
digits: usize,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
let (num, third_u64) = div_base_to_10(num);
let first_u64 = (num >> 10) as u64 / (BASE_TO_10 >> 10) as u64;
let second_u64 = (num - first_u64 as u128 * BASE_TO_10) as u64;
unsafe {
let [c1, c2] = *encode_pairs.get_unchecked(first_u64 as usize);
let is_22 = digits - 21; *buf.get_unchecked_mut(0) = c1;
*buf.get_unchecked_mut(is_22) = c2;
}
unsafe {
encode_impl_20_digits(
second_u64,
third_u64,
&mut buf[(digits - 20)..],
encode_pairs,
);
}
digits
}
unsafe fn encode_impl_20_digits(
first_u64: u64,
second_u64: u64,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
let first_u32 = (first_u64 / BASE_TO_5) as u32;
let second_u32 = (first_u64 % BASE_TO_5) as u32;
let third_u32 = (second_u64 / BASE_TO_5) as u32;
let fourth_u32 = (second_u64 % BASE_TO_5) as u32;
let mut nums = [first_u32, second_u32, third_u32, fourth_u32];
const BASE_POSITIONS: [usize; 4] = [0, 5, 10, 15];
for pair_idx in 0..2 {
nums.iter_mut().zip(BASE_POSITIONS).for_each(|(num, base)| {
let quotient = *num / BASE_TO_2 as u32;
let pair = (*num - BASE_TO_2 as u32 * quotient) as usize;
*num = quotient;
let pos = base + 4 - 2 * pair_idx;
unsafe {
let [c1, c2] = *encode_pairs.get_unchecked(pair);
*buf.get_unchecked_mut(pos - 1) = c1;
*buf.get_unchecked_mut(pos) = c2;
}
});
}
nums.iter()
.zip(BASE_POSITIONS)
.for_each(|(num, base)| unsafe {
*buf.get_unchecked_mut(base) = encode_pairs.get_unchecked(*num as usize)[1];
});
20
}
unsafe fn encode_impl_over_10_under_20_digits(
num: u128,
digits: usize,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
let (prefix, suffix) = div_base_to_10(num);
let prefix_digits = digits - 10;
unsafe {
encode_impl_u64_under_10_digits(
prefix as u64,
prefix_digits,
&mut buf[..prefix_digits],
encode_pairs,
);
encode_impl_u64_10_digits(suffix, &mut buf[prefix_digits..], encode_pairs);
}
digits
}
unsafe fn encode_impl_u64(
num: u64,
digits: usize,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
if digits >= 10 {
let first_digit = num / (BASE_TO_10 as u64);
let remainder = num % (BASE_TO_10 as u64);
let offset = digits - 10;
unsafe {
*buf.get_unchecked_mut(0) = encode_pairs.get_unchecked(first_digit as usize)[1];
encode_impl_u64_10_digits(remainder, &mut buf[offset..], encode_pairs);
}
digits
} else {
unsafe { encode_impl_u64_under_10_digits(num, digits, buf, encode_pairs) }
}
}
unsafe fn encode_impl_u64_under_10_digits(
mut num: u64,
digits: usize,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
debug_assert!((1..=9).contains(&digits));
debug_assert!(num < BASE.pow(digits as u32));
debug_assert!(buf.len() >= digits);
let mut write_idx = digits;
while write_idx > 2 {
let quotient = num / BASE_TO_2;
let pair = (num - quotient * BASE_TO_2) as usize;
num = quotient;
write_idx -= 2;
unsafe {
let [c1, c2] = *encode_pairs.get_unchecked(pair);
*buf.get_unchecked_mut(write_idx) = c1;
*buf.get_unchecked_mut(write_idx + 1) = c2;
}
}
unsafe {
let [c1, c2] = *encode_pairs.get_unchecked(num as usize);
*buf.get_unchecked_mut(0) = c1;
*buf.get_unchecked_mut(write_idx - 1) = c2;
}
digits
}
unsafe fn encode_impl_u64_10_digits(
num: u64,
buf: &mut [u8],
encode_pairs: &[[u8; 2]; BASE_TO_2 as usize],
) -> usize {
let first_u32 = (num / BASE_TO_5) as u32;
let second_u32 = (num % BASE_TO_5) as u32;
let mut nums = [first_u32, second_u32];
const BASE_POSITIONS: [usize; 2] = [0, 5];
for pair_idx in 0..2 {
nums.iter_mut().zip(BASE_POSITIONS).for_each(|(num, base)| {
let quotient = *num / BASE_TO_2 as u32;
let pair = (*num - BASE_TO_2 as u32 * quotient) as usize;
*num = quotient;
let pos = base + 4 - 2 * pair_idx;
unsafe {
let [c1, c2] = *encode_pairs.get_unchecked(pair);
*buf.get_unchecked_mut(pos - 1) = c1;
*buf.get_unchecked_mut(pos) = c2;
}
});
}
nums.iter()
.zip(BASE_POSITIONS)
.for_each(|(num, base)| unsafe {
*buf.get_unchecked_mut(base) = encode_pairs.get_unchecked(*num as usize)[1];
});
10
}
fn div_base_to_10(num: u128) -> (u128, u64) {
let quotient = mulh(DIV_BASE_TO_10_MULTIPLY, num) >> DIV_BASE_TO_10_SHIFT;
let remainder = num - BASE_TO_10 * quotient;
(quotient, remainder as u64)
}
const fn mulh(x: u128, y: u128) -> u128 {
const LOWER_HALF_MASK: u128 = (1 << 64) - 1;
let x_low = x & LOWER_HALF_MASK;
let y_low = y & LOWER_HALF_MASK;
let t = x_low.wrapping_mul(y_low);
let k = t >> 64;
let x_high = x >> 64;
let t = x_high.wrapping_mul(y_low) + k;
let k = t & LOWER_HALF_MASK;
let w1 = t >> 64;
let y_high = y >> 64;
let t = x_low.wrapping_mul(y_high) + k;
let k = t >> 64;
x_high.wrapping_mul(y_high) + w1 + k
}
#[inline]
unsafe fn _encode_buf(num: u128, digits: usize, buf: &mut [u8]) -> usize {
unsafe { encode_impl(num, digits, buf, &STANDARD_ENCODE_PAIRS) }
}
#[inline]
unsafe fn _encode_alternative_buf(num: u128, digits: usize, buf: &mut [u8]) -> usize {
unsafe { encode_impl(num, digits, buf, &ALTERNATIVE_ENCODE_PAIRS) }
}
#[cfg(feature = "alloc")]
mod alloc_support {
use super::*;
use alloc::string::String;
#[must_use = "this allocates a new `String`"]
pub fn encode<T: Into<u128>>(num: T) -> String {
let num = num.into();
let digits = digit_count(num);
unsafe {
let mut buf = String::with_capacity(digits);
let ptr = buf.as_mut_vec().as_mut_ptr();
let slice = core::slice::from_raw_parts_mut(ptr, digits);
let len = _encode_buf(num, digits, slice);
debug_assert_eq!(len, digits);
buf.as_mut_vec().set_len(digits);
buf
}
}
pub fn encode_buf<T: Into<u128>>(num: T, buf: &mut String) {
let num = num.into();
let digits = digit_count(num);
let old_len = buf.len();
buf.reserve(digits);
unsafe {
let vec = buf.as_mut_vec();
let ptr = vec.as_mut_ptr().add(old_len);
let slice = core::slice::from_raw_parts_mut(ptr, digits);
let len = _encode_buf(num, digits, slice);
debug_assert_eq!(len, digits);
vec.set_len(old_len + digits);
}
}
#[must_use = "this allocates a new `String`"]
pub fn encode_alternative<T: Into<u128>>(num: T) -> String {
let num = num.into();
let digits = digit_count(num);
unsafe {
let mut buf = String::with_capacity(digits);
let ptr = buf.as_mut_vec().as_mut_ptr();
let slice = core::slice::from_raw_parts_mut(ptr, digits);
let len = _encode_alternative_buf(num, digits, slice);
debug_assert_eq!(len, digits);
buf.as_mut_vec().set_len(digits);
buf
}
}
pub fn encode_alternative_buf<T: Into<u128>>(num: T, buf: &mut String) {
let num = num.into();
let digits = digit_count(num);
let old_len = buf.len();
buf.reserve(digits);
unsafe {
let vec = buf.as_mut_vec();
let ptr = vec.as_mut_ptr().add(old_len);
let slice = core::slice::from_raw_parts_mut(ptr, digits);
let len = _encode_alternative_buf(num, digits, slice);
debug_assert_eq!(len, digits);
vec.set_len(old_len + digits);
}
}
}
#[cfg(feature = "alloc")]
pub use alloc_support::*;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(all(test, feature = "alloc", not(miri)))]
mod quickcheck_tests {
use super::*;
use alloc::string::ToString;
use quickcheck::{quickcheck, TestResult};
quickcheck! {
fn encode_decode(num: u128) -> bool {
decode(encode(num)) == Ok(num)
}
}
quickcheck! {
fn encode_decode_alternative(num: u128) -> bool {
decode_alternative(encode_alternative(num)) == Ok(num)
}
}
quickcheck! {
fn decode_bad(input: Vec<u8>) -> TestResult {
if !input.is_empty() && input.iter().all(|ch| ch.is_ascii_alphanumeric()) {
TestResult::discard()
} else {
TestResult::from_bool(decode(&input).is_err())
}
}
}
quickcheck! {
fn decode_good(input: Vec<u8>) -> TestResult {
if !input.is_empty() && input.iter().all(|ch| ch.is_ascii_alphanumeric()) {
TestResult::from_bool(decode(&input).is_ok())
} else {
TestResult::discard()
}
}
}
quickcheck! {
fn fmt_matches_encode(num: u128) -> bool {
let direct = encode(num);
let formatted = encode_fmt(num).to_string();
direct == formatted
}
}
quickcheck! {
fn fmt_alt_matches_encode_alt(num: u128) -> bool {
let direct = encode_alternative(num);
let formatted = encode_fmt_alt(num).to_string();
direct == formatted
}
}
#[cfg(feature = "std")]
quickcheck! {
fn encode_io_decode(num: u128) -> bool {
let mut v = Vec::new();
encode_io(num, &mut v).unwrap();
decode(&v) == Ok(num)
}
}
#[cfg(feature = "std")]
quickcheck! {
fn encode_alternative_io_decode(num: u128) -> bool {
let mut v = Vec::new();
encode_alternative_io(num, &mut v).unwrap();
decode_alternative(&v) == Ok(num)
}
}
quickcheck! {
fn encode_buf_decode(num: u128) -> bool {
let mut s = String::new();
encode_buf(num, &mut s);
decode(&s) == Ok(num)
}
}
quickcheck! {
fn encode_alternative_buf_decode(num: u128) -> bool {
let mut s = String::new();
encode_alternative_buf(num, &mut s);
decode_alternative(&s) == Ok(num)
}
}
quickcheck! {
fn encode_bytes_decode(num: u128) -> bool {
let mut buf = [0u8; 22]; if let Ok(len) = encode_bytes(num, &mut buf) {
decode(&buf[..len]) == Ok(num)
} else {
false
}
}
}
quickcheck! {
fn encode_alternative_bytes_decode(num: u128) -> bool {
let mut buf = [0u8; 22]; if let Ok(len) = encode_alternative_bytes(num, &mut buf) {
decode_alternative(&buf[..len]) == Ok(num)
} else {
false
}
}
}
quickcheck! {
fn div_base_to_10_matches_std(num: u128) -> bool {
let (quotient_fast, remainder_fast) = div_base_to_10(num);
let quotient = num / BASE_TO_10;
let remainder = (num % BASE_TO_10) as u64;
quotient == quotient_fast && remainder == remainder_fast
}
}
}
#[test]
fn test_digit_count() {
for pow in 1..22 {
let this_power = (BASE as u128).pow(pow as u32);
assert_eq!(digit_count(this_power - 1), pow);
assert_eq!(digit_count(this_power), pow + 1);
}
assert_eq!(digit_count(0), 1);
assert_eq!(digit_count(1), 1);
assert_eq!(digit_count(u64::MAX as u128), 11);
assert_eq!(digit_count(u64::MAX as u128 + 1), 11);
assert_eq!(digit_count(u128::MAX), 22);
}
#[test]
fn test_decode_empty_input() {
assert_eq!(decode([]), Err(DecodeError::EmptyInput));
}
#[test]
fn test_decode_overflow() {
let long_input = [b'1'; 23];
assert_eq!(decode(long_input), Err(DecodeError::ArithmeticOverflow));
}
#[test]
fn test_encode_exact_and_short_buffers() {
for alternative in [false, true] {
let alphabet = if alternative {
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
} else {
b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
};
let encoder = if alternative {
encode_alternative_bytes::<u128>
} else {
encode_bytes::<u128>
};
for boundary in (0..=21).map(|p| 62_u128.pow(p)).chain([
u64::MAX as u128,
u64::MAX as u128 + 1,
u128::MAX - 1,
]) {
for num in [boundary - 1, boundary, boundary + 1] {
let mut expected = [0; 22];
let mut start = expected.len();
let mut remaining = num;
loop {
start -= 1;
expected[start] = alphabet[(remaining % 62) as usize];
remaining /= 62;
if remaining == 0 {
break;
}
}
let expected = &expected[start..];
for capacity in [expected.len() - 1, expected.len(), 22] {
let mut buf = [0xa5; 24];
let result = encoder(num, &mut buf[1..1 + capacity]);
if capacity < expected.len() {
assert_eq!(result, Err(EncodeError::BufferTooSmall));
assert_eq!(buf, [0xa5; 24]);
} else {
assert_eq!(result, Ok(expected.len()));
assert_eq!(&buf[1..1 + expected.len()], expected);
assert_eq!(buf[0], 0xa5);
assert!(buf[1 + expected.len()..].iter().all(|&ch| ch == 0xa5));
}
}
}
}
}
}
#[test]
fn test_decode_invalid_bytes_and_overflow_precedence() {
let decoders: [fn(&[u8]) -> _; 2] =
[|input| decode(input), |input| decode_alternative(input)];
for decoder in decoders {
for byte in 0..=255_u8 {
if byte.is_ascii_alphanumeric() {
continue;
}
for index in [0, 1, 9, 10, 19, 20, 21] {
let mut input = [b'0'; 24];
input[2] = b'1';
input[2 + index] = byte;
assert_eq!(
decoder(&input),
Err(DecodeError::InvalidBase62Byte(byte, 2 + index))
);
}
}
let mut input = [b'0'; 23];
input[0] = b'!';
assert_eq!(
decoder(&input[..22]),
Err(DecodeError::InvalidBase62Byte(b'!', 0))
);
assert_eq!(decoder(&input), Err(DecodeError::ArithmeticOverflow));
assert_eq!(
decoder(b"zzzzzzzzz!000000000000"),
Err(DecodeError::InvalidBase62Byte(b'!', 9))
);
assert_eq!(
decoder(b"zzzzzzzzzz!00000000000"),
Err(DecodeError::ArithmeticOverflow)
);
}
assert_eq!(
decode(b"7n42DGM5Tflk9n8mt7Fhc!"),
Err(DecodeError::InvalidBase62Byte(b'!', 21))
);
assert_eq!(
decode(b"7n42DGM5Tflk9n8mt7Fhc8"),
Err(DecodeError::ArithmeticOverflow)
);
assert_eq!(
decode_alternative(b"7N42dgm5tFLK9N8MT7fHC!"),
Err(DecodeError::InvalidBase62Byte(b'!', 21))
);
assert_eq!(
decode_alternative(b"7N42dgm5tFLK9N8MT7fHC8"),
Err(DecodeError::ArithmeticOverflow)
);
}
#[test]
fn test_decode_invalid_char() {
assert_eq!(decode(b"!"), Err(DecodeError::InvalidBase62Byte(b'!', 0)));
}
#[test]
fn test_decode_alternative_empty_input() {
assert_eq!(decode_alternative([]), Err(DecodeError::EmptyInput));
}
#[test]
fn test_decode_alternative_overflow() {
let long_input = [b'1'; 23];
assert_eq!(
decode_alternative(long_input),
Err(DecodeError::ArithmeticOverflow)
);
}
#[test]
fn test_decode_alternative_invalid_char() {
assert_eq!(
decode_alternative(b"!"),
Err(DecodeError::InvalidBase62Byte(b'!', 0))
);
}
#[test]
fn test_encode_bytes() {
let mut buf = [0; 22];
assert!(encode_bytes(u128::MAX, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"7n42DGM5Tflk9n8mt7Fhc7"));
buf.fill(0);
assert!(encode_bytes(u64::MAX as u128 + 1, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"LygHa16AHYG"));
buf.fill(0);
assert!(encode_bytes(u64::MAX, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"LygHa16AHYF"));
buf.fill(0);
assert!(encode_bytes(0_u8, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"0"));
buf.fill(0);
#[cfg(feature = "alloc")]
{
use alloc::string::String;
let mut power = 1_u128;
let mut power_minus_one_str = String::with_capacity(21);
let mut power_str = String::with_capacity(22);
power_str.push('1');
for _ in 1..22 {
power *= BASE as u128;
power_minus_one_str.push('z');
power_str.push('0');
assert!(encode_bytes(power - 1, &mut buf).is_ok());
assert!(&buf[..].starts_with(power_minus_one_str.as_bytes()));
buf.fill(0);
assert!(encode_bytes(power, &mut buf).is_ok());
assert!(&buf[..].starts_with(power_str.as_bytes()));
buf.fill(0);
}
}
assert!(encode_bytes(691337691337_u64, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"CAcoUun"));
buf.fill(0);
assert!(encode_bytes(92202686130861137968548313400401640448_u128, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"26tF05fvSIgh0000000000"));
}
#[test]
fn test_encode_alternative_bytes() {
let mut buf = [0; 22];
assert!(encode_alternative_bytes(u128::MAX, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"7N42dgm5tFLK9N8MT7fHC7"));
buf.fill(0);
assert!(encode_alternative_bytes(u64::MAX as u128 + 1, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"lYGhA16ahyg"));
buf.fill(0);
assert!(encode_alternative_bytes(u64::MAX, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"lYGhA16ahyf"));
buf.fill(0);
assert!(encode_alternative_bytes(0_u8, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"0"));
buf.fill(0);
#[cfg(feature = "alloc")]
{
use alloc::string::String;
let mut power = 1_u128;
let mut power_minus_one_str = String::with_capacity(21);
let mut power_str = String::with_capacity(22);
power_str.push('1');
for _ in 1..22 {
power *= BASE as u128;
power_minus_one_str.push('Z');
power_str.push('0');
assert!(encode_alternative_bytes(power - 1, &mut buf).is_ok());
assert!(&buf[..].starts_with(power_minus_one_str.as_bytes()));
buf.fill(0);
assert!(encode_alternative_bytes(power, &mut buf).is_ok());
assert!(&buf[..].starts_with(power_str.as_bytes()));
buf.fill(0);
}
}
assert!(encode_alternative_bytes(691337691337_u64, &mut buf).is_ok());
assert!(&buf[..].starts_with(b"caCOuUN"));
buf.fill(0);
assert!(
encode_alternative_bytes(92202686130861137968548313400401640448_u128, &mut buf).is_ok()
);
assert!(&buf[..].starts_with(b"26Tf05FVsiGH0000000000"));
}
#[test]
fn test_decode() {
assert_eq!(
decode("00001000000000000000000000"),
Ok((BASE as u128).pow(21))
);
assert_eq!(decode("7n42DGM5Tflk9n8mt7Fhc7"), Ok(u128::MAX));
assert_eq!(decode("LygHa16AHYG"), Ok(u64::MAX as u128 + 1));
assert_eq!(decode("LygHa16AHYF"), Ok(u64::MAX as u128));
assert_eq!(decode("0"), Ok(0));
#[cfg(feature = "alloc")]
{
use alloc::string::String;
let mut power = 1_u128;
let mut power_minus_one_str = String::with_capacity(21);
let mut power_str = String::with_capacity(22);
power_str.push('1');
for _ in 1..22 {
power *= BASE as u128;
power_minus_one_str.push('z');
power_str.push('0');
assert_eq!(decode(&power_minus_one_str), Ok(power - 1));
assert_eq!(decode(&power_str), Ok(power));
}
}
assert_eq!(decode("CAcoUun"), Ok(691337691337));
assert_eq!(
decode("26tF05fvSIgh0000000000"),
Ok(92202686130861137968548313400401640448)
);
}
#[test]
fn test_decode_alternative() {
assert_eq!(
decode_alternative("00001000000000000000000000"),
Ok((BASE as u128).pow(21))
);
assert_eq!(decode_alternative("7N42dgm5tFLK9N8MT7fHC7"), Ok(u128::MAX));
assert_eq!(decode_alternative("lYGhA16ahyg"), Ok(u64::MAX as u128 + 1));
assert_eq!(decode_alternative("lYGhA16ahyf"), Ok(u64::MAX as u128));
assert_eq!(decode_alternative("0"), Ok(0));
#[cfg(feature = "alloc")]
{
use alloc::string::String;
let mut power = 1_u128;
let mut power_minus_one_str = String::with_capacity(21);
let mut power_str = String::with_capacity(22);
power_str.push('1');
for _ in 1..22 {
power *= BASE as u128;
power_minus_one_str.push('Z');
power_str.push('0');
assert_eq!(decode_alternative(&power_minus_one_str), Ok(power - 1));
assert_eq!(decode_alternative(&power_str), Ok(power));
}
}
assert_eq!(decode_alternative("caCOuUN"), Ok(691337691337));
assert_eq!(
decode_alternative("26Tf05FVsiGH0000000000"),
Ok(92202686130861137968548313400401640448)
);
}
#[cfg(feature = "alloc")]
mod alloc_tests {
use super::*;
use alloc::{format, string::ToString};
#[test]
fn test_encode() {
assert_eq!(encode(u128::MAX), "7n42DGM5Tflk9n8mt7Fhc7");
assert_eq!(encode(u64::MAX as u128 + 1), "LygHa16AHYG");
assert_eq!(encode(u64::MAX), "LygHa16AHYF");
assert_eq!(encode(0_u8), "0");
let mut power = 1_u128;
let mut power_minus_one_str = String::with_capacity(21);
let mut power_str = String::with_capacity(22);
power_str.push('1');
for _ in 1..22 {
power *= BASE as u128;
power_minus_one_str.push('z');
power_str.push('0');
assert_eq!(encode(power - 1), power_minus_one_str);
assert_eq!(encode(power), power_str);
}
}
#[test]
fn test_encode_buf() {
let mut buf = String::new();
buf.push_str("Base62: ");
encode_buf(10622433094_u64, &mut buf);
assert_eq!(buf, "Base62: Base62");
buf.clear();
encode_buf(u128::MAX, &mut buf);
assert_eq!(buf, "7n42DGM5Tflk9n8mt7Fhc7");
buf.clear();
}
#[test]
fn test_encode_alternative() {
assert_eq!(encode_alternative(u128::MAX), "7N42dgm5tFLK9N8MT7fHC7");
assert_eq!(encode_alternative(u64::MAX as u128 + 1), "lYGhA16ahyg");
assert_eq!(encode_alternative(u64::MAX), "lYGhA16ahyf");
assert_eq!(encode_alternative(0_u8), "0");
}
#[test]
fn test_encode_alternative_buf() {
let mut buf = String::new();
buf.push_str("Base62: ");
encode_alternative_buf(34051405518_u64, &mut buf);
assert_eq!(buf, "Base62: Base62");
}
#[test]
fn test_fmt_basics() {
assert_eq!(encode_fmt(1337u32).to_string(), "LZ");
assert_eq!(encode_fmt(0u32).to_string(), "0");
assert_eq!(encode_fmt(u128::MAX).to_string(), "7n42DGM5Tflk9n8mt7Fhc7");
}
#[test]
fn test_fmt_padding() {
assert_eq!(
format!("{:0>22}", encode_fmt(1337u32)),
"00000000000000000000LZ"
);
assert_eq!(
format!("{:0>22}", encode_fmt(0u32)),
"0000000000000000000000"
);
}
#[test]
fn test_fmt_alternative_basics() {
assert_eq!(encode_fmt_alt(1337u32).to_string(), "lz");
assert_eq!(encode_fmt_alt(0u32).to_string(), "0");
assert_eq!(
encode_fmt_alt(u128::MAX).to_string(),
"7N42dgm5tFLK9N8MT7fHC7"
);
}
#[test]
fn test_fmt_alternative_padding() {
assert_eq!(
format!("{:0>22}", encode_fmt_alt(1337u32)),
"00000000000000000000lz"
);
assert_eq!(
format!("{:0>22}", encode_fmt_alt(0u32)),
"0000000000000000000000"
);
}
#[test]
fn test_fmt_alignment() {
let num = 1337u32;
assert_eq!(format!("{:<5}", encode_fmt(num)), "LZ ");
assert_eq!(format!("{:>5}", encode_fmt(num)), " LZ");
assert_eq!(format!("{:^5}", encode_fmt(num)), " LZ ");
}
#[test]
fn test_fmt_with_prefix() {
assert_eq!(format!("base62: {}", encode_fmt(1337u32)), "base62: LZ");
assert_eq!(format!("base62: {}", encode_fmt_alt(1337u32)), "base62: lz");
}
}
#[cfg(feature = "std")]
mod std_tests {
use super::*;
#[test]
fn test_encode_io() {
let mut output = Vec::new();
encode_io(1337_u32, &mut output).unwrap();
assert_eq!(output, b"LZ");
output.clear();
encode_io(0_u8, &mut output).unwrap();
assert_eq!(output, b"0");
}
#[test]
fn test_encode_alternative_io() {
let mut output = Vec::new();
encode_alternative_io(1337_u32, &mut output).unwrap();
assert_eq!(output, b"lz");
output.clear();
encode_alternative_io(0_u8, &mut output).unwrap();
assert_eq!(output, b"0");
output.clear();
encode_alternative_io(u128::MAX, &mut output).unwrap();
assert_eq!(output, b"7N42dgm5tFLK9N8MT7fHC7");
output.clear();
encode_alternative_io(u64::MAX as u128 + 1, &mut output).unwrap();
assert_eq!(output, b"lYGhA16ahyg");
}
}
}