mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-03 06:27:45 +00:00
printf: move number formatting to separate module
This commit is contained in:
parent
bdfe5f1cc6
commit
198f7c7f26
3 changed files with 258 additions and 178 deletions
|
@ -10,8 +10,8 @@
|
|||
//! parsing errors occur during writing.
|
||||
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
||||
|
||||
// mod num_format;
|
||||
mod spec;
|
||||
mod num_format;
|
||||
|
||||
use spec::Spec;
|
||||
use std::{
|
||||
|
|
232
src/uucore/src/lib/features/format/num_format.rs
Normal file
232
src/uucore/src/lib/features/format/num_format.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
use std::io::Write;
|
||||
|
||||
use super::{
|
||||
spec::{
|
||||
Case, FloatVariant, ForceDecimal, NumberAlignment, PositiveSign, Prefix, UnsignedIntVariant,
|
||||
},
|
||||
FormatError,
|
||||
};
|
||||
|
||||
pub trait Formatter {
|
||||
type Input;
|
||||
fn fmt(&self, writer: impl Write, x: Self::Input) -> Result<(), FormatError>;
|
||||
}
|
||||
|
||||
pub struct SignedInt {
|
||||
pub width: usize,
|
||||
pub positive_sign: PositiveSign,
|
||||
pub alignment: NumberAlignment,
|
||||
}
|
||||
|
||||
impl Formatter for SignedInt {
|
||||
type Input = i64;
|
||||
|
||||
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> {
|
||||
if x >= 0 {
|
||||
match self.positive_sign {
|
||||
PositiveSign::None => Ok(()),
|
||||
PositiveSign::Plus => write!(writer, "+"),
|
||||
PositiveSign::Space => write!(writer, " "),
|
||||
}
|
||||
.map_err(FormatError::IoError)?;
|
||||
}
|
||||
|
||||
match self.alignment {
|
||||
NumberAlignment::Left => write!(writer, "{x:<width$}", width = self.width),
|
||||
NumberAlignment::RightSpace => write!(writer, "{x:>width$}", width = self.width),
|
||||
NumberAlignment::RightZero => write!(writer, "{x:0>width$}", width = self.width),
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnsignedInt {
|
||||
pub variant: UnsignedIntVariant,
|
||||
pub width: usize,
|
||||
pub alignment: NumberAlignment,
|
||||
}
|
||||
|
||||
impl Formatter for UnsignedInt {
|
||||
type Input = u64;
|
||||
|
||||
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> {
|
||||
let s = match self.variant {
|
||||
UnsignedIntVariant::Decimal => format!("{x}"),
|
||||
UnsignedIntVariant::Octal(Prefix::No) => format!("{x:o}"),
|
||||
UnsignedIntVariant::Octal(Prefix::Yes) => format!("{x:#o}"),
|
||||
UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::No) => {
|
||||
format!("{x:x}")
|
||||
}
|
||||
UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::Yes) => {
|
||||
format!("{x:#x}")
|
||||
}
|
||||
UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::No) => {
|
||||
format!("{x:X}")
|
||||
}
|
||||
UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::Yes) => {
|
||||
format!("{x:#X}")
|
||||
}
|
||||
};
|
||||
|
||||
match self.alignment {
|
||||
NumberAlignment::Left => write!(writer, "{s:<width$}", width = self.width),
|
||||
NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width),
|
||||
NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width),
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Float {
|
||||
pub variant: FloatVariant,
|
||||
pub case: Case,
|
||||
pub force_decimal: ForceDecimal,
|
||||
pub width: usize,
|
||||
pub positive_sign: PositiveSign,
|
||||
pub alignment: NumberAlignment,
|
||||
pub precision: usize,
|
||||
}
|
||||
|
||||
impl Formatter for Float {
|
||||
type Input = f64;
|
||||
|
||||
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> {
|
||||
if x.is_sign_positive() {
|
||||
match self.positive_sign {
|
||||
PositiveSign::None => Ok(()),
|
||||
PositiveSign::Plus => write!(writer, "+"),
|
||||
PositiveSign::Space => write!(writer, " "),
|
||||
}
|
||||
.map_err(FormatError::IoError)?;
|
||||
}
|
||||
|
||||
let s = match self.variant {
|
||||
FloatVariant::Decimal => {
|
||||
format_float_decimal(x, self.precision, self.case, self.force_decimal)
|
||||
}
|
||||
FloatVariant::Scientific => {
|
||||
format_float_scientific(x, self.precision, self.case, self.force_decimal)
|
||||
}
|
||||
FloatVariant::Shortest => {
|
||||
format_float_shortest(x, self.precision, self.case, self.force_decimal)
|
||||
}
|
||||
FloatVariant::Hexadecimal => {
|
||||
format_float_hexadecimal(x, self.precision, self.case, self.force_decimal)
|
||||
}
|
||||
};
|
||||
|
||||
match self.alignment {
|
||||
NumberAlignment::Left => write!(writer, "{s:<width$}", width = self.width),
|
||||
NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width),
|
||||
NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width),
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_float_nonfinite(f: f64, case: Case) -> String {
|
||||
debug_assert!(!f.is_finite());
|
||||
let mut s = format!("{f}");
|
||||
if case == Case::Uppercase {
|
||||
s.make_ascii_uppercase();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
fn format_float_decimal(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
if !f.is_finite() {
|
||||
return format_float_nonfinite(f, case);
|
||||
}
|
||||
|
||||
if precision == 0 && force_decimal == ForceDecimal::Yes {
|
||||
format!("{f:.0}.")
|
||||
} else {
|
||||
format!("{f:.*}", precision)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_float_scientific(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
// If the float is NaN, -Nan, Inf or -Inf, format like any other float
|
||||
if !f.is_finite() {
|
||||
return format_float_nonfinite(f, case);
|
||||
}
|
||||
|
||||
let exponent: i32 = f.log10().floor() as i32;
|
||||
let normalized = f / 10.0_f64.powi(exponent);
|
||||
|
||||
let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal {
|
||||
"."
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let exp_char = match case {
|
||||
Case::Lowercase => 'e',
|
||||
Case::Uppercase => 'E',
|
||||
};
|
||||
|
||||
format!(
|
||||
"{normalized:.*}{additional_dot}{exp_char}{exponent:+03}",
|
||||
precision
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: This could be optimized. It's not terribly important though.
|
||||
fn format_float_shortest(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
let a = format_float_decimal(f, precision, case, force_decimal);
|
||||
let b = format_float_scientific(f, precision, case, force_decimal);
|
||||
|
||||
if a.len() > b.len() {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
fn format_float_hexadecimal(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
if !f.is_finite() {
|
||||
return format_float_nonfinite(f, case);
|
||||
}
|
||||
|
||||
let (first_digit, mantissa, exponent) = if f == 0.0 {
|
||||
(0, 0, 0)
|
||||
} else {
|
||||
let bits = f.to_bits();
|
||||
let exponent_bits = ((bits >> 52) & 0x7fff) as i64;
|
||||
let exponent = exponent_bits - 1023;
|
||||
let mantissa = bits & 0xf_ffff_ffff_ffff;
|
||||
(1, mantissa, exponent)
|
||||
};
|
||||
|
||||
let mut s = match (precision, force_decimal) {
|
||||
(0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"),
|
||||
(0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"),
|
||||
_ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"),
|
||||
};
|
||||
|
||||
if case == Case::Uppercase {
|
||||
s.make_ascii_uppercase();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
||||
|
||||
use super::{FormatArgument, FormatError};
|
||||
use super::{
|
||||
num_format::{self, Formatter},
|
||||
FormatArgument, FormatError,
|
||||
};
|
||||
use std::{fmt::Display, io::Write};
|
||||
|
||||
pub enum Spec {
|
||||
|
@ -256,7 +259,7 @@ impl Spec {
|
|||
|
||||
pub fn write<'a>(
|
||||
&self,
|
||||
mut writer: impl Write,
|
||||
writer: impl Write,
|
||||
mut args: impl Iterator<Item = &'a FormatArgument>,
|
||||
) -> Result<(), FormatError> {
|
||||
match self {
|
||||
|
@ -288,21 +291,11 @@ impl Spec {
|
|||
return Err(FormatError::InvalidArgument(arg.clone()));
|
||||
};
|
||||
|
||||
if *i >= 0 {
|
||||
match positive_sign {
|
||||
PositiveSign::None => Ok(()),
|
||||
PositiveSign::Plus => write!(writer, "+"),
|
||||
PositiveSign::Space => write!(writer, " "),
|
||||
}
|
||||
.map_err(FormatError::IoError)?;
|
||||
}
|
||||
|
||||
match alignment {
|
||||
NumberAlignment::Left => write!(writer, "{i:<width$}"),
|
||||
NumberAlignment::RightSpace => write!(writer, "{i:>width$}"),
|
||||
NumberAlignment::RightZero => write!(writer, "{i:0>width$}"),
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
num_format::SignedInt {
|
||||
width,
|
||||
positive_sign,
|
||||
alignment,
|
||||
}.fmt(writer, *i)
|
||||
}
|
||||
&Spec::UnsignedInt {
|
||||
variant,
|
||||
|
@ -312,34 +305,16 @@ impl Spec {
|
|||
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
|
||||
|
||||
let arg = next_arg(args)?;
|
||||
let FormatArgument::SignedInt(i) = arg else {
|
||||
let FormatArgument::UnsignedInt(i) = arg else {
|
||||
return Err(FormatError::InvalidArgument(arg.clone()));
|
||||
};
|
||||
|
||||
let s = match variant {
|
||||
UnsignedIntVariant::Decimal => format!("{i}"),
|
||||
UnsignedIntVariant::Octal(Prefix::No) => format!("{i:o}"),
|
||||
UnsignedIntVariant::Octal(Prefix::Yes) => format!("{i:#o}"),
|
||||
UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::No) => {
|
||||
format!("{i:x}")
|
||||
}
|
||||
UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::Yes) => {
|
||||
format!("{i:#x}")
|
||||
}
|
||||
UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::No) => {
|
||||
format!("{i:X}")
|
||||
}
|
||||
UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::Yes) => {
|
||||
format!("{i:#X}")
|
||||
}
|
||||
};
|
||||
|
||||
match alignment {
|
||||
NumberAlignment::Left => write!(writer, "{s:<width$}"),
|
||||
NumberAlignment::RightSpace => write!(writer, "{s:>width$}"),
|
||||
NumberAlignment::RightZero => write!(writer, "{s:0>width$}"),
|
||||
num_format::UnsignedInt {
|
||||
variant,
|
||||
width,
|
||||
alignment,
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
.fmt(writer, *i)
|
||||
}
|
||||
&Spec::Float {
|
||||
variant,
|
||||
|
@ -358,148 +333,21 @@ impl Spec {
|
|||
return Err(FormatError::InvalidArgument(arg.clone()));
|
||||
};
|
||||
|
||||
if f.is_sign_positive() {
|
||||
match positive_sign {
|
||||
PositiveSign::None => Ok(()),
|
||||
PositiveSign::Plus => write!(writer, "+"),
|
||||
PositiveSign::Space => write!(writer, " "),
|
||||
}
|
||||
.map_err(FormatError::IoError)?;
|
||||
num_format::Float {
|
||||
variant,
|
||||
case,
|
||||
force_decimal,
|
||||
width,
|
||||
positive_sign,
|
||||
alignment,
|
||||
precision,
|
||||
}
|
||||
|
||||
let s = match variant {
|
||||
FloatVariant::Decimal => {
|
||||
format_float_decimal(*f, precision, case, force_decimal)
|
||||
}
|
||||
FloatVariant::Scientific => {
|
||||
format_float_scientific(*f, precision, case, force_decimal)
|
||||
}
|
||||
FloatVariant::Shortest => {
|
||||
format_float_shortest(*f, precision, case, force_decimal)
|
||||
}
|
||||
FloatVariant::Hexadecimal => {
|
||||
format_float_hexadecimal(*f, precision, case, force_decimal)
|
||||
}
|
||||
};
|
||||
|
||||
match alignment {
|
||||
NumberAlignment::Left => write!(writer, "{s:<width$}"),
|
||||
NumberAlignment::RightSpace => write!(writer, "{s:>width$}"),
|
||||
NumberAlignment::RightZero => write!(writer, "{s:0>width$}"),
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
.fmt(writer, *f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_float_nonfinite(f: f64, case: Case) -> String {
|
||||
debug_assert!(!f.is_finite());
|
||||
let mut s = format!("{f}");
|
||||
if case == Case::Uppercase {
|
||||
s.make_ascii_uppercase();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
fn format_float_decimal(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
if !f.is_finite() {
|
||||
return format_float_nonfinite(f, case);
|
||||
}
|
||||
|
||||
if precision == 0 && force_decimal == ForceDecimal::Yes {
|
||||
format!("{f:.0}.")
|
||||
} else {
|
||||
format!("{f:.*}", precision)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_float_scientific(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
// If the float is NaN, -Nan, Inf or -Inf, format like any other float
|
||||
if !f.is_finite() {
|
||||
return format_float_nonfinite(f, case);
|
||||
}
|
||||
|
||||
let exponent: i32 = f.log10().floor() as i32;
|
||||
let normalized = f / 10.0_f64.powi(exponent);
|
||||
|
||||
let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal {
|
||||
"."
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let exp_char = match case {
|
||||
Case::Lowercase => 'e',
|
||||
Case::Uppercase => 'E',
|
||||
};
|
||||
|
||||
format!(
|
||||
"{normalized:.*}{additional_dot}{exp_char}{exponent:+03}",
|
||||
precision
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: This could be optimized. It's not terribly important though.
|
||||
fn format_float_shortest(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
let a = format_float_decimal(f, precision, case, force_decimal);
|
||||
let b = format_float_scientific(f, precision, case, force_decimal);
|
||||
|
||||
if a.len() > b.len() {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
fn format_float_hexadecimal(
|
||||
f: f64,
|
||||
precision: usize,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
) -> String {
|
||||
if !f.is_finite() {
|
||||
return format_float_nonfinite(f, case);
|
||||
}
|
||||
|
||||
let (first_digit, mantissa, exponent) = if f == 0.0 {
|
||||
(0, 0, 0)
|
||||
} else {
|
||||
let bits = f.to_bits();
|
||||
let exponent_bits = ((bits >> 52) & 0x7fff) as i64;
|
||||
let exponent = exponent_bits - 1023;
|
||||
let mantissa = bits & 0xf_ffff_ffff_ffff;
|
||||
(1, mantissa, exponent)
|
||||
};
|
||||
|
||||
let mut s = match (precision, force_decimal) {
|
||||
(0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"),
|
||||
(0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"),
|
||||
_ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"),
|
||||
};
|
||||
|
||||
if case == Case::Uppercase {
|
||||
s.make_ascii_uppercase();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
fn resolve_asterisk<'a>(
|
||||
option: Option<CanAsterisk<usize>>,
|
||||
args: impl Iterator<Item = &'a FormatArgument>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue