Add printf crate to workspace

This adds a crate containing a new implementation of printf, ported from musl.

This has some advantages:

- locale support is direct instead of being "applied after".
- No dependencies on libc printf. No unsafe code at all.
- No more WideWrite - just uses std::fmt::Write.
- Rounding is handled directly in all cases, instead of relying on Rust and/or
  libc.
- No essential dependency on WString.
- Supports %n.
- Implementation is more likely to be correct since it's based on a widely used
  printf, instead of a low-traffic Rust crate.
- Significantly faster.
This commit is contained in:
ridiculousfish 2024-05-12 15:27:10 -07:00 committed by Peter Ammon
parent b9b7dc5f6c
commit 7002571cf8
11 changed files with 3097 additions and 3 deletions

14
Cargo.lock generated
View file

@ -348,6 +348,14 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "printf"
version = "0.1.0"
dependencies = [
"libc",
"widestring",
]
[[package]]
name = "printf-compat"
version = "0.1.1"
@ -488,9 +496,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.61"
version = "2.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
dependencies = [
"proc-macro2",
"quote",
@ -646,5 +654,5 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.61",
"syn 2.0.63",
]

View file

@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"printf"
]
[workspace.package]

8
printf/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "printf"
edition = "2021"
version = "0.1.0"
[dependencies]
libc = "= 0.2.151"
widestring = "1.0.2"

240
printf/src/arg.rs Normal file
View file

@ -0,0 +1,240 @@
use super::printf_impl::Error;
use std::result::Result;
use widestring::{Utf32Str as wstr, Utf32String as WString};
/// Printf argument types.
/// Note no implementation of ToArg constructs the owned variants (String and WString);
/// callers can do so explicitly.
#[derive(Debug, PartialEq)]
pub enum Arg<'a> {
Str(&'a str),
WStr(&'a wstr),
String(String),
WString(WString),
UInt(u64),
SInt(i64, u8), // signed integers track their width as the number of bits
Float(f64),
USizeRef(&'a mut usize), // for use with %n
}
impl<'a> Arg<'a> {
pub fn set_count(&mut self, count: usize) -> Result<(), Error> {
match self {
Arg::USizeRef(p) => **p = count,
_ => return Err(Error::BadArgType),
}
Ok(())
}
// Convert this to a narrow string, using the provided storage if necessary.
pub fn as_str<'s>(&'s self, storage: &'s mut String) -> Result<&'s str, Error>
where
'a: 's,
{
match self {
Arg::Str(s) => Ok(s),
Arg::String(s) => Ok(s),
Arg::WStr(s) => {
storage.clear();
storage.extend(s.chars());
Ok(storage)
}
Arg::WString(s) => {
storage.clear();
storage.extend(s.chars());
Ok(storage)
}
_ => Err(Error::BadArgType),
}
}
// Return this value as an unsigned integer. Negative signed values will report overflow.
pub fn as_uint(&self) -> Result<u64, Error> {
match *self {
Arg::UInt(u) => Ok(u),
Arg::SInt(i, _w) => i.try_into().map_err(|_| Error::Overflow),
_ => Err(Error::BadArgType),
}
}
// Return this value as a signed integer. Unsigned values > i64::MAX will report overflow.
pub fn as_sint(&self) -> Result<i64, Error> {
match *self {
Arg::UInt(u) => u.try_into().map_err(|_| Error::Overflow),
Arg::SInt(i, _w) => Ok(i),
_ => Err(Error::BadArgType),
}
}
// If this is a signed value, then return the sign (true if negative) and the magnitude,
// masked to the value's width. This allows for e.g. -1 to be returned as 0xFF, 0xFFFF, etc.
// depending on the original width.
// If this is an unsigned value, simply return (false, u64).
pub fn as_wrapping_sint(&self) -> Result<(bool, u64), Error> {
match *self {
Arg::UInt(u) => Ok((false, u)),
Arg::SInt(i, w) => {
// Need to shift twice in case w is 64.
debug_assert!(w > 0);
let mask = ((1u64 << (w - 1)) << 1).wrapping_sub(1);
let ui = (i as u64) & mask;
Ok((i < 0, ui))
}
_ => Err(Error::BadArgType),
}
}
// Note we allow passing ints as floats, even allowing precision loss.
pub fn as_float(&self) -> Result<f64, Error> {
#[allow(clippy::cast_precision_loss)]
match *self {
Arg::Float(f) => Ok(f),
Arg::UInt(u) => Ok(u as f64),
Arg::SInt(i, _w) => Ok(i as f64),
_ => Err(Error::BadArgType),
}
}
pub fn as_char(&self) -> Result<char, Error> {
let v: u32 = self.as_uint()?.try_into().map_err(|_| Error::Overflow)?;
v.try_into().map_err(|_| Error::Overflow)
}
}
/// Conversion from a raw value to a printf argument.
pub trait ToArg<'a> {
fn to_arg(self) -> Arg<'a>;
}
impl<'a> ToArg<'a> for &'a str {
fn to_arg(self) -> Arg<'a> {
Arg::Str(self)
}
}
impl<'a> ToArg<'a> for &'a String {
fn to_arg(self) -> Arg<'a> {
Arg::Str(self)
}
}
impl<'a> ToArg<'a> for &'a wstr {
fn to_arg(self) -> Arg<'a> {
Arg::WStr(self)
}
}
impl<'a> ToArg<'a> for &'a WString {
fn to_arg(self) -> Arg<'a> {
Arg::WStr(self)
}
}
impl<'a> ToArg<'a> for f32 {
fn to_arg(self) -> Arg<'a> {
Arg::Float(self.into())
}
}
impl<'a> ToArg<'a> for f64 {
fn to_arg(self) -> Arg<'a> {
Arg::Float(self)
}
}
impl<'a> ToArg<'a> for char {
fn to_arg(self) -> Arg<'a> {
Arg::UInt((self as u32).into())
}
}
impl<'a> ToArg<'a> for &'a mut usize {
fn to_arg(self) -> Arg<'a> {
Arg::USizeRef(self)
}
}
impl<'a, T> ToArg<'a> for &'a *const T {
fn to_arg(self) -> Arg<'a> {
Arg::UInt((*self) as usize as u64)
}
}
/// All signed types.
macro_rules! impl_to_arg {
($($t:ty),*) => {
$(
impl<'a> ToArg<'a> for $t {
fn to_arg(self) -> Arg<'a> {
Arg::SInt(self as i64, <$t>::BITS as u8)
}
}
)*
};
}
impl_to_arg!(i8, i16, i32, i64, isize);
/// All unsigned types.
macro_rules! impl_to_arg_u {
($($t:ty),*) => {
$(
impl<'a> ToArg<'a> for $t {
fn to_arg(self) -> Arg<'a> {
Arg::UInt(self as u64)
}
}
)*
};
}
impl_to_arg_u!(u8, u16, u32, u64, usize);
#[cfg(test)]
mod tests {
use super::*;
use widestring::utf32str;
#[test]
fn test_to_arg() {
const SIZE_WIDTH: u8 = isize::BITS as u8;
assert!(matches!("test".to_arg(), Arg::Str("test")));
assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
assert!(matches!(utf32str!("test").to_arg(), Arg::WStr(_)));
assert!(matches!(WString::from("test").to_arg(), Arg::WStr(_)));
assert!(matches!(42f32.to_arg(), Arg::Float(_)));
assert!(matches!(42f64.to_arg(), Arg::Float(_)));
assert!(matches!('x'.to_arg(), Arg::UInt(120)));
let mut usize_val: usize = 0;
assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
assert!(matches!(42i8.to_arg(), Arg::SInt(42, 8)));
assert!(matches!(42i16.to_arg(), Arg::SInt(42, 16)));
assert!(matches!(42i32.to_arg(), Arg::SInt(42, 32)));
assert!(matches!(42i64.to_arg(), Arg::SInt(42, 64)));
assert!(matches!(42isize.to_arg(), Arg::SInt(42, SIZE_WIDTH)));
assert_eq!((-42i8).to_arg(), Arg::SInt(-42, 8));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42, 16));
assert_eq!((-42i32).to_arg(), Arg::SInt(-42, 32));
assert_eq!((-42i64).to_arg(), Arg::SInt(-42, 64));
assert_eq!((-42isize).to_arg(), Arg::SInt(-42, SIZE_WIDTH));
assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
assert!(matches!(42u16.to_arg(), Arg::UInt(42)));
assert!(matches!(42u32.to_arg(), Arg::UInt(42)));
assert!(matches!(42u64.to_arg(), Arg::UInt(42)));
assert!(matches!(42usize.to_arg(), Arg::UInt(42)));
let ptr = &42f32 as *const f32;
assert!(matches!(ptr.to_arg(), Arg::UInt(_)));
}
#[test]
fn test_negative_to_arg() {
assert_eq!((-1_i8).to_arg().as_sint(), Ok(-1));
assert_eq!((-1_i16).to_arg().as_sint(), Ok(-1));
assert_eq!((-1_i32).to_arg().as_sint(), Ok(-1));
assert_eq!((-1_i64).to_arg().as_sint(), Ok(-1));
assert_eq!((u64::MAX).to_arg().as_sint(), Err(Error::Overflow));
}
}

View file

@ -0,0 +1,309 @@
use super::{frexp, log10u};
use std::collections::VecDeque;
// A module which represents a floating point value using base 1e9 digits,
// and tracks the radix point.
// We represent floating point values in base 1e9.
pub const DIGIT_WIDTH: usize = 9;
pub const DIGIT_BASE: u32 = 1_000_000_000;
// log2(1e9) = 29.9, so store 29 binary digits per base 1e9 decimal digit.
pub const BITS_PER_DIGIT: usize = 29;
// Returns n/d and n%d, rounding towards negative infinity.
#[inline]
pub fn divmod_floor(n: i32, d: i32) -> (i32, i32) {
(n.div_euclid(d), n.rem_euclid(d))
}
// Helper to limit excess precision in our decimal representation.
// Do not compute more digits (in our base) than needed.
#[derive(Debug, Clone, Copy)]
pub enum DigitLimit {
Total(usize),
Fractional(usize),
}
// A struct representing an array of digits in base 1e9, along with the offset from the
// first digit to the least significant digit before the decimal, and the sign.
#[derive(Debug)]
pub struct Decimal {
// The list of digits, in our base.
pub digits: VecDeque<u32>,
// The offset from first digit to least significant digit before the decimal.
// Possibly negative!
pub radix: i32,
// Whether our initial value was negative.
pub negative: bool,
}
impl Decimal {
// Construct a Decimal from a floating point number.
// The number must be finite.
pub fn new(y: f64, limit: DigitLimit) -> Self {
debug_assert!(y.is_finite());
let negative = y.is_sign_negative();
// Break the number into exponent and and mantissa.
// Normalize mantissa to a single leading digit, if nonzero.
let (mut y, mut e2) = frexp(y.abs());
if y != 0.0 {
y *= (1 << BITS_PER_DIGIT) as f64;
e2 -= BITS_PER_DIGIT as i32;
}
// Express the mantissa as a decimal string in our base.
let mut digits = Vec::new();
while y != 0.0 {
debug_assert!(y >= 0.0 && y < DIGIT_BASE as f64);
let digit = y as u32;
digits.push(digit);
y = (DIGIT_BASE as f64) * (y - digit as f64);
}
// Construct ourselves and apply our exponent.
let mut decimal = Decimal {
digits: digits.into(),
radix: 0,
negative,
};
if e2 >= 0 {
decimal.shift_left(e2 as usize);
} else {
decimal.shift_right(-e2 as usize, limit);
}
decimal
}
// Push a digit to the beginning, preserving the radix point.
pub fn push_front(&mut self, digit: u32) {
self.digits.push_front(digit);
self.radix += 1;
}
// Push a digit to the end, preserving the radix point.
pub fn push_back(&mut self, digit: u32) {
self.digits.push_back(digit);
}
// Return the least significant digit.
pub fn last(&self) -> Option<u32> {
self.digits.back().copied()
}
// Return the most significant digit.
pub fn first(&self) -> Option<u32> {
self.digits.front().copied()
}
// Shift left by a power of 2.
pub fn shift_left(&mut self, mut amt: usize) {
while amt > 0 {
let sh = amt.min(BITS_PER_DIGIT);
let mut carry: u32 = 0;
for digit in self.digits.iter_mut().rev() {
let nd = ((*digit as u64) << sh) + carry as u64;
*digit = (nd % DIGIT_BASE as u64) as u32;
carry = (nd / DIGIT_BASE as u64) as u32;
}
if carry != 0 {
self.push_front(carry);
}
self.trim_trailing_zeros();
amt -= sh;
}
}
// Shift right by a power of 2, limiting the precision.
pub fn shift_right(&mut self, mut amt: usize, limit: DigitLimit) {
// Divide by 2^sh, moving left to right.
// Do no more than DIGIT_WIDTH at a time because that is the largest
// power of 2 that divides DIGIT_BASE; therefore DIGIT_BASE >> sh
// is always exact.
while amt > 0 {
let sh = amt.min(DIGIT_WIDTH);
let mut carry: u32 = 0;
// It is significantly faster to iterate over the two slices of the deque
// than the deque itself.
let (s1, s2) = self.digits.as_mut_slices();
for digit in s1.iter_mut() {
let remainder = *digit & ((1 << sh) - 1); // digit % 2^sh
*digit = (*digit >> sh) + carry;
carry = (DIGIT_BASE >> sh) * remainder;
}
for digit in s2.iter_mut() {
let remainder = *digit & ((1 << sh) - 1); // digit % 2^sh
*digit = (*digit >> sh) + carry;
carry = (DIGIT_BASE >> sh) * remainder;
}
self.trim_leading_zeros();
if carry != 0 {
self.push_back(carry);
}
amt -= sh;
// Truncate if we have computed more than we need.
match limit {
DigitLimit::Total(n) => {
self.digits.truncate(n);
}
DigitLimit::Fractional(n) => {
let current = (self.digits.len() as i32 - self.radix - 1).max(0) as usize;
let to_trunc = current.saturating_sub(n);
self.digits
.truncate(self.digits.len().saturating_sub(to_trunc));
}
}
}
}
// Return the length as an i32.
pub fn len_i32(&self) -> i32 {
self.digits.len() as i32
}
// Compute the exponent, base 10.
pub fn exponent(&self) -> i32 {
let Some(first_digit) = self.first() else {
return 0;
};
self.radix * (DIGIT_WIDTH as i32) + log10u(first_digit)
}
// Compute the number of fractional digits - possibly negative.
pub fn fractional_digit_count(&self) -> i32 {
(DIGIT_WIDTH as i32) * (self.digits.len() as i32 - self.radix - 1)
}
// Trim leading zeros.
fn trim_leading_zeros(&mut self) {
while self.digits.front() == Some(&0) {
self.digits.pop_front();
self.radix -= 1;
}
}
// Trim trailing zeros.
fn trim_trailing_zeros(&mut self) {
while self.digits.iter().last() == Some(&0) {
self.digits.pop_back();
}
}
// Round to a given number of fractional digits (possibly negative).
pub fn round_to_fractional_digits(&mut self, desired_frac_digits: i32) {
let frac_digit_count = self.fractional_digit_count();
if desired_frac_digits >= frac_digit_count {
return;
}
let (quot, rem) = divmod_floor(desired_frac_digits, DIGIT_WIDTH as i32);
// Find the index of the last digit to keep.
let mut last_digit_idx = self.radix + 1 + quot;
// If desired_frac_digits is small, and we are very small, then last_digit_idx may be negative.
while last_digit_idx < 0 {
self.push_front(0);
last_digit_idx += 1;
}
// Now we have the index of the digit - figure out how much of the digit to keep.
// If 'rem' is 0 then we keep all of it; if 'rem' is 8 then we keep only the most
// significant power of 10 (mod by 10**8).
debug_assert!(DIGIT_WIDTH as i32 > rem);
let mod_base = 10u32.pow((DIGIT_WIDTH as i32 - rem) as u32);
debug_assert!(mod_base <= DIGIT_BASE);
let remainder_to_round = self[last_digit_idx] % mod_base;
self[last_digit_idx] -= remainder_to_round;
// Round up if necessary.
if self.should_round_up(last_digit_idx, remainder_to_round, mod_base) {
self[last_digit_idx] += mod_base;
// Propogate carry.
while self[last_digit_idx] >= DIGIT_BASE {
self[last_digit_idx] = 0;
last_digit_idx -= 1;
if last_digit_idx < 0 {
self.push_front(0);
last_digit_idx = 0;
}
self[last_digit_idx] += 1;
}
}
self.digits.truncate(last_digit_idx as usize + 1);
self.trim_trailing_zeros();
}
// We are about to round ourself such that digit_idx is the last digit,
// with mod_base being a power of 10 such that we round off self[digit_idx] % mod_base,
// which is given by remainder (that is, remainder = self[digit_idx] % mod_base).
// Return true if we should round up (in magnitude), as determined by the floating point
// rounding mode.
#[inline]
fn should_round_up(&self, digit_idx: i32, remainder: u32, mod_base: u32) -> bool {
if remainder == 0 && digit_idx + 1 == self.len_i32() {
// No remaining digits.
return false;
}
// 'round' is the first float such that 'round + 1.0' is not representable.
// We will add a value to it and see whether it rounds up or down, thus
// matching the fp rounding mode.
let mut round = 2.0_f64.powi(f64::MANTISSA_DIGITS as i32);
// In the likely event that the fp rounding mode is FE_TONEAREST, then ties are rounded to
// the nearest value with a least significant digit of 0. Ensure 'round's least significant
// bit agrees with whether our rounding digit is odd.
let rounding_digit = if mod_base < DIGIT_BASE {
self[digit_idx] / mod_base
} else if digit_idx > 0 {
self[digit_idx - 1]
} else {
0
};
if rounding_digit & 1 != 0 {
round += 2.0;
// round now has an odd lsb (though round itself is even).
debug_assert!(round.to_bits() & 1 != 0);
}
// Set 'small' to a value which is less than halfway, exactly halfway, or more than halfway
// between round and the next representable float (which is round + 2.0).
let mut small = if remainder < mod_base / 2 {
0.5
} else if remainder == mod_base / 2 && digit_idx + 1 == self.len_i32() {
1.0
} else {
1.5
};
// If the initial value was negative, then negate round and small, thus respecting FE_UPWARD / FE_DOWNWARD.
if self.negative {
round = -round;
small = -small;
}
// Round up if round + small increases (in magnitude).
round + small != round
}
}
// Index, with i32.
impl std::ops::Index<i32> for Decimal {
type Output = u32;
fn index(&self, index: i32) -> &Self::Output {
assert!(index >= 0);
&self.digits[index as usize]
}
}
// IndexMut, with i32.
impl std::ops::IndexMut<i32> for Decimal {
fn index_mut(&mut self, index: i32) -> &mut Self::Output {
assert!(index >= 0);
&mut self.digits[index as usize]
}
}

573
printf/src/fmt_fp/mod.rs Normal file
View file

@ -0,0 +1,573 @@
mod decimal;
#[cfg(test)]
mod tests;
use super::locale::Locale;
use super::printf_impl::{pad, ConversionSpec, Error, ModifierFlags};
use decimal::{Decimal, DigitLimit, DIGIT_WIDTH};
use std::cmp::min;
use std::fmt::Write;
// Number of binary digits in the mantissa, including any implicit 1.
const MANTISSA_BITS: usize = f64::MANTISSA_DIGITS as usize;
// Break a floating point number into a normalized fraction and a power of 2.
// The fraction's magnitude will either be 0, or in the range [1/2, 1).
// We have value = frac * 2^exp.
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; // exponent
if ee == 0 {
if x == 0.0 {
(x, 0)
} else {
// Subnormal. Scale up.
let (x, e) = frexp(x * 2.0f64.powi(64));
(x, e - 64)
}
} else if ee == 0x7ff {
// Inf or NaN.
(x, 0)
} else {
// Normal.
// The mantissa is conceptually in the range [1, 2), but we want to
// return it in the range [1/2, 1); remove the exponent bias but increase the
// exponent by 1.
let e = ee - (EXPONENT_BIAS - 1);
// Set the exponent to -1, so we are in the range [1/2, 1).
i &= 0x800fffffffffffff;
i |= (EXPONENT_BIAS as u64 - 1) << EXPLICIT_MANTISSA_BITS;
(f64::from_bits(i), e)
}
}
// Return floor of log base 10 of an unsigned value.
// The log base 10 of 0 is treated as 0, for convenience.
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
}
// Returns the number of trailing decimal zeros in the given value.
// If the value is 0, return 9.
fn trailing_decimal_zeros(mut d: u32) -> i32 {
if d == 0 {
return 9;
}
let mut zeros = 0;
while d % 10 == 0 {
zeros += 1;
d /= 10;
}
zeros
}
/// A helper type to store common formatting parameters.
struct FormatParams<'a, W: Write> {
// The receiver of formatted output.
f: &'a mut W,
// Width of the output.
width: usize,
// Precision of the output. This defaults to 6.
prec: usize,
// Whether the precision was explicitly set.
had_prec: bool,
// Flags to control formatting options.
flags: ModifierFlags,
// The locale to apply.
locale: &'a Locale,
// The initial prefix such as sign or space. Not used for hex.
prefix: &'static str,
// Whether our conversion specifier was lowercase.
lower: bool,
// A buffer to use for temporary storage.
buf: &'a mut String,
}
/// Formats a floating-point number `y` into a provided writer `f` with specified formatting options.
///
/// # Parameters
/// - `f`: The receiver of formatted output.
/// - `y`: The value to format.
/// - `width`: The minimum width of the formatted string. If the result is shorter, it will be padded.
/// - `prec`: The precision, i.e., the number of digits after the decimal point, or None if not given.
/// - `flags`: ModifierFlags to control formatting options.
/// - `locale`: The locale.
/// - `conv_spec`: The type of formatting : 'e', 'f', 'g', 'a', 'E', 'F', 'G', 'A'.
/// - `buf`: A buffer to use for temporary storage.
///
/// # Returns
/// A `Result` which is `Ok` containing the number of bytes written on success, or an `Error`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn format_float(
f: &mut impl Write,
y: f64,
width: usize,
prec: Option<usize>,
flags: ModifierFlags,
locale: &Locale,
conv_spec: ConversionSpec,
buf: &mut String,
) -> Result<usize, Error> {
// Only float conversions are expected.
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, _, _) => "-",
(false, true, _) => "+",
(false, false, true) => " ",
(false, false, false) => "",
};
// "If the precision is missing, it is taken as 6" (except for %a and %A, which care about a missing precision).
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,
};
// Handle infinities and NaNs.
if !y.is_finite() {
return format_nonfinite(y, params);
}
// Handle hex formatting.
if matches!(conv_spec, CS::a | CS::A) {
return format_a(y, params);
}
// As an optimization, allow the precision to limit the number of digits we compute.
// Count this as number of desired decimal digits, converted to our base, rounded up, +1 for
// rounding off.
// For 'f'/'F', precision is after the decimal; for others it is total number of digits.
let prec_limit = match conv_spec {
CS::f | CS::F => DigitLimit::Fractional(prec / DIGIT_WIDTH + 2),
_ => DigitLimit::Total(prec / DIGIT_WIDTH + 2),
};
// Construct our digits.
let mut decimal = Decimal::new(y, prec_limit);
// Compute the number of desired fractional digits - possibly negative.
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) {
// For 'e' and 'E', the precision is the number of digits after the decimal point.
// We are going to divide by 10^e, so adjust desired_frac_digits accordingly.
// Note that e10 may be negative, so guard against overflow in the positive direction.
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!(),
}
}
// Format a non-finite float.
fn format_nonfinite(y: f64, params: FormatParams<'_, impl Write>) -> Result<usize, Error> {
let FormatParams {
f,
width,
flags,
prefix,
lower,
..
} = params;
let s = match (y.is_nan(), lower) {
(true, true) => "nan",
(true, false) => "NAN",
(false, true) => "inf",
(false, false) => "INF",
};
let unpadded_width = s.len() + prefix.len();
if !flags.left_adj {
pad(f, ' ', width, unpadded_width)?;
}
f.write_str(prefix)?;
f.write_str(s)?;
if flags.left_adj {
pad(f, ' ', width, unpadded_width)?;
}
Ok(width.max(unpadded_width))
}
/// Formats a floating-point number `y` as hex (%a/%A).
///
/// # Parameters
/// - `y`: The value to format. This is always finite.
/// - `params`: Params controlling formatting.
///
/// # Returns
/// A `Result` which is `Ok` containing the number of bytes written on success, or an `Error`.
fn format_a(mut y: f64, params: FormatParams<'_, impl Write>) -> 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);
// normalize to range [1, 2), or 0.0.
if y != 0.0 {
y *= 2.0;
e2 -= 1;
}
let prefix = if lower {
match (negative, flags.mark_pos, flags.pad_pos) {
(true, _, _) => "-0x",
(false, true, _) => "+0x",
(false, false, true) => " 0x",
(false, false, false) => "0x",
}
} else {
match (negative, flags.mark_pos, flags.pad_pos) {
(true, _, _) => "-0X",
(false, true, _) => "+0X",
(false, false, true) => " 0X",
(false, false, false) => "0X",
}
};
// Compute the number of hex digits in the mantissa after the decimal.
// -1 for leading 1 bit (we are to the range [1, 2)), then divide by 4, rounding up.
const MANTISSA_HEX_DIGITS: usize = (MANTISSA_BITS - 1 + 3) / 4;
if had_prec && prec < MANTISSA_HEX_DIGITS {
// Decide how many least-significant bits to round off the mantissa.
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 estr = format!(
"{}{}{}",
if lower { 'p' } else { 'P' },
if e2 < 0 { '-' } else { '+' },
e2.unsigned_abs()
);
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] as char);
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 {
// +2 for leading digit and decimal.
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)?;
// Pad on the left with spaces to the desired width?
if !flags.left_adj && !flags.zero_pad {
pad(f, ' ', width, unpadded_width)?;
}
// Output any prefix.
f.write_str(prefix)?;
// Pad after the prefix with zeros to the desired width?
if !flags.left_adj && flags.zero_pad {
pad(f, '0', width, unpadded_width)?;
}
// Output the actual value.
f.write_str(body)?;
// Pad the body with zeros on the right (reflecting precision)?
pad(f, '0', body_exp_len - estr.len() - body.len(), 0)?;
// Output the exponent.
f.write_str(&estr)?;
// Pad on the right with spaces to the desired width?
if flags.left_adj {
pad(f, ' ', width, prefix_len + body_exp_len)?;
}
Ok(width.max(unpadded_width))
}
/// Formats a floating-point number in formats %e/%E/%f/%F.
///
/// # Parameters
/// - `digits`: The extracted digits of the value.
/// - `params`: Params controlling formatting.
/// - `is_e`: If true, the conversion specifier is 'e' or 'E', otherwise 'f' or 'F'.
fn format_e_f(
decimal: &mut Decimal,
params: FormatParams<'_, impl Write>,
is_e: bool,
) -> Result<usize, Error> {
let FormatParams {
f,
width,
prec,
flags,
locale,
prefix,
lower,
buf,
..
} = params;
// Exponent base 10.
let e10 = decimal.exponent();
// Compute an exponent string for 'e' / 'E'.
let estr = if is_e {
// "The exponent always contains at least two digits."
let sign = if e10 < 0 { '-' } else { '+' };
let e = if lower { 'e' } else { 'E' };
format!("{}{}{:02}", e, sign, e10.unsigned_abs())
} else {
// No exponent for 'f' / 'F'.
String::new()
};
// Compute the body length.
// For 'f' / 'F' formats, the precision is after the decimal point, so a positive exponent
// will increase the body length. We also must consider insertion of separators.
// Note the body length must be correct, as it is used to compute the width.
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();
// Emit the prefix and any padding.
if !flags.left_adj && !flags.zero_pad {
pad(f, ' ', width, prefix_len + body_len)?;
}
f.write_str(prefix)?;
if !flags.left_adj && flags.zero_pad {
pad(f, '0', width, prefix_len + body_len)?;
}
if is_e {
format_mantissa_e(decimal, prec, flags, locale, f, buf)?;
// Emit the exponent.
f.write_str(&estr)?;
} else {
format_mantissa_f(decimal, prec, flags, locale, f, buf)?;
}
if flags.left_adj && !flags.zero_pad {
pad(f, ' ', width, prefix_len + body_len)?;
}
Ok(width.max(prefix_len + body_len))
}
/// Formats a floating point number in "g" / "G" form.
///
/// # Parameters
/// - `digits`: The extracted digits of the value.
/// - `params`: Params controlling formatting.
fn format_g(
decimal: &mut Decimal,
mut params: FormatParams<'_, impl Write>,
) -> Result<usize, Error> {
// "If the precision is zero, it is treated as 1."
params.prec = params.prec.max(1);
// "Style e is used if the exponent from its conversion is less than -4 or greater than or equal to the precision."
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;
// prec -= e10. Overflow is impossible since prec <= i32::MAX.
params.prec = if e10 < 0 {
params.prec.checked_add(e10mag).unwrap()
} else {
params.prec.checked_sub(e10mag).unwrap()
};
}
if !params.flags.alt_form {
// Count trailing zeros in last place.
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)
}
// Helper to format the mantissa of a floating point number in "e" / "E" form.
fn format_mantissa_e(
decimal: &Decimal,
prec: usize,
flags: ModifierFlags,
locale: &Locale,
f: &mut impl Write,
buf: &mut String,
) -> Result<(), Error> {
let mut prec_left = prec;
// The decimal may be empty, so ensure we loop at least once.
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, "{:0width$}", digit, width = min_width)?;
let mut s = buf.as_str();
if d == 0 {
// First digit. Emit it, and likely also a decimal point.
f.write_str(&s[..1])?;
s = &s[1..];
if prec_left > 0 || flags.alt_form {
f.write_char(locale.decimal_point)?;
}
}
let outlen = s.len().min(prec_left);
f.write_str(&s[..outlen])?;
prec_left -= outlen;
if prec_left == 0 {
break;
}
}
// Emit trailing zeros for excess precision.
pad(f, '0', prec_left, 0)?;
Ok(())
}
// Helper to format the mantissa of a floating point number in "f" / "F" form.
fn format_mantissa_f(
decimal: &mut Decimal,
prec: usize,
flags: ModifierFlags,
locale: &Locale,
f: &mut impl Write,
buf: &mut String,
) -> Result<(), Error> {
// %f conversions (almost) always have at least one digit before the decimal,
// so ensure that the radix is not-negative and the decimal covers the radix.
while decimal.radix < 0 {
decimal.push_front(0);
}
while decimal.len_i32() <= decimal.radix {
decimal.push_back(0);
}
// Emit digits before the decimal.
// We may need thousands grouping here (but for no other floating point types).
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 {
// Emit into our buffer so we can later apply thousands grouping.
write!(buf, "{:0width$}", decimal[d], width = min_width)?;
} else {
// Write digits directly.
write!(f, "{:0width$}", decimal[d], width = min_width)?;
}
}
if do_grouping {
f.write_str(&locale.apply_grouping(buf))?;
}
// Emit decimal point.
if prec != 0 || flags.alt_form {
f.write_char(locale.decimal_point)?;
}
// Emit prec digits after the decimal, stopping if we run out.
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)?;
f.write_str(&buf[..max_digits])?;
prec_left -= max_digits;
}
// Emit trailing zeros for excess precision.
pad(f, '0', prec_left, 0)?;
Ok(())
}

