mod decimal;
#[cfg(test)]
mod tests;
use super::locale::Locale;
use super::printf_impl::{ConversionSpec, Error, FormatSink, ModifierFlags, pad};
use decimal::{DIGIT_WIDTH, Decimal, DigitLimit};
use std::cmp::min;
use std::io::Write as _;
const MANTISSA_BITS: usize = f64::MANTISSA_DIGITS as usize;
fn frexp(x: f64) -> (f64, i32) {
const EXPLICIT_MANTISSA_BITS: i32 = MANTISSA_BITS as i32 - 1;
const EXPONENT_BIAS: i32 = 1023;
let mut i = x.to_bits();
let ee = ((i >> EXPLICIT_MANTISSA_BITS) & 0x7ff) as i32; if ee == 0 {
if x == 0.0 {
(x, 0)
} else {
let (x, e) = frexp(x * 2.0f64.powi(64));
(x, e - 64)
}
} else if ee == 0x7ff {
(x, 0)
} else {
let e = ee - (EXPONENT_BIAS - 1);
i &= 0x800fffffffffffff;
i |= (EXPONENT_BIAS as u64 - 1) << EXPLICIT_MANTISSA_BITS;
(f64::from_bits(i), e)
}
}
fn log10u(x: u32) -> i32 {
if x >= 1_000_000_000 {
return 9;
}
let mut result = 0;
let mut prod = 10;
while prod <= x {
result += 1;
prod *= 10;
}
result
}
fn trailing_decimal_zeros(mut d: u32) -> i32 {
if d == 0 {
return 9;
}
let mut zeros = 0;
while d.is_multiple_of(10) {
zeros += 1;
d /= 10;
}
zeros
}
struct FormatParams<'a, W: FormatSink + ?Sized> {
f: &'a mut W,
width: usize,
prec: usize,
had_prec: bool,
flags: ModifierFlags,
locale: &'a Locale,
prefix: &'static [u8],
lower: bool,
buf: &'a mut Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn format_float(
f: &mut (impl FormatSink + ?Sized),
y: f64,
width: usize,
prec: Option<usize>,
flags: ModifierFlags,
locale: &Locale,
conv_spec: ConversionSpec,
buf: &mut Vec<u8>,
) -> Result<usize, Error> {
type CS = ConversionSpec;
debug_assert!(matches!(
conv_spec,
CS::e | CS::E | CS::f | CS::F | CS::g | CS::G | CS::a | CS::A
));
let prefix = match (y.is_sign_negative(), flags.mark_pos, flags.pad_pos) {
(true, _, _) => b"-".as_slice(),
(false, true, _) => b"+".as_slice(),
(false, false, true) => b" ".as_slice(),
(false, false, false) => b"".as_slice(),
};
let had_prec = prec.is_some();
let prec = prec.unwrap_or(6);
let params = FormatParams {
f,
width,
prec,
had_prec,
flags,
locale,
prefix,
lower: conv_spec.is_lower(),
buf,
};
if !y.is_finite() {
return format_nonfinite(y, params);
}
if matches!(conv_spec, CS::a | CS::A) {
return format_a(y, params);
}
let prec_limit = match conv_spec {
CS::f | CS::F => DigitLimit::Fractional(prec / DIGIT_WIDTH + 3),
_ => DigitLimit::Total(prec / DIGIT_WIDTH + 3),
};
let mut decimal = Decimal::new(y, prec_limit);
let mut desired_frac_digits: i32 = prec.try_into().map_err(|_| Error::Overflow)?;
if matches!(conv_spec, CS::e | CS::E | CS::g | CS::G) {
let e10 = decimal.exponent();
desired_frac_digits = desired_frac_digits.saturating_sub(e10);
}
if matches!(conv_spec, CS::g | CS::G) && prec != 0 {
desired_frac_digits -= 1;
}
decimal.round_to_fractional_digits(desired_frac_digits);
match conv_spec {
CS::e | CS::E => format_e_f(&mut decimal, params, true),
CS::f | CS::F => format_e_f(&mut decimal, params, false),
CS::g | CS::G => format_g(&mut decimal, params),
_ => unreachable!(),
}
}
fn format_nonfinite<W: FormatSink + ?Sized>(
y: f64,
params: FormatParams<'_, W>,
) -> Result<usize, Error> {
let FormatParams {
f,
width,
flags,
prefix,
lower,
..
} = params;
let s = match (y.is_nan(), lower) {
(true, true) => b"nan".as_slice(),
(true, false) => b"NAN".as_slice(),
(false, true) => b"inf".as_slice(),
(false, false) => b"INF".as_slice(),
};
let unpadded_width = s.len() + prefix.len();
if !flags.left_adj {
pad(f, b' ', width, unpadded_width)?;
}
f.write_bytes(prefix)?;
f.write_bytes(s)?;
if flags.left_adj {
pad(f, b' ', width, unpadded_width)?;
}
Ok(width.max(unpadded_width))
}
fn format_a<W: FormatSink + ?Sized>(
mut y: f64,
params: FormatParams<'_, W>,
) -> Result<usize, Error> {
debug_assert!(y.is_finite());
let negative = y.is_sign_negative();
y = y.abs();
let FormatParams {
f,
width,
had_prec,
prec,
flags,
locale,
lower,
buf,
..
} = params;
let (mut y, mut e2) = frexp(y);
if y != 0.0 {
y *= 2.0;
e2 -= 1;
}
let prefix = if lower {
match (negative, flags.mark_pos, flags.pad_pos) {
(true, _, _) => b"-0x".as_slice(),
(false, true, _) => b"+0x".as_slice(),
(false, false, true) => b" 0x".as_slice(),
(false, false, false) => b"0x".as_slice(),
}
} else {
match (negative, flags.mark_pos, flags.pad_pos) {
(true, _, _) => b"-0X".as_slice(),
(false, true, _) => b"+0X".as_slice(),
(false, false, true) => b" 0X".as_slice(),
(false, false, false) => b"0X".as_slice(),
}
};
#[allow(clippy::manual_div_ceil)]
const MANTISSA_HEX_DIGITS: usize = (MANTISSA_BITS - 1 + 3) / 4;
if had_prec && prec < MANTISSA_HEX_DIGITS {
let desired_bits = prec * 4;
let bits_to_round = MANTISSA_BITS - 1 - desired_bits;
debug_assert!(bits_to_round > 0 && bits_to_round < MANTISSA_BITS);
let round = 2.0f64.powi(bits_to_round as i32);
if negative {
y = -y;
y -= round;
y += round;
y = -y;
} else {
y += round;
y -= round;
}
}
let mut estr = Vec::new();
write!(
&mut estr,
"{}{}{}",
if lower { 'p' } else { 'P' },
if e2 < 0 { '-' } else { '+' },
e2.unsigned_abs()
)
.expect("writing to Vec cannot fail");
let xdigits: &[u8; 16] = if lower {
b"0123456789abcdef"
} else {
b"0123456789ABCDEF"
};
let body = buf;
loop {
let x = y as i32;
body.push(xdigits[x as usize]);
y = 16.0 * (y - (x as f64));
if body.len() == 1 && (y != 0.0 || (had_prec && prec > 0) || flags.alt_form) {
body.push(locale.decimal_point);
}
if y == 0.0 {
break;
}
}
let mut body_exp_len = body.len() + estr.len();
if had_prec && prec > 0 {
let len_with_prec = prec.checked_add(2 + estr.len()).ok_or(Error::Overflow)?;
body_exp_len = body_exp_len.max(len_with_prec);
}
let prefix_len = prefix.len();
let unpadded_width = prefix_len
.checked_add(body_exp_len)
.ok_or(Error::Overflow)?;
if !flags.left_adj && !flags.zero_pad {
pad(f, b' ', width, unpadded_width)?;
}
f.write_bytes(prefix)?;
if !flags.left_adj && flags.zero_pad {
pad(f, b'0', width, unpadded_width)?;
}
f.write_bytes(body)?;
pad(f, b'0', body_exp_len - estr.len() - body.len(), 0)?;
f.write_bytes(&estr)?;
if flags.left_adj {
pad(f, b' ', width, prefix_len + body_exp_len)?;
}
Ok(width.max(unpadded_width))
}
fn format_e_f(
decimal: &mut Decimal,
params: FormatParams<'_, impl FormatSink + ?Sized>,
is_e: bool,
) -> Result<usize, Error> {
let FormatParams {
f,
width,
prec,
flags,
locale,
prefix,
lower,
buf,
..
} = params;
let e10 = decimal.exponent();
let mut estr = Vec::new();
if is_e {
let sign = if e10 < 0 { '-' } else { '+' };
let e = if lower { 'e' } else { 'E' };
write!(&mut estr, "{}{}{:02}", e, sign, e10.unsigned_abs())
.expect("writing to Vec cannot fail");
}
let integer_len = if is_e {
1
} else {
let mut len = 1 + e10.max(0) as usize;
if flags.grouped {
len += locale.separator_count(len);
}
len
};
let decimal_len = if prec > 0 || flags.alt_form { 1 } else { 0 };
let body_len = integer_len + decimal_len + prec + estr.len();
let prefix_len = prefix.len();
if !flags.left_adj && !flags.zero_pad {
pad(f, b' ', width, prefix_len + body_len)?;
}
f.write_bytes(prefix)?;
if !flags.left_adj && flags.zero_pad {
pad(f, b'0', width, prefix_len + body_len)?;
}
if is_e {
format_mantissa_e(decimal, prec, flags, locale, f, buf)?;
f.write_bytes(&estr)?;
} else {
format_mantissa_f(decimal, prec, flags, locale, f, buf)?;
}
if flags.left_adj && !flags.zero_pad {
pad(f, b' ', width, prefix_len + body_len)?;
}
Ok(width.max(prefix_len + body_len))
}
fn format_g(
decimal: &mut Decimal,
mut params: FormatParams<'_, impl FormatSink + ?Sized>,
) -> Result<usize, Error> {
params.prec = params.prec.max(1);
let use_style_e;
let e10 = decimal.exponent();
let e10mag = e10.unsigned_abs() as usize;
if e10 < -4 || (e10 >= 0 && e10mag >= params.prec) {
use_style_e = true;
params.prec -= 1;
} else {
use_style_e = false;
params.prec -= 1;
params.prec = if e10 < 0 {
params.prec.checked_add(e10mag).unwrap()
} else {
params.prec.checked_sub(e10mag).unwrap()
};
}
if !params.flags.alt_form {
let trailing_zeros = trailing_decimal_zeros(decimal.last().unwrap_or(0));
let mut computed_prec = decimal.fractional_digit_count() - trailing_zeros;
if use_style_e {
computed_prec += e10;
}
params.prec = params.prec.min(computed_prec.max(0) as usize);
}
format_e_f(decimal, params, use_style_e)
}
fn format_mantissa_e(
decimal: &Decimal,
prec: usize,
flags: ModifierFlags,
locale: &Locale,
f: &mut (impl FormatSink + ?Sized),
buf: &mut Vec<u8>,
) -> Result<(), Error> {
let mut prec_left = prec;
for d in 0..decimal.len_i32().max(1) {
let digit = if d < decimal.len_i32() { decimal[d] } else { 0 };
let min_width = if d > 0 { DIGIT_WIDTH } else { 1 };
buf.clear();
write!(buf, "{digit:0min_width$}").expect("writing to Vec cannot fail");
let mut s = buf.as_slice();
if d == 0 {
f.write_bytes(&s[..1])?;
s = &s[1..];
if prec_left > 0 || flags.alt_form {
f.write_bytes(&[locale.decimal_point])?;
}
}
let outlen = s.len().min(prec_left);
f.write_bytes(&s[..outlen])?;
prec_left -= outlen;
if prec_left == 0 {
break;
}
}
pad(f, b'0', prec_left, 0)?;
Ok(())
}
fn format_mantissa_f(
decimal: &mut Decimal,
prec: usize,
flags: ModifierFlags,
locale: &Locale,
f: &mut (impl FormatSink + ?Sized),
buf: &mut Vec<u8>,
) -> Result<(), Error> {
while decimal.radix < 0 {
decimal.push_front(0);
}
while decimal.len_i32() <= decimal.radix {
decimal.push_back(0);
}
let do_grouping = flags.grouped && locale.thousands_sep.is_some();
for d in 0..=decimal.radix {
let min_width = if d > 0 { DIGIT_WIDTH } else { 1 };
if do_grouping {
write!(buf, "{:0width$}", decimal[d], width = min_width)
.expect("writing to Vec cannot fail");
} else {
buf.clear();
write!(buf, "{:0width$}", decimal[d], width = min_width)
.expect("writing to Vec cannot fail");
f.write_bytes(buf)?;
}
}
if do_grouping {
f.write_bytes(&locale.apply_grouping(buf))?;
}
if prec != 0 || flags.alt_form {
f.write_bytes(&[locale.decimal_point])?;
}
let mut prec_left: usize = prec;
for d in (decimal.radix + 1)..decimal.len_i32() {
if prec_left == 0 {
break;
}
let max_digits = min(DIGIT_WIDTH, prec_left);
buf.clear();
write!(buf, "{:0width$}", decimal[d], width = DIGIT_WIDTH)
.expect("writing to Vec cannot fail");
f.write_bytes(&buf[..max_digits])?;
prec_left -= max_digits;
}
pad(f, b'0', prec_left, 0)?;
Ok(())
}