use core::fmt;
use core::fmt::Write;
pub(crate) struct StackString<const N: usize> {
buf: [u8; N],
len: usize,
}
impl<const N: usize> StackString<N> {
pub(crate) const fn new() -> Self {
Self {
buf: [0u8; N],
len: 0,
}
}
#[inline]
pub(crate) fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) }
}
}
impl<const N: usize> Default for StackString<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> fmt::Write for StackString<N> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
if self.len + bytes.len() > N {
return Err(fmt::Error);
}
self.buf[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
Ok(())
}
}
pub(crate) fn write_grouped_ascii_digits(
f: &mut fmt::Formatter<'_>,
digits: &str,
group_separator: char,
) -> fmt::Result {
let len = digits.len();
if len <= 3 {
return f.write_str(digits);
}
let first = match len % 3 {
0 => 3,
n => n,
};
f.write_str(&digits[..first])?;
let mut pos = first;
while pos < len {
f.write_char(group_separator)?;
f.write_str(&digits[pos..pos + 3])?;
pos += 3;
}
Ok(())
}
pub(crate) fn write_u128(
f: &mut fmt::Formatter<'_>,
mut value: u128,
group: bool,
group_separator: char,
) -> fmt::Result {
if value == 0 {
return f.write_str("0");
}
let mut rev = [0u8; 39];
let mut len = 0usize;
while value != 0 {
rev[len] = b'0' + (value % 10) as u8;
len += 1;
value /= 10;
}
let mut fwd = [0u8; 39];
for i in 0..len {
fwd[i] = rev[len - 1 - i];
}
let digits = unsafe { core::str::from_utf8_unchecked(&fwd[..len]) };
if group {
write_grouped_ascii_digits(f, digits, group_separator)
} else {
f.write_str(digits)
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct DecimalParts {
pub(crate) integer: u128,
pub(crate) frac_digits: [u8; 6],
pub(crate) frac_len: u8,
}
impl DecimalParts {
pub(crate) fn is_exactly_one(&self) -> bool {
self.integer == 1 && self.frac_len == 0
}
pub(crate) fn as_f64(&self) -> f64 {
let mut value = self.integer as f64;
let mut denom = 10.0;
for i in 0..(self.frac_len as usize) {
value += (self.frac_digits[i] - b'0') as f64 / denom;
denom *= 10.0;
}
value
}
}
pub(crate) fn decimal_parts_rounded(
magnitude: u128,
unit: u128,
precision: u8,
rounding: crate::RoundingMode,
is_negative: bool,
) -> DecimalParts {
let precision = precision.min(6);
let mut integer = magnitude / unit;
let remainder = magnitude % unit;
let (frac_digits, mut frac_len, carry) =
fractional_digits_rounded(remainder, unit, precision, rounding, is_negative);
if carry {
integer = integer.saturating_add(1);
}
while frac_len != 0 && frac_digits[(frac_len - 1) as usize] == b'0' {
frac_len -= 1;
}
DecimalParts {
integer,
frac_digits,
frac_len,
}
}
fn fractional_digits_rounded(
remainder: u128,
unit: u128,
precision: u8,
rounding: crate::RoundingMode,
is_negative: bool,
) -> ([u8; 6], u8, bool) {
let mut digits = [b'0'; 6];
let mut rem = remainder;
if precision == 0 {
let has_remainder = rem > 0;
let next_digit = rem.saturating_mul(10) / unit;
let carry = evaluate_carry(next_digit, has_remainder, rounding, is_negative);
return (digits, 0, carry);
}
for slot in digits.iter_mut().take(precision as usize) {
rem = rem.saturating_mul(10);
let digit = rem / unit;
rem %= unit;
*slot = b'0' + digit as u8;
}
let has_remainder = rem > 0;
let next_digit = rem.saturating_mul(10) / unit;
let carry = evaluate_carry(next_digit, has_remainder, rounding, is_negative);
if !carry {
return (digits, precision, false);
}
let mut idx = precision as i32 - 1;
while idx >= 0 {
let i = idx as usize;
if digits[i] != b'9' {
digits[i] += 1;
return (digits, precision, false);
}
digits[i] = b'0';
idx -= 1;
}
(digits, precision, true)
}
#[inline]
fn evaluate_carry(
next_digit: u128,
has_remainder: bool,
rounding: crate::RoundingMode,
is_negative: bool,
) -> bool {
match rounding {
crate::RoundingMode::HalfUp => next_digit >= 5,
crate::RoundingMode::Floor => is_negative && has_remainder,
crate::RoundingMode::Ceil => !is_negative && has_remainder,
}
}
#[inline]
pub(crate) fn compute_sigfigs_u128(
magnitude: u128,
unit: u128,
sig_figs: u8,
rounding: crate::RoundingMode,
negative: bool,
) -> (u8, DecimalParts) {
if magnitude == 0 {
return (
sig_figs.saturating_sub(1),
decimal_parts_rounded(0, unit, 0, rounding, negative),
);
}
let scaled_int = magnitude / unit;
let int_digits = if scaled_int == 0 {
1
} else {
(scaled_int.ilog10() + 1) as u8
};
let shift = sig_figs as i32 - int_digits as i32;
if shift >= 0 {
let mut decimals = (shift as u8).min(6);
let mut parts = decimal_parts_rounded(magnitude, unit, decimals, rounding, negative);
let new_int_digits = if parts.integer == 0 {
1
} else {
(parts.integer.ilog10() + 1) as u8
};
if new_int_digits > int_digits && decimals > 0 {
decimals -= 1;
if parts.frac_len > decimals {
parts.frac_len = decimals;
}
}
(decimals, parts)
} else {
let drop_digits = (-shift) as u32;
let round_factor = 10u128.pow(drop_digits);
let new_unit = unit.saturating_mul(round_factor);
let mut parts = decimal_parts_rounded(magnitude, new_unit, 0, rounding, negative);
parts.integer *= round_factor;
(0, parts)
}
}
pub(crate) fn write_frac_digits(f: &mut fmt::Formatter<'_>, digits: &[u8]) -> fmt::Result {
let s = unsafe { core::str::from_utf8_unchecked(digits) };
f.write_str(s)
}