289
printf/src/fmt_fp/tests.rs Normal file
View file

@ -0,0 +1,289 @@
use super::*;
use decimal::*;
use std::collections::VecDeque;
#[test]
fn test_frexp() {
// Note f64::MIN_POSITIVE is normalized - we want denormal.
let min_pos_denormal = f64::from_bits(1);
let min_neg_denormal = -min_pos_denormal;
let cases = vec![
(0.0, (0.0, 0)),
(-0.0, (-0.0, 0)),
(1.0, (0.5, 1)),
(-1.0, (-0.5, 1)),
(2.5, (0.625, 2)),
(-2.5, (-0.625, 2)),
(1024.0, (0.5, 11)),
(f64::MAX, (0.9999999999999999, 1024)),
(-f64::MAX, (-0.9999999999999999, 1024)),
(f64::INFINITY, (f64::INFINITY, 0)),
(f64::NEG_INFINITY, (f64::NEG_INFINITY, 0)),
(f64::NAN, (f64::NAN, 0)),
(min_pos_denormal, (0.5, -1073)),
(min_neg_denormal, (-0.5, -1073)),
];
for (x, (want_frac, want_exp)) in cases {
let (frac, exp) = frexp(x);
if x.is_nan() {
assert!(frac.is_nan());
continue;
}
assert_eq!(frac, want_frac);
assert_eq!(frac.is_sign_negative(), want_frac.is_sign_negative());
assert_eq!(exp, want_exp);
}
}
#[test]
fn test_log10u() {
assert_eq!(log10u(0), 0);
assert_eq!(log10u(1), 0);
assert_eq!(log10u(5), 0);
assert_eq!(log10u(9), 0);
assert_eq!(log10u(10), 1);
assert_eq!(log10u(500), 2);
assert_eq!(log10u(6000), 3);
assert_eq!(log10u(9999), 3);
assert_eq!(log10u(70000), 4);
assert_eq!(log10u(70001), 4);
assert_eq!(log10u(900000), 5);
assert_eq!(log10u(3000000), 6);
assert_eq!(log10u(50000000), 7);
assert_eq!(log10u(100000000), 8);
assert_eq!(log10u(1840683745), 9);
assert_eq!(log10u(4000000000), 9);
assert_eq!(log10u(u32::MAX), 9);
}
#[test]
fn test_div_floor() {
for numer in -100..100 {
for denom in 1..100 {
let (q, r) = divmod_floor(numer, denom);
assert!(r >= 0, "Remainder should be non-negative");
assert!(r < denom.abs(), "Remainder should be less than divisor");
assert_eq!(numer, q * denom + r, "Quotient should be exact");
}
}
assert_eq!(divmod_floor(i32::MIN, 1), (i32::MIN, 0));
assert_eq!(divmod_floor(i32::MIN, i32::MAX), (-2, i32::MAX - 1));
assert_eq!(divmod_floor(i32::MAX, i32::MAX), (1, 0));
}
#[test]
fn test_digits_new() {
let unlimit = DigitLimit::Total(usize::MAX);
let mut decimal = Decimal::new(0.0, unlimit);
assert_eq!(decimal.digits, &[]);
assert_eq!(decimal.radix, 0);
decimal = Decimal::new(1.0, unlimit);
assert_eq!(decimal.digits, &[1]);
assert_eq!(decimal.radix, 0);
decimal = Decimal::new(0.5, unlimit);
assert_eq!(decimal.digits, &[500_000_000]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(0.25, unlimit);
assert_eq!(decimal.digits, &[250_000_000]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(2.0, unlimit);
assert_eq!(decimal.digits, &[2]);
assert_eq!(decimal.radix, 0);
decimal = Decimal::new(1_234_567_890.5, unlimit);
assert_eq!(decimal.digits, &[1, 234_567_890, 500_000_000]);
assert_eq!(decimal.radix, 1);
decimal = Decimal::new(12_345_678_901.0, unlimit);
assert_eq!(decimal.digits, &[12, 345_678_901]);
assert_eq!(decimal.radix, 1);
decimal = Decimal::new(2.0_f64.powi(-1), unlimit);
assert_eq!(decimal.digits, &[500_000_000]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(2.0_f64.powi(-2), unlimit);
assert_eq!(decimal.digits, &[250_000_000]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(2.0_f64.powi(-4), unlimit);
assert_eq!(decimal.digits, &[62_500_000]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(2.0_f64.powi(-8), unlimit);
assert_eq!(decimal.digits, &[3_906_250]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(2.0_f64.powi(-16), unlimit);
assert_eq!(decimal.digits, &[15_258, 789_062_500]);
assert_eq!(decimal.radix, -1);
decimal = Decimal::new(2.0_f64.powi(-64), unlimit);
assert_eq!(
decimal.digits,
&[
54_210_108,
624_275_221,
700_372_640,
43_497_085,
571_289_062,
500_000_000
]
);
assert_eq!(decimal.radix, -3);
assert!(!Decimal::new(1.0, unlimit).negative);
assert!(Decimal::new(-1.0, unlimit).negative);
assert!(!Decimal::new(0.0, unlimit).negative);
assert!(Decimal::new(-0.0, unlimit).negative);
}
#[test]
fn test_shift_left() {
// No carry.
let mut decimal = Decimal {
digits: VecDeque::from(vec![1, 2]),
radix: 0,
negative: false,
};
decimal.shift_left(1);
assert_eq!(decimal.digits, &[2, 4]);
assert_eq!(decimal.radix, 0);
// Simple carry. Trailing zeros are trimmed.
let mut decimal = Decimal {
digits: VecDeque::from(vec![500_000_000, 500_000_000]),
radix: 0,
negative: false,
};
decimal.shift_left(1);
assert_eq!(decimal.digits, &[1, 1]);
assert_eq!(decimal.radix, 1);
// Big carry.
// 1 << 100 == 1267650600228229401496703205376
let mut decimal = Decimal {
digits: VecDeque::from(vec![1]),
radix: 0,
negative: false,
};
decimal.shift_left(100);
assert_eq!(
decimal.digits,
&[1267, 650_600_228, 229_401_496, 703_205_376]
);
assert_eq!(decimal.radix, 3);
}
#[test]
fn test_shift_right() {
let unlimit = DigitLimit::Total(usize::MAX);
// No carry.
let mut decimal = Decimal {
digits: VecDeque::from(vec![2, 4]),
radix: 0,
negative: false,
};
decimal.shift_right(1, unlimit);
assert_eq!(decimal.digits, &[1, 2]);
assert_eq!(decimal.radix, 0);
// Carry. Leading zeros are trimmed.
let mut decimal = Decimal {
digits: VecDeque::from(vec![1, 0, 0]),
radix: 1,
negative: false,
};
decimal.shift_right(1, unlimit);
assert_eq!(decimal.digits, &[500_000_000, 0]);
assert_eq!(decimal.radix, 0);
// Big shift right
// 1267650600228229401496703205376 >> 100 should logically result in 1
let mut decimal = Decimal {
digits: VecDeque::from(vec![1_267, 650_600_228, 229_401_496, 703_205_376]),
radix: 3,
negative: false,
};
decimal.shift_right(100, unlimit);
assert_eq!(decimal.digits, VecDeque::from(vec![1]));
assert_eq!(decimal.radix, 0);
}
#[test]
fn test_shift_right_with_precision() {
let mut decimal = Decimal {
digits: VecDeque::from(vec![1]),
radix: 1,
negative: false,
};
decimal.shift_right(10, DigitLimit::Total(1));
assert_eq!(decimal.digits, &[976562]);
assert_eq!(decimal.radix, 0);
decimal = Decimal {
digits: VecDeque::from(vec![10000000, 10000000, 0]),
radix: 3,
negative: false,
};
decimal.shift_right(10, DigitLimit::Total(3));
assert_eq!(decimal.digits, &[9765, 625009765, 625000000]);
assert_eq!(decimal.radix, 3);
let mut decimal = Decimal {
digits: VecDeque::from(vec![1]),
radix: 1,
negative: false,
};
decimal.shift_right(10, DigitLimit::Fractional(1));
assert_eq!(decimal.digits, &[976562, 500000000]);
assert_eq!(decimal.radix, 0);
decimal.shift_right(20, DigitLimit::Fractional(1));
assert_eq!(decimal.digits, &[931322574]);
assert_eq!(decimal.radix, -1);
decimal = Decimal {
digits: VecDeque::from(vec![10000000, 10000000, 0]),
radix: 3,
negative: false,
};
decimal.shift_right(10, DigitLimit::Total(3));
assert_eq!(decimal.digits, &[9765, 625009765, 625000000]);
assert_eq!(decimal.radix, 3);
}
#[test]
fn test_exponent() {
let decimal = Decimal {
digits: VecDeque::from(vec![123456789]),
radix: 2,
negative: false,
};
assert_eq!(decimal.exponent(), 2 * (DIGIT_WIDTH as i32) + 8);
let decimal = Decimal {
digits: VecDeque::from(vec![12345]),
radix: -1,
negative: false,
};
assert_eq!(decimal.exponent(), -(DIGIT_WIDTH as i32) + 4);
let decimal = Decimal {
digits: VecDeque::from(vec![123456789]),
radix: 0,
negative: false,
};
assert_eq!(decimal.exponent(), 8);
let decimal = Decimal {
digits: VecDeque::new(),
radix: 0,
negative: false,
};
assert_eq!(decimal.exponent(), 0);
}

84
printf/src/lib.rs Normal file
View file

@ -0,0 +1,84 @@
/** Rust printf implementation, based on musl. */
mod arg;
pub use arg::{Arg, ToArg};
mod fmt_fp;
mod printf_impl;
pub use printf_impl::{sprintf_locale, Error};
pub mod locale;
pub use locale::{Locale, C_LOCALE, EN_US_LOCALE};
#[cfg(test)]
mod tests;
#[macro_export]
macro_rules! sprintf {
// Variant which allows a string literal and returns a `Utf32String`.
($fmt:literal, $($arg:expr),* $(,)?) => {
{
let mut target = widestring::Utf32String::new();
$crate::sprintf!(=> &mut target, widestring::utf32str!($fmt), $($arg),*);
target
}
};
// Variant which allows a string literal and writes to a target.
// The target should implement std::fmt::Write.
(
=> $target:expr, // target string
$fmt:literal, // format string
$($arg:expr),* // arguments
$(,)? // optional trailing comma
) => {
{
$crate::sprintf!(=> $target, widestring::utf32str!($fmt), $($arg),*);
}
};
// Variant which allows a `Utf32String` as a format, and writes to a target.
(
=> $target:expr, // target string
$fmt:expr, // format string as UTF32String
$($arg:expr),* // arguments
$(,)? // optional trailing comma
) => {
{
// May be no args!
#[allow(unused_imports)]
use $crate::ToArg;
$crate::sprintf_c_locale(
$target,
$fmt.as_char_slice(),
&mut [$($arg.to_arg()),*],
).unwrap()
}
};
// Variant which allows a `Utf32String` as a format, and returns a `Utf32String`.
($fmt:expr, $($arg:expr),* $(,)?) => {
{
let mut target = widestring::Utf32String::new();
$crate::sprintf!(=> &mut target, $fmt, $($arg),*);
target
}
};
}
/// Formats a string using the provided format specifiers and arguments, using the C locale,
/// and writes the output to the given `Write` implementation.
///
/// # Parameters
/// - `f`: The receiver of formatted output.
/// - `fmt`: The format string being parsed.
/// - `locale`: The locale to use for number formatting.
/// - `args`: Iterator over the arguments to format.
///
/// # Returns
/// A `Result` which is `Ok` containing the number of bytes written on success, or an `Error`.
pub fn sprintf_c_locale(
f: &mut impl std::fmt::Write,
fmt: &[char],
args: &mut [Arg],
) -> Result<usize, Error> {
sprintf_locale(f, fmt, &locale::C_LOCALE, args)
}

207
printf/src/locale.rs Normal file
View file

@ -0,0 +1,207 @@
/// The numeric locale. Note this is a pure value type.
#[derive(Debug, Clone, Copy)]
pub struct Locale {
/// The decimal point. Only single-char decimal points are supported.
pub decimal_point: char,
/// The thousands separator, or None if none.
/// Note some obscure locales like it_IT.ISO8859-15 seem to have a multi-char thousands separator!
/// We do not support that.
pub thousands_sep: Option<char>,
/// The grouping of digits.
/// This is to be read from left to right.
/// For example, the number 88888888888888 with a grouping of [2, 3, 4, 4]
/// would produce the string "8,8888,8888,888,88".
/// If 0, no grouping at all.
pub grouping: [u8; 4],
/// If true, the group is repeated.
/// If false, there are no groups after the last.
pub group_repeat: bool,
}
impl Locale {
/// Given a string containing only ASCII digits, return a new string with thousands separators applied.
/// This panics if the locale has no thousands separator; callers should only call this if there is a
/// thousands separator.
pub fn apply_grouping(&self, mut input: &str) -> String {
debug_assert!(input.bytes().all(|b| b.is_ascii_digit()));
let sep = self.thousands_sep.expect("no thousands separator");
let mut result = String::with_capacity(input.len() + self.separator_count(input.len()));
while !input.is_empty() {
let group_size = self.next_group_size(input.len());
let (group, rest) = input.split_at(group_size);
result.push_str(group);
if !rest.is_empty() {
result.push(sep);
}
input = rest;
}
result
}
// Given a count of remaining digits, return the number of characters in the next group, from the left (most significant).
fn next_group_size(&self, digits_left: usize) -> usize {
let mut accum: usize = 0;
for group in self.grouping {
if digits_left <= accum + group as usize {
return digits_left - accum;
}
accum += group as usize;
}
// accum now contains the sum of all groups.
// Maybe repeat.
debug_assert!(digits_left >= accum);
let repeat_group = if self.group_repeat {
*self.grouping.last().unwrap()
} else {
0
};
if repeat_group == 0 {
// No further grouping.
digits_left - accum
} else {
// Divide remaining digits by repeat_group.
// Apply any remainder to the first group.
let res = (digits_left - accum) % (repeat_group as usize);
if res > 0 {
res
} else {
repeat_group as usize
}
}
}
// Given a count of remaining digits, return the total number of separators.
pub fn separator_count(&self, digits_count: usize) -> usize {
if self.thousands_sep.is_none() {
return 0;
}
let mut sep_count = 0;
let mut accum = 0;
for group in self.grouping {
if digits_count <= accum + group as usize {
return sep_count;
}
if group > 0 {
sep_count += 1;
}
accum += group as usize;
}
debug_assert!(digits_count >= accum);
let repeat_group = if self.group_repeat {
*self.grouping.last().unwrap()
} else {
0
};
// Divide remaining digits by repeat_group.
// -1 because it's "100,000" and not ",100,100".
if repeat_group > 0 && digits_count > accum {
sep_count += (digits_count - accum - 1) / repeat_group as usize;
}
sep_count
}
}
/// The "C" numeric locale.
pub const C_LOCALE: Locale = Locale {
decimal_point: '.',
thousands_sep: None,
grouping: [0; 4],
group_repeat: false,
};
// en_us numeric locale, for testing.
#[allow(dead_code)]
pub const EN_US_LOCALE: Locale = Locale {
decimal_point: '.',
thousands_sep: Some(','),
grouping: [3, 3, 3, 3],
group_repeat: true,
};
#[test]
fn test_apply_grouping() {
let input = "123456789";
let mut result: String;
// en_US has commas.
assert_eq!(EN_US_LOCALE.thousands_sep, Some(','));
result = EN_US_LOCALE.apply_grouping(input);
assert_eq!(result, "123,456,789");
// Test weird locales.
let input: &str = "1234567890123456";
let mut locale: Locale = C_LOCALE;
locale.thousands_sep = Some('!');
locale.grouping = [5, 3, 1, 0];
locale.group_repeat = false;
result = locale.apply_grouping(input);
assert_eq!(result, "1234567!8!901!23456");
// group_repeat doesn't matter because trailing group is 0
locale.grouping = [5, 3, 1, 0];
locale.group_repeat = true;
result = locale.apply_grouping(input);
assert_eq!(result, "1234567!8!901!23456");
locale.grouping = [5, 3, 1, 2];
locale.group_repeat = false;
result = locale.apply_grouping(input);
assert_eq!(result, "12345!67!8!901!23456");
locale.grouping = [5, 3, 1, 2];
locale.group_repeat = true;
result = locale.apply_grouping(input);
assert_eq!(result, "1!23!45!67!8!901!23456");
}
#[test]
#[should_panic]
fn test_thousands_grouping_length_panics_if_no_sep() {
// We should panic if we try to group with no thousands separator.
assert_eq!(C_LOCALE.thousands_sep, None);
C_LOCALE.apply_grouping("123");
}
#[test]
fn test_thousands_grouping_length() {
fn validate_grouping_length_hint(locale: Locale, mut input: &str) {
loop {
let expected = locale.separator_count(input.len()) + input.len();
let actual = locale.apply_grouping(input).len();
assert_eq!(expected, actual);
if input.is_empty() {
break;
}
input = &input[1..];
}
}
validate_grouping_length_hint(EN_US_LOCALE, "123456789");
// Test weird locales.
let input = "1234567890123456";
let mut locale: Locale = C_LOCALE;
locale.thousands_sep = Some('!');
locale.grouping = [5, 3, 1, 0];
locale.group_repeat = false;
validate_grouping_length_hint(locale, input);
// group_repeat doesn't matter because trailing group is 0
locale.grouping = [5, 3, 1, 0];
locale.group_repeat = true;
validate_grouping_length_hint(locale, input);
locale.grouping = [5, 3, 1, 2];
locale.group_repeat = false;
validate_grouping_length_hint(locale, input);
locale.grouping = [5, 3, 1, 2];
locale.group_repeat = true;
validate_grouping_length_hint(locale, input);
}

530
printf/src/printf_impl.rs Normal file
View file

@ -0,0 +1,530 @@
/** Rust printf implementation, based on musl. */
use super::arg::Arg;
use super::fmt_fp::format_float;
use super::locale::Locale;
use std::fmt::{self, Write};
use std::mem;
use std::ops::{AddAssign, Index};
use std::result::Result;
/// Possible errors from printf.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Invalid format string.
BadFormatString,
/// Too few arguments.
MissingArg,
/// Too many arguments.
ExtraArg,
/// Argument type doesn't match format specifier.
BadArgType,
/// Precision is too large to represent.
Overflow,
/// Error emitted by the output stream.
Fmt(fmt::Error),
}
// Convenience conversion from fmt::Error.
impl From<fmt::Error> for Error {
fn from(err: fmt::Error) -> Error {
Error::Fmt(err)
}
}
#[derive(Debug, Copy, Clone, Default)]
pub(super) struct ModifierFlags {
pub alt_form: bool, // #
pub zero_pad: bool, // 0
pub left_adj: bool, // negative field width
pub pad_pos: bool, // space: blank before positive numbers
pub mark_pos: bool, // +: sign before positive numbers
pub grouped: bool, // ': group indicator
}
impl ModifierFlags {
// If c is a modifier character, set the flag and return true.
// Otherwise return false. Note we allow repeated modifier flags.
fn try_set(&mut self, c: char) -> bool {
match c {
'#' => self.alt_form = true,
'0' => self.zero_pad = true,
'-' => self.left_adj = true,
' ' => self.pad_pos = true,
'+' => self.mark_pos = true,
'\'' => self.grouped = true,
_ => return false,
};
true
}
}
// The set of prefixes of conversion specifiers.
// Note that we mostly ignore prefixes - we take sizes of values from the arguments themselves.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(non_camel_case_types)]
enum ConversionPrefix {
Empty,
hh,
h,
l,
ll,
j,
t,
z,
L,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(non_camel_case_types)]
#[rustfmt::skip]
pub(super) enum ConversionSpec {
// Integers, with prefixes "hh", "h", "l", "ll", "j", "t", "z"
// Note that we treat '%i' as '%d'.
d, o, u, x, X,
// USizeRef receiver, with same prefixes as ints
n,
// Float, with prefixes "l" and "L"
a, A, e, E, f, F, g, G,
// Pointer, no prefixes
p,
// Character or String, with supported prefixes "l"
// Note that we treat '%C' as '%c' and '%S' as '%s'.
c, s,
}
impl ConversionSpec {
// Returns true if the given prefix is supported by this conversion specifier.
fn supports_prefix(self, prefix: ConversionPrefix) -> bool {
use ConversionPrefix::*;
use ConversionSpec::*;
if matches!(prefix, Empty) {
// No prefix is always supported.
return true;
}
match self {
d | o | u | x | X | n => matches!(prefix, hh | h | l | ll | j | t | z),
a | A | e | E | f | F | g | G => matches!(prefix, l | L),
p => false,
c | s => matches!(prefix, l),
}
}
// Returns true if the conversion specifier is lowercase,
// which affects certain rendering.
#[inline]
pub(super) fn is_lower(self) -> bool {
use ConversionSpec::*;
match self {
d | o | u | x | n | a | e | f | g | p | c | s => true,
X | A | E | F | G => false,
}
}
// Returns a ConversionSpec from a character, or None if none.
fn from_char(cc: char) -> Option<Self> {
use ConversionSpec::*;
let res = match cc {
'd' | 'i' => d,
'o' => o,
'u' => u,
'x' => x,
'X' => X,
'n' => n,
'a' => a,
'A' => A,
'e' => e,
'E' => E,
'f' => f,
'F' => F,
'g' => g,
'G' => G,
'p' => p,
'c' | 'C' => c,
's' | 'S' => s,
_ => return None,
};
Some(res)
}
}
// A helper type that holds a format string slice and points into it.
// As a convenience, this returns '\0' for one-past-the-end.
#[derive(Debug)]
struct FormatString<'a>(&'a [char]);
impl<'a> FormatString<'a> {
// Return the underlying slice.
fn as_slice(&self) -> &'a [char] {
self.0
}
// Return true if we are empty.
fn is_empty(&self) -> bool {
self.0.is_empty()
}
// Read an int from our cursor, stopping at the first non-digit.
// Negative values are not supported.
// If there are no digits, return 0.
// Adjust the cursor to point to the char after the int.
fn get_int(&mut self) -> Result<usize, Error> {
use Error::Overflow;
let mut i: usize = 0;
while let Some(digit) = self[0].to_digit(10) {
i = i.checked_mul(10).ok_or(Overflow)?;
i = i.checked_add(digit as usize).ok_or(Overflow)?;
*self += 1;
}
Ok(i)
}
// Read a conversion prefix from our cursor, advancing it.
fn get_prefix(&mut self) -> ConversionPrefix {
use ConversionPrefix as CP;
let prefix = match self[0] {
'h' if self[1] == 'h' => CP::hh,
'h' => CP::h,
'l' if self[1] == 'l' => CP::ll,
'l' => CP::l,
'j' => CP::j,
't' => CP::t,
'z' => CP::z,
'L' => CP::L,
_ => CP::Empty,
};
*self += match prefix {
CP::Empty => 0,
CP::hh | CP::ll => 2,
_ => 1,
};
prefix
}
// Read an (optionally prefixed) format specifier, such as d, Lf, etc.
// Adjust the cursor to point to the char after the specifier.
fn get_specifier(&mut self) -> Result<ConversionSpec, Error> {
let prefix = self.get_prefix();
// Awkwardly placed hack to disallow %lC and %lS, since we otherwise treat
// them as the same.
if prefix != ConversionPrefix::Empty && matches!(self[0], 'C' | 'S') {
return Err(Error::BadFormatString);
}
let spec = ConversionSpec::from_char(self[0]).ok_or(Error::BadFormatString)?;
if !spec.supports_prefix(prefix) {
return Err(Error::BadFormatString);
}
*self += 1;
Ok(spec)
}
// Read a sequence of characters to be output literally, advancing the cursor.
// This handles a tail of %%.
fn get_lit(&mut self) -> &'a [char] {
let s = self.0;
let non_percents = s.iter().take_while(|&&c| c != '%').count();
// Take only an even number of percents.
let percent_pairs: usize = s[non_percents..].iter().take_while(|&&c| c == '%').count() / 2;
*self += non_percents + percent_pairs * 2;
&s[..non_percents + percent_pairs]
}
}
// Advance this format string by a number of chars.
impl AddAssign<usize> for FormatString<'_> {
fn add_assign(&mut self, rhs: usize) {
self.0 = &self.0[rhs..];
}
}
// Index into FormatString, returning \0 for one-past-the-end.
impl Index<usize> for FormatString<'_> {
type Output = char;
fn index(&self, idx: usize) -> &char {
let s = self.as_slice();
if idx == s.len() {
&'\0'
} else {
&s[idx]
}
}
}
// Pad output by emitting `c` until `min_width` is reached.
pub(super) fn pad(
f: &mut impl Write,
c: char,
min_width: usize,
current_width: usize,
) -> fmt::Result {
assert!(c == '0' || c == ' ');
if current_width >= min_width {
return Ok(());
}
const ZEROS: &str = "0000000000000000";
const SPACES: &str = " ";
let buff = if c == '0' { ZEROS } else { SPACES };
let mut remaining = min_width - current_width;
while remaining > 0 {
let n = remaining.min(buff.len());
f.write_str(&buff[..n])?;
remaining -= n;
}
Ok(())
}
/// Formats a string using the provided format specifiers, arguments, and locale,
/// and writes the output to the given `Write` implementation.
///
/// # Parameters
/// - `f`: The receiver of formatted output.
/// - `fmt`: The format string being parsed.
/// - `locale`: The locale to use for number formatting.
/// - `args`: Iterator over the arguments to format.
///
/// # Returns
/// A `Result` which is `Ok` containing the number of bytes written on success, or an `Error`.
pub fn sprintf_locale(
f: &mut impl Write,
fmt: &[char],
locale: &Locale,
args: &mut [Arg],
) -> Result<usize, Error> {
use ConversionSpec as CS;
let mut s = FormatString(fmt);
let mut args = args.iter_mut();
let mut out_len: usize = 0;
// Shared storage for the output of the conversion specifier.
let buf = &mut String::new();
'main: while !s.is_empty() {
buf.clear();
// Handle literal text and %% format specifiers.
let lit = s.get_lit();
if !lit.is_empty() {
buf.extend(lit.iter());
f.write_str(buf)?;
out_len = out_len.checked_add(lit.len()).ok_or(Error::Overflow)?;
continue 'main;
}
// Consume the % at the start of the format specifier.
debug_assert!(s[0] == '%');
s += 1;
// Read modifier flags. '-' and '0' flags are mutually exclusive.
let mut flags = ModifierFlags::default();
while flags.try_set(s[0]) {
s += 1;
}
if flags.left_adj {
flags.zero_pad = false;
}
// Read field width. We do not support $.
let width = if s[0] == '*' {
let arg_width = args.next().ok_or(Error::MissingArg)?.as_sint()?;
s += 1;
if arg_width < 0 {
flags.left_adj = true;
}
arg_width
.unsigned_abs()
.try_into()
.map_err(|_| Error::Overflow)?
} else {
s.get_int()?
};
// Optionally read precision. We do not support $.
let mut prec: Option<usize> = if s[0] == '.' && s[1] == '*' {
// "A negative precision is treated as though it were missing."
// Here we assume the precision is always signed.
s += 2;
let p = args.next().ok_or(Error::MissingArg)?.as_sint()?;
p.try_into().ok()
} else if s[0] == '.' {
s += 1;
Some(s.get_int()?)
} else {
None
};
// Disallow precisions larger than i32::MAX, in keeping with C.
if prec.unwrap_or(0) > i32::MAX as usize {
return Err(Error::Overflow);
}
// Read out the format specifier and arg.
let conv_spec = s.get_specifier()?;
let arg = args.next().ok_or(Error::MissingArg)?;
let mut prefix = "";
// Thousands grouping only works for d,u,i,f,F.
// 'i' is mapped to 'd'.
if flags.grouped && !matches!(conv_spec, CS::d | CS::u | CS::f | CS::F) {
return Err(Error::BadFormatString);
}
// Disable zero-pad if we have an explicit precision.
// "If a precision is given with a numeric conversion (d, i, o, u, i, x, and X),
// the 0 flag is ignored." p is included here.
let spec_is_numeric = matches!(conv_spec, CS::d | CS::u | CS::o | CS::p | CS::x | CS::X);
if spec_is_numeric && prec.is_some() {
flags.zero_pad = false;
}
// Apply the formatting. Some cases continue the main loop.
// Note that numeric conversions must leave 'body' empty if the value is 0.
let body: &str = match conv_spec {
CS::n => {
arg.set_count(out_len)?;
continue 'main;
}
CS::e | CS::f | CS::g | CS::a | CS::E | CS::F | CS::G | CS::A => {
// Floating point types handle output on their own.
let float = arg.as_float()?;
let len = format_float(f, float, width, prec, flags, locale, conv_spec, buf)?;
out_len = out_len.checked_add(len).ok_or(Error::Overflow)?;
continue 'main;
}
CS::p => {
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
prec = prec.map(|p| p.max(PTR_HEX_DIGITS));
let uint = arg.as_uint()?;
if uint != 0 {
prefix = "0x";
write!(buf, "{:x}", uint)?;
}
buf
}
CS::x | CS::X => {
// If someone passes us a negative value, format it with the width
// we were given.
let lower = conv_spec.is_lower();
let (_, uint) = arg.as_wrapping_sint()?;
if uint != 0 {
if flags.alt_form {
prefix = if lower { "0x" } else { "0X" };
}
if lower {
write!(buf, "{:x}", uint)?;
} else {
write!(buf, "{:X}", uint)?;
}
}
buf
}
CS::o => {
let uint = arg.as_uint()?;
if uint != 0 {
write!(buf, "{:o}", uint)?;
}
if flags.alt_form && prec.unwrap_or(0) <= buf.len() + 1 {
prec = Some(buf.len() + 1);
}
buf
}
CS::u => {
let uint = arg.as_uint()?;
if uint != 0 {
write!(buf, "{}", uint)?;
}
buf
}
CS::d => {
let arg_i = arg.as_sint()?;
if arg_i < 0 {
prefix = "-";
} else if flags.mark_pos {
prefix = "+";
} else if flags.pad_pos {
prefix = " ";
}
if arg_i != 0 {
write!(buf, "{}", arg_i.unsigned_abs())?;
}
buf
}
CS::c => {
// also 'C'
flags.zero_pad = false;
buf.push(arg.as_char()?);
buf
}
CS::s => {
// also 'S'
let s = arg.as_str(buf)?;
let p = prec.unwrap_or(s.len()).min(s.len());
prec = Some(p);
flags.zero_pad = false;
&s[..p]
}
};
// Numeric output should be empty iff the value is 0.
if spec_is_numeric && body.is_empty() {
debug_assert!(arg.as_uint().unwrap() == 0);
}
// Decide if we want to apply thousands grouping to the body, and compute its size.
// Note we have already errored out if grouped is set and this is non-numeric.
let wants_grouping = flags.grouped && locale.thousands_sep.is_some();
let body_len = match wants_grouping {
true => body.len() + locale.separator_count(body.len()),
false => body.len(),
};
// Resolve the precision.
// In the case of a non-numeric conversion, update the precision to at least the
// length of the string.
let prec = if !spec_is_numeric {
prec.unwrap_or(body_len)
} else {
prec.unwrap_or(1).max(body_len)
};
let prefix_len = prefix.len();
let unpadded_width = prefix_len.checked_add(prec).ok_or(Error::Overflow)?;
let width = width.max(unpadded_width);
// Pad on the left with spaces to the desired width?
if !flags.left_adj && !flags.zero_pad {
pad(f, ' ', width, unpadded_width)?;
}
// Output any prefix.
f.write_str(prefix)?;
// Pad after the prefix with zeros to the desired width?
if !flags.left_adj && flags.zero_pad {
pad(f, '0', width, unpadded_width)?;
}
// Pad on the left to the given precision?
pad(f, '0', prec, body_len)?;
// Output the actual value, perhaps with grouping.
if wants_grouping {
f.write_str(&locale.apply_grouping(body))?;
} else {
f.write_str(body)?;
}
// Pad on the right with spaces if we are left adjusted?
if flags.left_adj {
pad(f, ' ', width, unpadded_width)?;
}
out_len = out_len.checked_add(width).ok_or(Error::Overflow)?;
}
// Too many args?
if args.next().is_some() {
return Err(Error::ExtraArg);
}
Ok(out_len)
}

