mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-05 07:27:46 +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.
|
//! parsing errors occur during writing.
|
||||||
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
||||||
|
|
||||||
// mod num_format;
|
|
||||||
mod spec;
|
mod spec;
|
||||||
|
mod num_format;
|
||||||
|
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use std::{
|
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
|
// 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};
|
use std::{fmt::Display, io::Write};
|
||||||
|
|
||||||
pub enum Spec {
|
pub enum Spec {
|
||||||
|
@ -256,7 +259,7 @@ impl Spec {
|
||||||
|
|
||||||
pub fn write<'a>(
|
pub fn write<'a>(
|
||||||
&self,
|
&self,
|
||||||
mut writer: impl Write,
|
writer: impl Write,
|
||||||
mut args: impl Iterator<Item = &'a FormatArgument>,
|
mut args: impl Iterator<Item = &'a FormatArgument>,
|
||||||
) -> Result<(), FormatError> {
|
) -> Result<(), FormatError> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -288,21 +291,11 @@ impl Spec {
|
||||||
return Err(FormatError::InvalidArgument(arg.clone()));
|
return Err(FormatError::InvalidArgument(arg.clone()));
|
||||||
};
|
};
|
||||||
|
|
||||||
if *i >= 0 {
|
num_format::SignedInt {
|
||||||
match positive_sign {
|
width,
|
||||||
PositiveSign::None => Ok(()),
|
positive_sign,
|
||||||
PositiveSign::Plus => write!(writer, "+"),
|
alignment,
|
||||||
PositiveSign::Space => write!(writer, " "),
|
}.fmt(writer, *i)
|
||||||
}
|
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
&Spec::UnsignedInt {
|
&Spec::UnsignedInt {
|
||||||
variant,
|
variant,
|
||||||
|
@ -312,34 +305,16 @@ impl Spec {
|
||||||
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
|
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
|
||||||
|
|
||||||
let arg = next_arg(args)?;
|
let arg = next_arg(args)?;
|
||||||
let FormatArgument::SignedInt(i) = arg else {
|
let FormatArgument::UnsignedInt(i) = arg else {
|
||||||
return Err(FormatError::InvalidArgument(arg.clone()));
|
return Err(FormatError::InvalidArgument(arg.clone()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = match variant {
|
num_format::UnsignedInt {
|
||||||
UnsignedIntVariant::Decimal => format!("{i}"),
|
variant,
|
||||||
UnsignedIntVariant::Octal(Prefix::No) => format!("{i:o}"),
|
width,
|
||||||
UnsignedIntVariant::Octal(Prefix::Yes) => format!("{i:#o}"),
|
alignment,
|
||||||
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$}"),
|
|
||||||
}
|
}
|
||||||
.map_err(FormatError::IoError)
|
.fmt(writer, *i)
|
||||||
}
|
}
|
||||||
&Spec::Float {
|
&Spec::Float {
|
||||||
variant,
|
variant,
|
||||||
|
@ -358,148 +333,21 @@ impl Spec {
|
||||||
return Err(FormatError::InvalidArgument(arg.clone()));
|
return Err(FormatError::InvalidArgument(arg.clone()));
|
||||||
};
|
};
|
||||||
|
|
||||||
if f.is_sign_positive() {
|
num_format::Float {
|
||||||
match positive_sign {
|
variant,
|
||||||
PositiveSign::None => Ok(()),
|
case,
|
||||||
PositiveSign::Plus => write!(writer, "+"),
|
force_decimal,
|
||||||
PositiveSign::Space => write!(writer, " "),
|
width,
|
||||||
}
|
positive_sign,
|
||||||
.map_err(FormatError::IoError)?;
|
alignment,
|
||||||
|
precision,
|
||||||
}
|
}
|
||||||
|
.fmt(writer, *f)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>(
|
fn resolve_asterisk<'a>(
|
||||||
option: Option<CanAsterisk<usize>>,
|
option: Option<CanAsterisk<usize>>,
|
||||||
args: impl Iterator<Item = &'a FormatArgument>,
|
args: impl Iterator<Item = &'a FormatArgument>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue