#![no_std]
#![doc = include_str!("../README.md")]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[inline]
pub fn write_to_slice(n: impl Integer, buf: &mut [u8]) -> Result<usize, usize> {
let (neg, abs) = n.unsigned_abs();
let len = neg + abs.dump_len();
if buf.len() < len {
return Err(len);
}
unsafe {
if neg != 0 {
*buf.get_unchecked_mut(0) = b'-';
}
abs.unchecked_dump(&mut buf[neg..len]);
}
Ok(len)
}
#[inline]
pub unsafe fn unchecked_write_to_slice(n: impl Integer, buf: &mut [u8]) -> usize {
let (neg, abs) = n.unsigned_abs();
let len = neg + abs.dump_len();
unsafe {
if neg != 0 {
*buf.get_unchecked_mut(0) = b'-';
}
abs.unchecked_dump(&mut buf[neg..len]);
}
len
}
#[cfg(feature = "alloc")]
#[inline]
pub fn write_to_string(n: impl Integer, s: &mut String) {
let (neg, abs) = n.unsigned_abs();
let dump_len = neg + abs.dump_len();
unsafe {
let v = s.as_mut_vec();
let origin_len = v.len();
v.reserve(dump_len);
v.set_len(origin_len + dump_len);
if neg != 0 {
*v.get_unchecked_mut(origin_len) = b'-';
}
abs.unchecked_dump(&mut v[neg + origin_len..]);
}
}
#[cfg(feature = "alloc")]
#[inline]
pub unsafe fn unchecked_write_to_string(n: impl Integer, s: &mut String) {
let (neg, abs) = n.unsigned_abs();
let dump_len = neg + abs.dump_len();
unsafe {
let v = s.as_mut_vec();
let origin_len = v.len();
v.set_len(origin_len + dump_len);
if neg != 0 {
*v.get_unchecked_mut(origin_len) = b'-';
}
abs.unchecked_dump(&mut v[neg + origin_len..]);
}
}
pub trait Integer {
type Unsigned: Unsigned;
fn unsigned_abs(self) -> (usize, Self::Unsigned);
}
macro_rules! impl_integer {
($signed:ty, $unsigned:ty) => {
impl Integer for $signed {
type Unsigned = $unsigned;
fn unsigned_abs(self) -> (usize, Self::Unsigned) {
if self < 0 {
(1, (!self).wrapping_add(1) as $unsigned)
} else {
(0, self as $unsigned)
}
}
}
impl Integer for $unsigned {
type Unsigned = $unsigned;
fn unsigned_abs(self) -> (usize, Self::Unsigned) {
(0, self)
}
}
};
}
impl_integer!(i8, u8);
impl_integer!(i16, u16);
impl_integer!(i32, u32);
impl_integer!(i64, u64);
impl_integer!(i128, u128);
pub trait Unsigned: Copy {
fn dump_len(self) -> usize;
unsafe fn unchecked_dump(self, buf: &mut [u8]);
}
macro_rules! impl_unsigned {
($ty:ty) => {
impl Unsigned for $ty {
#[inline]
fn dump_len(self) -> usize {
(self | 1).ilog10() as usize + 1
}
#[inline]
#[allow(overflowing_literals)]
#[allow(unused_comparisons)]
unsafe fn unchecked_dump(self: $ty, buf: &mut [u8]) {
let mut offset = buf.len();
let mut remain = self;
while core::mem::size_of::<Self>() > 1 && remain >= 1000 {
offset -= 4;
let quad = (remain % 10000) as usize;
remain /= 10000;
let pair1 = quad / 100;
let pair2 = quad % 100;
unsafe {
*buf.get_unchecked_mut(offset) = digit(pair1 * 2);
*buf.get_unchecked_mut(offset + 1) = digit(pair1 * 2 + 1);
*buf.get_unchecked_mut(offset + 2) = digit(pair2 * 2);
*buf.get_unchecked_mut(offset + 3) = digit(pair2 * 2 + 1);
}
}
if remain >= 10 {
offset -= 2;
let pair = remain as usize % 100;
remain = remain / 100;
unsafe {
*buf.get_unchecked_mut(offset) = digit(pair * 2);
*buf.get_unchecked_mut(offset + 1) = digit(pair * 2 + 1);
}
}
if remain != 0 || self == 0 {
offset -= 1;
unsafe {
*buf.get_unchecked_mut(offset) = b'0' + remain as u8;
}
}
debug_assert_eq!(offset, 0);
}
}
};
}
impl_unsigned!(u8);
impl_unsigned!(u16);
impl_unsigned!(u32);
impl_unsigned!(u64);
impl Unsigned for u128 {
#[inline]
fn dump_len(self) -> usize {
(self | 1).ilog10() as usize + 1
}
#[inline]
unsafe fn unchecked_dump(self, buf: &mut [u8]) {
let mut offset = buf.len();
let mut remain = self;
loop {
if let Ok(n64) = u64::try_from(remain) {
unsafe { n64.unchecked_dump(&mut buf[..offset]) };
return;
}
let (quot_1e16, mut mod_1e16) = div_rem_1e16(remain);
for _ in 0..4 {
offset -= 4;
let quad = (mod_1e16 % 1_00_00) as usize;
mod_1e16 /= 1_00_00;
let pair1 = quad / 100;
let pair2 = quad % 100;
unsafe {
*buf.get_unchecked_mut(offset) = digit(pair1 * 2);
*buf.get_unchecked_mut(offset + 1) = digit(pair1 * 2 + 1);
*buf.get_unchecked_mut(offset + 2) = digit(pair2 * 2);
*buf.get_unchecked_mut(offset + 3) = digit(pair2 * 2 + 1);
}
}
remain = quot_1e16;
}
}
}
#[inline]
unsafe fn digit(p: usize) -> u8 {
static DECIMAL_PAIRS: &[u8; 200] = b"\
0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
unsafe { *DECIMAL_PAIRS.get_unchecked(p) }
}
fn div_rem_1e16(n: u128) -> (u128, u64) {
const D: u128 = 1_0000_0000_0000_0000;
const M_HIGH: u128 = 76624777043294442917917351357515459181;
const SH_POST: u8 = 51;
let quot = u128_mulhi(n, M_HIGH) >> SH_POST;
let rem = n - quot * D;
(quot, rem as u64)
}
fn u128_mulhi(x: u128, y: u128) -> u128 {
let x_lo = x as u64;
let x_hi = (x >> 64) as u64;
let y_lo = y as u64;
let y_hi = (y >> 64) as u64;
let carry = (u128::from(x_lo) * u128::from(y_lo)) >> 64;
let m = u128::from(x_lo) * u128::from(y_hi) + carry;
let high1 = m >> 64;
let m_lo = m as u64;
let high2 = (u128::from(x_hi) * u128::from(y_lo) + u128::from(m_lo)) >> 64;
u128::from(x_hi) * u128::from(y_hi) + high1 + high2
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
fn test<T>(n: T)
where
T: Integer + Copy + core::fmt::Display + core::fmt::Debug,
{
let mut buf: [u8; 40] = [0; 40];
let pos = write_to_slice(n, &mut buf).unwrap();
assert_eq!(str::from_utf8(&buf[..pos]).unwrap(), alloc::format!("{n}"));
}
#[test]
fn it_works() {
test(0);
test(i64::MIN);
test(i64::MAX);
test(-i64::MAX);
test(i128::MIN);
test(i128::MAX);
test(-i128::MAX);
test(10000);
test(10000000);
let mut n = u8::MAX;
while n != 0 {
test(n);
if let Ok(i) = i8::try_from(n) {
test(i);
test(-i);
}
n /= 10;
}
let mut n = u64::MAX;
while n != 0 {
test(n);
if let Ok(i) = i64::try_from(n) {
test(i);
test(-i);
}
n /= 10;
}
let mut n = u64::MAX;
while n != 0 {
test(n);
if let Ok(i) = i64::try_from(n) {
test(i);
test(-i);
}
n /= 7;
}
let mut n = u128::MAX;
while n != 0 {
test(n);
if let Ok(i) = i128::try_from(n) {
test(i);
test(-i);
}
n /= 10;
}
let mut n = u128::MAX;
while n != 0 {
test(n);
if let Ok(i) = i128::try_from(n) {
test(i);
test(-i);
}
n /= 3;
}
}
}