845
printf/src/tests.rs Normal file
View file

@ -0,0 +1,845 @@
use crate::arg::ToArg;
use crate::locale::{Locale, C_LOCALE, EN_US_LOCALE};
use crate::{sprintf_locale, Error};
use std::f64::consts::{E, PI, TAU};
use std::fmt;
use widestring::{utf32str, Utf32Str};
// sprintf, checking length
macro_rules! sprintf_check {
(
$fmt:expr, // format string
$($arg:expr),* // arguments
$(,)? // optional trailing comma
) => {
{
let mut target = String::new();
let chars: Vec<char> = $fmt.chars().collect();
let len = $crate::sprintf_c_locale(
&mut target,
&chars,
&mut [$($arg.to_arg()),*]
).expect("printf failed");
assert!(len == target.len(), "Wrong length returned: {} vs {}", len, target.len());
target
}
};
}
macro_rules! assert_fmt {
($fmt:expr $(, $arg:expr)* => $expected:expr) => {
assert_eq!(sprintf_check!($fmt, $($arg),*), $expected)
};
}
macro_rules! assert_fmt1 {
($fmt:expr, $arg:expr, $expected:expr) => {
assert_fmt!($fmt, $arg => $expected)
};
}
// sprintf, except we expect to return an error.
macro_rules! sprintf_err {
($fmt:expr, $($arg:expr),* => $expected:expr) => {
{
let chars: Vec<char> = $fmt.chars().collect();
let err = $crate::sprintf_c_locale(
&mut NullOutput,
&chars,
&mut [$($arg.to_arg()),*],
).unwrap_err();
assert_eq!(err, $expected, "Wrong error returned: {:?}", err);
}
};
}
// sprintf, except we throw away the output and return only the count.
macro_rules! sprintf_count {
($fmt:expr $(, $arg:expr)*) => {
{
let chars: Vec<char> = $fmt.chars().collect();
$crate::sprintf_c_locale(
&mut NullOutput,
&chars,
&mut [$($arg.to_arg()),*],
).expect("printf failed")
}
};
}
// Null writer which ignores all input.
struct NullOutput;
impl fmt::Write for NullOutput {
fn write_str(&mut self, _s: &str) -> fmt::Result {
Ok(())
}
}
#[test]
fn smoke() {
assert_fmt!("Hello, %s!", "world" => "Hello, world!");
assert_fmt!("Hello, %ls!", "world" => "Hello, world!");
assert_fmt!("Hello, world! %d %%%%", 3 => "Hello, world! 3 %%");
assert_fmt!("" => "");
}
#[test]
fn test1() {
// A convenient place to isolate a single test, e.g. cargo test -- test1
assert_fmt!("%.0e", 0 => "0e+00");
}
#[test]
fn test_n() {
// Test that the %n specifier correctly stores the number of characters written.
let mut count: usize = 0;
assert_fmt!("%d%n", 123, &mut count => "123");
assert_eq!(count, 3);
assert_fmt!("%256d%%%n", 123, &mut count => format!("{:>256}%", 123));
assert_eq!(count, 257);
assert_fmt!("%d %s%n", 123, "hello", &mut count => "123 hello");
assert_eq!(count, 3 + 1 + 5);
assert_fmt!("%%%n", &mut count => "%");
assert_eq!(count, 1);
}
#[test]
fn test_plain() {
assert_fmt!("abc" => "abc");
assert_fmt!("" => "");
assert_fmt!("%%" => "%");
assert_fmt!("%% def" => "% def");
assert_fmt!("abc %%" => "abc %");
assert_fmt!("abc %% def" => "abc % def");
assert_fmt!("abc %%%% def" => "abc %% def");
assert_fmt!("%%%%%%" => "%%%");
}
#[test]
fn test_str() {
assert_fmt!("hello %s", "world" => "hello world");
assert_fmt!("hello %%%s", "world" => "hello %world");
assert_fmt!("%10s", "world" => " world");
assert_fmt!("%.4s", "world" => "worl");
assert_fmt!("%10.4s", "world" => " worl");
assert_fmt!("%-10.4s", "world" => "worl ");
assert_fmt!("%-10s", "world" => "world ");
assert_fmt!("test %% with string: %s yay\n", "FOO" => "test % with string: FOO yay\n");
assert_fmt!("test char %c", '~' => "test char ~");
assert_fmt!("%.0s", "test" => "");
assert_fmt!("%.1s", "test" => "t");
assert_fmt!("%.3s", "test" => "tes");
assert_fmt!("%5.3s", "test" => " tes");
assert_fmt!("%.4s", "test" => "test");
assert_fmt!("%.100s", "test" => "test");
}
#[test]
fn test_int() {
assert_fmt!("% 0*i", 23125, 17 => format!(" {:023124}", 17));
assert_fmt!("% 010i", 23125 => " 000023125");
assert_fmt!("% 10i", 23125 => " 23125");
assert_fmt!("% 5i", 23125 => " 23125");
assert_fmt!("% 4i", 23125 => " 23125");
assert_fmt!("%- 010i", 23125 => " 23125 ");
assert_fmt!("%- 10i", 23125 => " 23125 ");
assert_fmt!("%- 5i", 23125 => " 23125");
assert_fmt!("%- 4i", 23125 => " 23125");
assert_fmt!("%+ 010i", 23125 => "+000023125");
assert_fmt!("%+ 10i", 23125 => " +23125");
assert_fmt!("%+ 5i", 23125 => "+23125");
assert_fmt!("%+ 4i", 23125 => "+23125");
assert_fmt!("%-010i", 23125 => "23125 ");
assert_fmt!("%-10i", 23125 => "23125 ");
assert_fmt!("%-5i", 23125 => "23125");
assert_fmt!("%-4i", 23125 => "23125");
assert_fmt!("%d", 12 => "12");
assert_fmt!("%d", -123 => "-123");
assert_fmt!("~%d~", 148 => "~148~");
assert_fmt!("00%dxx", -91232 => "00-91232xx");
assert_fmt!("%x", -9232 => "ffffdbf0");
assert_fmt!("%X", 432 => "1B0");
assert_fmt!("%09X", 432 => "0000001B0");
assert_fmt!("%9X", 432 => " 1B0");
assert_fmt!("%+9X", 492 => " 1EC");
assert_fmt!("% #9x", 4589 => " 0x11ed");
assert_fmt!("%2o", 4 => " 4");
assert_fmt!("% 12d", -4 => " -4");
assert_fmt!("% 12d", 48 => " 48");
assert_fmt!("%ld", -4_i64 => "-4");
assert_fmt!("%lld", -4_i64 => "-4");
assert_fmt!("%lX", -4_i64 => "FFFFFFFFFFFFFFFC");
assert_fmt!("%ld", 48_i64 => "48");
assert_fmt!("%lld", 48_i64 => "48");
assert_fmt!("%-8hd", -12_i16 => "-12 ");
assert_fmt!("%u", 12 => "12");
assert_fmt!("~%u~", 148 => "~148~");
assert_fmt!("00%uxx", 91232 => "0091232xx");
assert_fmt!("%x", 9232 => "2410");
assert_fmt!("%9X", 492 => " 1EC");
assert_fmt!("% 12u", 4 => " 4");
assert_fmt!("% 12u", 48 => " 48");
assert_fmt!("%lu", 4_u64 => "4");
assert_fmt!("%llu", 4_u64 => "4");
assert_fmt!("%lX", 4_u64 => "4");
assert_fmt!("%lu", 48_u64 => "48");
assert_fmt!("%llu", 48_u64 => "48");
assert_fmt!("%-8hu", 12_u16 => "12 ");
// Gross combinations of padding and precision.
assert_fmt!("%30d", 1234565678 => " 1234565678");
assert_fmt!("%030d", 1234565678 => "000000000000000000001234565678");
assert_fmt!("%30.20d", 1234565678 => " 00000000001234565678");
// Here we specify both a precision and request zero-padding.
// "If a precision is given with a numeric conversion (d, i, o, u, x, and X), the 0 flag is ignored."
assert_fmt!("%030.20d", 1234565678 => " 00000000001234565678");
assert_fmt!("%030.0d", 1234565678 => " 1234565678");
// width, precision, alignment
assert_fmt1!("%04d", 12, "0012");
assert_fmt1!("%.3d", 12, "012");
assert_fmt1!("%3d", 12, " 12");
assert_fmt1!("%-3d", 12, "12 ");
assert_fmt1!("%+3d", 12, "+12");
assert_fmt1!("%+-5d", 12, "+12 ");
assert_fmt1!("%+- 5d", 12, "+12 ");
assert_fmt1!("%- 5d", 12, " 12 ");
assert_fmt1!("% d", 12, " 12");
assert_fmt1!("%0-5d", 12, "12 ");
assert_fmt1!("%-05d", 12, "12 ");
// ...explicit precision of 0 shall be no characters except for alt-octal.
assert_fmt1!("%.0d", 0, "");
assert_fmt1!("%.0o", 0, "");
assert_fmt1!("%#.0d", 0, "");
assert_fmt1!("%#.0o", 0, "0");
assert_fmt1!("%#.0x", 0, "");
// ...but it still has to honor width and flags.
assert_fmt1!("%2.0u", 0, " ");
assert_fmt1!("%02.0u", 0, " ");
assert_fmt1!("%2.0d", 0, " ");
assert_fmt1!("%02.0d", 0, " ");
assert_fmt1!("% .0d", 0, " ");
assert_fmt1!("%+.0d", 0, "+");
}
#[test]
fn test_octal() {
assert_fmt!("% 010o", 23125 => "0000055125");
assert_fmt!("% 10o", 23125 => " 55125");
assert_fmt!("% 5o", 23125 => "55125");
assert_fmt!("% 4o", 23125 => "55125");
assert_fmt!("%- 010o", 23125 => "55125 ");
assert_fmt!("%- 10o", 23125 => "55125 ");
assert_fmt!("%- 5o", 23125 => "55125");
assert_fmt!("%- 4o", 23125 => "55125");
assert_fmt!("%+ 010o", 23125 => "0000055125");
assert_fmt!("%+ 10o", 23125 => " 55125");
assert_fmt!("%+ 5o", 23125 => "55125");
assert_fmt!("%+ 4o", 23125 => "55125");
assert_fmt!("%-010o", 23125 => "55125 ");
assert_fmt!("%-10o", 23125 => "55125 ");
assert_fmt!("%-5o", 23125 => "55125");
assert_fmt!("%-4o", 23125 => "55125");
assert_fmt1!("%o", 15, "17");
assert_fmt1!("%#o", 15, "017");
assert_fmt1!("%#o", 0, "0");
assert_fmt1!("%#.0o", 0, "0");
assert_fmt1!("%#.1o", 0, "0");
assert_fmt1!("%#o", 1, "01");
assert_fmt1!("%#.0o", 1, "01");
assert_fmt1!("%#.1o", 1, "01");
assert_fmt1!("%#04o", 1, "0001");
assert_fmt1!("%#04.0o", 1, " 01");
assert_fmt1!("%#04.1o", 1, " 01");
assert_fmt1!("%04o", 1, "0001");
assert_fmt1!("%04.0o", 1, " 1");
assert_fmt1!("%04.1o", 1, " 1");
}
#[test]
fn test_hex() {
assert_fmt!("% 010x", 23125 => "0000005a55");
assert_fmt!("% 10x", 23125 => " 5a55");
assert_fmt!("% 5x", 23125 => " 5a55");
assert_fmt!("% 4x", 23125 => "5a55");
assert_fmt!("%- 010x", 23125 => "5a55 ");
assert_fmt!("%- 10x", 23125 => "5a55 ");
assert_fmt!("%- 5x", 23125 => "5a55 ");
assert_fmt!("%- 4x", 23125 => "5a55");
assert_fmt!("%+ 010x", 23125 => "0000005a55");
assert_fmt!("%+ 10x", 23125 => " 5a55");
assert_fmt!("%+ 5x", 23125 => " 5a55");
assert_fmt!("%+ 4x", 23125 => "5a55");
assert_fmt!("%-010x", 23125 => "5a55 ");
assert_fmt!("%-10x", 23125 => "5a55 ");
assert_fmt!("%-5x", 23125 => "5a55 ");
assert_fmt!("%-4x", 23125 => "5a55");
assert_fmt!("%# 010x", 23125 => "0x00005a55");
assert_fmt!("%# 10x", 23125 => " 0x5a55");
assert_fmt!("%# 5x", 23125 => "0x5a55");
assert_fmt!("%# 4x", 23125 => "0x5a55");
assert_fmt!("%#- 010x", 23125 => "0x5a55 ");
assert_fmt!("%#- 10x", 23125 => "0x5a55 ");
assert_fmt!("%#- 5x", 23125 => "0x5a55");
assert_fmt!("%#- 4x", 23125 => "0x5a55");
assert_fmt!("%#+ 010x", 23125 => "0x00005a55");
assert_fmt!("%#+ 10x", 23125 => " 0x5a55");
assert_fmt!("%#+ 5x", 23125 => "0x5a55");
assert_fmt!("%#+ 4x", 23125 => "0x5a55");
assert_fmt!("%#-010x", 23125 => "0x5a55 ");
assert_fmt!("%#-10x", 23125 => "0x5a55 ");
assert_fmt!("%#-5x", 23125 => "0x5a55");
assert_fmt!("%#-4x", 23125 => "0x5a55");
assert_fmt!("% 010X", 23125 => "0000005A55");
assert_fmt!("% 10X", 23125 => " 5A55");
assert_fmt!("% 5X", 23125 => " 5A55");
assert_fmt!("% 4X", 23125 => "5A55");
assert_fmt!("%- 010X", 23125 => "5A55 ");
assert_fmt!("%- 10X", 23125 => "5A55 ");
assert_fmt!("%- 5X", 23125 => "5A55 ");
assert_fmt!("%- 4X", 23125 => "5A55");
assert_fmt!("%+ 010X", 23125 => "0000005A55");
assert_fmt!("%+ 10X", 23125 => " 5A55");
assert_fmt!("%+ 5X", 23125 => " 5A55");
assert_fmt!("%+ 4X", 23125 => "5A55");
assert_fmt!("%-010X", 23125 => "5A55 ");
assert_fmt!("%-10X", 23125 => "5A55 ");
assert_fmt!("%-5X", 23125 => "5A55 ");
assert_fmt!("%-4X", 23125 => "5A55");
assert_fmt!("%#x", 234834 => "0x39552");
assert_fmt!("%#X", 234834 => "0X39552");
assert_fmt!("%#.10o", 54834 => "0000153062");
assert_fmt1!("%x", 63, "3f");
assert_fmt1!("%#x", 63, "0x3f");
assert_fmt1!("%X", 63, "3F");
}
#[test]
fn test_char() {
assert_fmt!("%c", 'a' => "a");
assert_fmt!("%10c", 'a' => " a");
assert_fmt!("%-10c", 'a' => "a ");
}
#[test]
fn test_ptr() {
assert_fmt!("%p", core::ptr::null::<u8>() => "0");
assert_fmt!("%p", 0xDEADBEEF_usize as *const u8 => "0xdeadbeef");
}
#[test]
fn test_float() {
// Basic form, handling of exponent/precision for 0
assert_fmt1!("%a", 0.0, "0x0p+0");
assert_fmt1!("%e", 0.0, "0.000000e+00");
assert_fmt1!("%f", 0.0, "0.000000");
assert_fmt1!("%g", 0.0, "0");
assert_fmt1!("%#g", 0.0, "0.00000");
assert_fmt1!("%la", 0.0, "0x0p+0");
assert_fmt1!("%le", 0.0, "0.000000e+00");
assert_fmt1!("%lf", 0.0, "0.000000");
assert_fmt1!("%lg", 0.0, "0");
assert_fmt1!("%#lg", 0.0, "0.00000");
// rounding
assert_fmt1!("%f", 1.1, "1.100000");
assert_fmt1!("%f", 1.2, "1.200000");
assert_fmt1!("%f", 1.3, "1.300000");
assert_fmt1!("%f", 1.4, "1.400000");
assert_fmt1!("%f", 1.5, "1.500000");
assert_fmt1!("%.4f", 1.06125, "1.0613"); /* input is not representible exactly as double */
assert_fmt1!("%.4f", 1.03125, "1.0312"); /* 0x1.08p0 */
assert_fmt1!("%.2f", 1.375, "1.38");
assert_fmt1!("%.1f", 1.375, "1.4");
assert_fmt1!("%.1lf", 1.375, "1.4");
assert_fmt1!("%.15f", 1.1, "1.100000000000000");
assert_fmt1!("%.16f", 1.1, "1.1000000000000001");
assert_fmt1!("%.17f", 1.1, "1.10000000000000009");
assert_fmt1!("%.2e", 1500001.0, "1.50e+06");
assert_fmt1!("%.2e", 1505000.0, "1.50e+06");
assert_fmt1!("%.2e", 1505000.0000009537, "1.51e+06");
assert_fmt1!("%.2e", 1505001.0, "1.51e+06");
assert_fmt1!("%.2e", 1506000.0, "1.51e+06");
// pi in double precision, printed to a few extra places
assert_fmt1!("%.15f", PI, "3.141592653589793");
assert_fmt1!("%.18f", PI, "3.141592653589793116");
// exact conversion of large integers
assert_fmt1!(
"%.0f",
340282366920938463463374607431768211456.0,
"340282366920938463463374607431768211456"
);
let tiny = f64::exp2(-1021.0);
assert_fmt1!("%.1022f", tiny, format!("{:.1022}", tiny));
let tiny = f64::exp2(-1022.0);
assert_fmt1!("%.1022f", tiny, format!("{:.1022}", tiny));
assert_fmt1!("%.12g", 1000000000005.0, "1e+12");
assert_fmt1!("%.12g", 100000000002500.0, "1.00000000002e+14");
assert_fmt1!("%.50g", 100000000000000.5, "100000000000000.5");
assert_fmt1!("%.50g", 987654321098765.0, "987654321098765");
assert_fmt1!("%.1f", 123123123123123.0, "123123123123123.0");
assert_fmt1!("%g", 999999999.0, "1e+09");
assert_fmt1!("%.3e", 999999999.75, "1.000e+09");
assert_fmt!("%f", 1234f64 => "1234.000000");
assert_fmt!("%.5f", 1234f64 => "1234.00000");
assert_fmt!("%.*f", 6, 1234.56f64 => "1234.560000");
assert_fmt!("%f", -46.38 => "-46.380000");
assert_fmt!("%012.3f", 1.2 => "00000001.200");
assert_fmt!("%012.3e", 1.7 => "0001.700e+00");
assert_fmt!("%e", 1e300 => "1.000000e+300");
assert_fmt!("%012.3g%%!", 2.6 => "0000000002.6%!");
assert_fmt!("%012.5G", -2.69 => "-00000002.69");
assert_fmt!("%+7.4f", 42.785 => "+42.7850");
assert_fmt!("{}% 7.4E", 493.12 => "{} 4.9312E+02");
assert_fmt!("% 7.4E", -120.3 => "-1.2030E+02");
assert_fmt!("%-10F", f64::INFINITY => "INF ");
assert_fmt!("%+010F", f64::INFINITY => " +INF");
assert_fmt!("% f", f64::NAN => " nan");
assert_fmt!("%+f", f64::NAN => "+nan");
assert_fmt!("%.1f", 999.99 => "1000.0");
assert_fmt!("%.1f", 9.99 => "10.0");
assert_fmt!("%.1e", 9.99 => "1.0e+01");
assert_fmt!("%.2f", 9.99 => "9.99");
assert_fmt!("%.2e", 9.99 => "9.99e+00");
assert_fmt!("%.3f", 9.99 => "9.990");
assert_fmt!("%.3e", 9.99 => "9.990e+00");
assert_fmt!("%.1g", 9.99 => "1e+01");
assert_fmt!("%.1G", 9.99 => "1E+01");
assert_fmt!("%.1f", 2.99 => "3.0");
assert_fmt!("%.1e", 2.99 => "3.0e+00");
assert_fmt!("%.1g", 2.99 => "3");
assert_fmt!("%.1f", 2.599 => "2.6");
assert_fmt!("%.1e", 2.599 => "2.6e+00");
assert_fmt!("%30.15f", 1234565678.0 => " 1234565678.000000000000000");
assert_fmt!("%030.15f", 1234565678.0 => "00001234565678.000000000000000");
assert_fmt!("%05.3a", 123.456 => "0x1.eddp+6");
assert_fmt!("%05.3A", 123.456 => "0X1.EDDP+6");
// Regression test using smallest denormal.
assert_fmt!("%.0f", f64::from_bits(1) => "0");
assert_fmt!("%.1f", f64::from_bits(1) => "0.0");
// More regression tests.
assert_fmt!("%0.6f", 1e15 => "1000000000000000.000000");
assert_fmt!("%.0e", 0 => "0e+00");
}
#[test]
fn test_float_g() {
// correctness in DBL_DIG places
assert_fmt1!("%.15g", 1.23456789012345, "1.23456789012345");
// correct choice of notation for %g
assert_fmt1!("%g", 0.0001, "0.0001");
assert_fmt1!("%g", 0.00001, "1e-05");
assert_fmt1!("%g", 123456, "123456");
assert_fmt1!("%g", 1234567, "1.23457e+06");
assert_fmt1!("%.7g", 1234567, "1234567");
assert_fmt1!("%.7g", 12345678, "1.234568e+07");
assert_fmt1!("%.8g", 0.1, "0.1");
assert_fmt1!("%.9g", 0.1, "0.1");
assert_fmt1!("%.10g", 0.1, "0.1");
assert_fmt1!("%.11g", 0.1, "0.1");
// %g with precisions
assert_fmt1!("%.5g", 12345, "12345");
assert_fmt1!("%.4g", 12345, "1.234e+04");
assert_fmt1!("%.3g", 12345, "1.23e+04");
assert_fmt1!("%.2g", 12345, "1.2e+04");
assert_fmt1!("%.1g", 12345, "1e+04");
assert_fmt1!("%.5g", 0.000123456, "0.00012346");
assert_fmt1!("%.4g", 0.000123456, "0.0001235");
assert_fmt1!("%.3g", 0.000123456, "0.000123");
assert_fmt1!("%.2g", 0.000123456, "0.00012");
assert_fmt1!("%.1g", 0.000123456, "0.0001");
assert_fmt1!("%.5g", 99999, "99999");
assert_fmt1!("%.4g", 99999, "1e+05");
assert_fmt1!("%.5g", 0.00001, "1e-05");
assert_fmt1!("%.6g", 0.00001, "1e-05");
// %g with precision and alt form
assert_fmt1!("%#.5g", 12345, "12345.");
assert_fmt1!("%#.4g", 12345, "1.234e+04");
assert_fmt1!("%#.3g", 12345, "1.23e+04");
assert_fmt1!("%#.2g", 12345, "1.2e+04");
assert_fmt1!("%#.1g", 12345, "1.e+04");
assert_fmt1!("%#.5g", 0.000123456, "0.00012346");
assert_fmt1!("%#.4g", 0.000123456, "0.0001235");
assert_fmt1!("%#.3g", 0.000123456, "0.000123");
assert_fmt1!("%#.2g", 0.000123456, "0.00012");
assert_fmt1!("%#.1g", 0.000123456, "0.0001");
assert_fmt1!("%#.5g", 99999, "99999.");
assert_fmt1!("%#.4g", 99999, "1.000e+05");
assert_fmt1!("%#.5g", 0.00001, "1.0000e-05");
assert_fmt1!("%#.6g", 0.00001, "1.00000e-05");
// 'g' specifier changes meaning of precision to number of sigfigs.
// This applies both to explicit precision, and the default precision, which is 6.
assert_fmt!("%.1g", 2.599 => "3");
assert_fmt!("%g", 3.0 => "3");
assert_fmt!("%G", 3.0 => "3");
assert_fmt!("%g", 1234234.532234234 => "1.23423e+06");
assert_fmt!("%g", 23490234723.234239 => "2.34902e+10");
assert_fmt!("%G", 23490234723.234239 => "2.34902E+10");
assert_fmt!("%g", 0.0 => "0");
assert_fmt!("%G", 0.0 => "0");
}
#[test]
fn test_float_hex() {
assert_fmt1!("%.0a", 0.0, "0x0p+0");
assert_fmt1!("%.1a", 0.0, "0x0.0p+0");
assert_fmt1!("%.2a", 0.0, "0x0.00p+0");
assert_fmt1!("%.3a", 0.0, "0x0.000p+0");
// Test mixed precision and padding with left-adjust.
assert_fmt!("%-10.5a", 1.23456 => "0x1.3c0c2p+0");
assert_fmt!("%-15.3a", -123.456 => "-0x1.eddp+6 ");
assert_fmt!("%-20.1a", 0.00001234 => "0x1.ap-17 ");
assert_fmt!("%.0a", PI => "0x2p+1");
assert_fmt!("%.1a", PI => "0x1.9p+1");
assert_fmt!("%.2a", PI => "0x1.92p+1");
assert_fmt!("%.3a", PI => "0x1.922p+1");
assert_fmt!("%.4a", PI => "0x1.9220p+1");
assert_fmt!("%.5a", PI => "0x1.921fbp+1");
assert_fmt!("%.6a", PI => "0x1.921fb5p+1");
assert_fmt!("%.7a", PI => "0x1.921fb54p+1");
assert_fmt!("%.8a", PI => "0x1.921fb544p+1");
assert_fmt!("%.9a", PI => "0x1.921fb5444p+1");
assert_fmt!("%.10a", PI => "0x1.921fb54443p+1");
assert_fmt!("%.11a", PI => "0x1.921fb54442dp+1");
assert_fmt!("%.12a", PI => "0x1.921fb54442d2p+1");
assert_fmt!("%.13a", PI => "0x1.921fb54442d18p+1");
assert_fmt!("%.14a", PI => "0x1.921fb54442d180p+1");
assert_fmt!("%.15a", PI => "0x1.921fb54442d1800p+1");
assert_fmt!("%.16a", PI => "0x1.921fb54442d18000p+1");
assert_fmt!("%.17a", PI => "0x1.921fb54442d180000p+1");
assert_fmt!("%.18a", PI => "0x1.921fb54442d1800000p+1");
assert_fmt!("%.19a", PI => "0x1.921fb54442d18000000p+1");
assert_fmt!("%.20a", PI => "0x1.921fb54442d180000000p+1");
}
#[test]
fn test_prefixes() {
// Test the valid prefixes.
// Note that we generally ignore prefixes, since we know the width of the actual passed-in type.
// We don't test prefixed 'n'.
// Integer prefixes.
use Error::BadFormatString;
for spec in "diouxX".chars() {
let expected = sprintf_check!(format!("%{}", spec), 5);
for prefix in ["", "h", "hh", "l", "ll", "z", "j", "t"] {
let actual = sprintf_check!(format!("%{}{}", prefix, spec), 5);
assert_eq!(actual, expected);
}
for prefix in ["L", "B", "!"] {
sprintf_err!(format!("%{}{}", prefix, spec), 5 => BadFormatString);
}
}
// Floating prefixes.
for spec in "aAeEfFgG".chars() {
let expected = sprintf_check!(format!("%{}", spec), 5.0);
for prefix in ["", "l", "L"] {
let actual = sprintf_check!(format!("%{}{}", prefix, spec), 5.0);
assert_eq!(actual, expected);
}
for prefix in ["h", "hh", "z", "j", "t", "!"] {
sprintf_err!(format!("%{}{}", prefix, spec), 5.0 => BadFormatString);
}
}
// Character prefixes.
assert_eq!(sprintf_check!("%c", 'c'), "c");
assert_eq!(sprintf_check!("%lc", 'c'), "c");
assert_eq!(sprintf_check!("%s", "cs"), "cs");
assert_eq!(sprintf_check!("%ls", "cs"), "cs");
}
#[allow(clippy::approx_constant)]
#[test]
fn negative_precision_width() {
assert_fmt!("%*s", -10, "hello" => "hello ");
assert_fmt!("%*s", -5, "world" => "world");
assert_fmt!("%-*s", 10, "rust" => "rust ");
assert_fmt!("%.*s", -3, "example" => "example");
assert_fmt!("%*d", -8, 456 => "456 ");
assert_fmt!("%*i", -4, -789 => "-789");
assert_fmt!("%-*o", 6, 123 => "173 ");
assert_fmt!("%.*x", -2, 255 => "ff");
assert_fmt!("%-*X", 7, 255 => "FF ");
assert_fmt!("%.*u", -5, 5000 => "5000");
assert_fmt!("%*f", -12, 78.9 => "78.900000 ");
assert_fmt!("%*g", -10, 12345.678 => "12345.7 ");
assert_fmt!("%-*e", 15, 0.00012 => "1.200000e-04 ");
assert_fmt!("%-*e", -15, 0.00012 => "1.200000e-04 ");
assert_fmt!("%.*G", -2, 123.456 => "123.456");
assert_fmt!("%-*E", 14, 123456.789 => "1.234568E+05 ");
assert_fmt!("%-*E", -14, 123456.789 => "1.234568E+05 ");
assert_fmt!("%.*f", -6, 3.14159 => "3.141590");
assert_fmt!("%*.*f", -12, -6, 78.9 => "78.900000 ");
assert_fmt!("%*.*g", -10, -3, 12345.678 => "12345.7 ");
assert_fmt!("%*.*e", -15, -8, 0.00012 => "1.200000e-04 ");
assert_fmt!("%*.*E", -14, -4, 123456.789 => "1.234568E+05 ");
assert_fmt!("%*.*d", -6, -4, 2024 => "2024 ");
assert_fmt!("%*.*x", -8, -3, 255 => "ff ");
assert_fmt!("%*.*f", -10, -2, 3.14159 => "3.141590 ");
assert_fmt!("%*.*g", -12, -5, 123.456 => "123.456 ");
assert_fmt!("%*.*e", -14, -3, 0.000789 => "7.890000e-04 ");
assert_fmt!("%*.*E", -16, -5, 98765.4321 => "9.876543E+04 ");
}
#[test]
fn test_precision_overflow() {
// Disallow precisions larger than i32::MAX.
sprintf_err!("%.*g", usize::MAX, 1.0 => Error::Overflow);
sprintf_err!("%.2147483648g", usize::MAX, 1.0 => Error::Overflow);
sprintf_err!("%.*g", i32::MAX as usize + 1, 1.0 => Error::Overflow);
sprintf_err!("%.2147483648g", i32::MAX as usize + 1, 1.0 => Error::Overflow);
}
#[test]
fn test_huge_precision_g() {
let f = 1e-100;
assert_eq!(sprintf_count!("%.2147483647g", f), 288);
assert_eq!(sprintf_count!("%.*g", i32::MAX, f), 288);
assert_fmt!("%.*g", i32::MAX, 2.0_f64.powi(-4) => "0.0625");
sprintf_err!("%.*g", usize::MAX, f => Error::Overflow);
sprintf_err!("%.2147483648g", f => Error::Overflow);
}
#[test]
fn test_errors() {
use Error::*;
sprintf_err!("%", => BadFormatString);
sprintf_err!("%1", => BadFormatString);
sprintf_err!("%%%k", => BadFormatString);
sprintf_err!("%B", => BadFormatString);
sprintf_err!("%lC", 'q' => BadFormatString);
sprintf_err!("%lS", 'q' => BadFormatString);
sprintf_err!("%d", => MissingArg);
sprintf_err!("%d %u", 1 => MissingArg);
sprintf_err!("%*d", 5 => MissingArg);
sprintf_err!("%.*d", 5 => MissingArg);
sprintf_err!("%%", 1 => ExtraArg);
sprintf_err!("%d %d", 1, 2, 3 => ExtraArg);
sprintf_err!("%d", "abc" => BadArgType);
sprintf_err!("%s", 5 => BadArgType);
sprintf_err!("%*d", "s", 5 => BadArgType);
sprintf_err!("%.*d", "s", 5 => BadArgType);
sprintf_err!("%18446744073709551616d", 5 => Overflow);
sprintf_err!("%.18446744073709551616d", 5 => Overflow);
// We allow passing an int for a float, but not a float for an int.
assert_fmt!("%f", 3 => "3.000000");
sprintf_err!("%d", 3.0 => BadArgType);
// We allow passing an int for a char, reporting "overflow" for ints
// which cannot be converted to char (treating surrogates as "overflow").
assert_fmt!("%c", 0 => "\0");
assert_fmt!("%c", 'Z' as u32 => "Z");
sprintf_err!("%c", 5.0 => BadArgType);
sprintf_err!("%c", -1 => Overflow);
sprintf_err!("%c", u64::MAX => Overflow);
sprintf_err!("%c", 0xD800 => Overflow);
sprintf_err!("%c", 0xD8FF => Overflow);
// Apostrophe only works for d,u,i,f,F
sprintf_err!("%'c", 0 => BadFormatString);
sprintf_err!("%'o", 0 => BadFormatString);
sprintf_err!("%'x", 0 => BadFormatString);
sprintf_err!("%'X", 0 => BadFormatString);
sprintf_err!("%'n", 0 => BadFormatString);
sprintf_err!("%'a", 0 => BadFormatString);
sprintf_err!("%'A", 0 => BadFormatString);
sprintf_err!("%'e", 0 => BadFormatString);
sprintf_err!("%'E", 0 => BadFormatString);
sprintf_err!("%'g", 0 => BadFormatString);
sprintf_err!("%'G", 0 => BadFormatString);
}
#[test]
fn test_locale() {
fn test_printf_loc<'a>(expected: &str, locale: &Locale, format: &str, arg: impl ToArg<'a>) {
let mut target = String::new();
let format_chars: Vec<char> = format.chars().collect();
let len = sprintf_locale(&mut target, &format_chars, locale, &mut [arg.to_arg()])
.expect("printf failed");
assert_eq!(len, target.len());
assert_eq!(target, expected);
}
let mut locale = C_LOCALE;
locale.decimal_point = ',';
locale.thousands_sep = Some('!');
locale.grouping = [3, 1, 0, 0];
test_printf_loc("-46,380000", &locale, "%f", -46.38);
test_printf_loc("00000001,200", &locale, "%012.3f", 1.2);
test_printf_loc("1234", &locale, "%d", 1234);
test_printf_loc("0x1,9p+3", &locale, "%a", 12.5);
test_printf_loc("12345!6!789", &locale, "%'d", 123456789);
test_printf_loc("123!4!567", &locale, "%'d", 1234567);
test_printf_loc("214748!3!647", &locale, "%'u", 2147483647);
test_printf_loc("-123!4!567", &locale, "%'i", -1234567);
test_printf_loc("-123!4!567,890000", &locale, "%'f", -1234567.89);
test_printf_loc("123!4!567,8899999999", &locale, "%'.10f", 1234567.89);
test_printf_loc("12!3!456,789", &locale, "%'.3F", 123456.789);
test_printf_loc("00000000001!234", &locale, "%'015d", 1234);
test_printf_loc("1!2!345", &locale, "%'7d", 12345);
test_printf_loc(" 1!2!345", &locale, "%'8d", 12345);
test_printf_loc("+1!2!345", &locale, "%'+d", 12345);
// Thousands seps count as width, and so remove some leading zeros.
// Padding does NOT use thousands sep.
test_printf_loc("0001234567", &EN_US_LOCALE, "%010d", 1234567);
test_printf_loc("01,234,567", &EN_US_LOCALE, "%'010d", 1234567);
test_printf_loc(
"000000000000000001,222,333,444",
&EN_US_LOCALE,
"%'0.30d",
1222333444,
);
}
#[test]
#[ignore]
fn test_float_hex_prec() {
// Check that libc and our hex float formatting agree for each precision.
// Note that our hex float formatting rounds according to the rounding mode,
// while libc may not; as a result we may differ in the last digit. So this
// requires manual comparison.
let mut c_storage = [0u8; 256];
let c_storage_ptr = c_storage.as_mut_ptr() as *mut i8;
let mut rust_str = String::with_capacity(256);
let c_fmt = b"%.*a\0".as_ptr() as *const i8;
let mut failed = false;
for sign in [1.0, -1.0].into_iter() {
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E].into_iter() {
v *= sign;
for preci in 1..=200_i32 {
rust_str.clear();
crate::sprintf!(=> &mut rust_str, utf32str!("%.*a"), preci, v);
let printf_str = unsafe {
let len = libc::snprintf(c_storage_ptr, c_storage.len(), c_fmt, preci, v);
assert!(len >= 0);
let sl = std::slice::from_raw_parts(c_storage_ptr as *const u8, len as usize);
std::str::from_utf8(sl).unwrap()
};
if rust_str != printf_str {
println!(
"Our printf and libc disagree on hex formatting of float: {v}
with precision: {preci}
our output: <{rust_str}>
libc output: <{printf_str}>"
);
failed = true;
}
}
}
}
assert!(!failed);
}
fn test_exhaustive(rust_fmt: &Utf32Str, c_fmt: *const i8) {
// "There's only 4 billion floats so test them all."
// This tests a format string expected to be of the form "%.*g" or "%.*e".
// That is, it takes a precision and a double.
println!("Testing {}", rust_fmt);
let mut rust_str = String::with_capacity(128);
let mut c_storage = [0u8; 128];
let c_storage_ptr = c_storage.as_mut_ptr() as *mut i8;
for i in 0..=u32::MAX {
if i % 1000000 == 0 {
println!("{:.2}%", (i as f64) / (u32::MAX as f64) * 100.0);
}
let f = f32::from_bits(i);
let ff = f as f64;
for preci in 0..=10 {
rust_str.clear();
crate::sprintf!(=> &mut rust_str, rust_fmt, preci, ff);
let printf_str = unsafe {
let len = libc::snprintf(c_storage_ptr, c_storage.len(), c_fmt, preci, ff);
assert!(len >= 0);
let sl = std::slice::from_raw_parts(c_storage_ptr as *const u8, len as usize);
std::str::from_utf8(sl).unwrap()
};
if rust_str != printf_str {
println!(
"Rust and libc disagree on formatting float {i:x}: {ff}\n
with precision: {preci}
format string: {rust_fmt}
rust output: <{rust_str}>
libc output: <{printf_str}>"
);
assert_eq!(rust_str, printf_str);
}
}
}
}
#[test]
#[ignore]
fn test_float_g_exhaustive() {
// To run: cargo test test_float_g_exhaustive --release -- --ignored --nocapture
test_exhaustive(
widestring::utf32str!("%.*g"),
b"%.*g\0".as_ptr() as *const i8,
);
}
#[test]
#[ignore]
fn test_float_e_exhaustive() {
// To run: cargo test test_float_e_exhaustive --release -- --ignored --nocapture
test_exhaustive(
widestring::utf32str!("%.*e"),
b"%.*e\0".as_ptr() as *const i8,
);
}
#[test]
#[ignore]
fn test_float_f_exhaustive() {
// To run: cargo test test_float_f_exhaustive --release -- --ignored --nocapture
test_exhaustive(
widestring::utf32str!("%.*f"),
b"%.*f\0".as_ptr() as *const i8,
);
}