use super::ExtendedBigDecimal;
use super::FormatError;
use super::spec::CanAsterisk;
use super::spec::Spec;
use bigdecimal::BigDecimal;
use bigdecimal::num_bigint::ToBigInt;
use num_traits::Signed;
use num_traits::Zero;
use std::cmp::min;
use std::io::Write;
pub trait Formatter<T> {
fn fmt(&self, writer: impl Write, x: T) -> std::io::Result<()>;
fn try_from_spec(s: Spec) -> Result<Self, FormatError>
where
Self: Sized;
}
#[derive(Clone, Copy, Debug)]
pub enum UnsignedIntVariant {
Decimal,
Octal(Prefix),
Hexadecimal(Case, Prefix),
}
#[derive(Clone, Copy, Debug)]
pub enum FloatVariant {
Decimal,
Scientific,
Shortest,
Hexadecimal,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Case {
Lowercase,
Uppercase,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Prefix {
No,
Yes,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ForceDecimal {
No,
Yes,
}
#[derive(Clone, Copy, Debug)]
pub enum PositiveSign {
None,
Plus,
Space,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NumberAlignment {
Left,
RightSpace,
RightZero,
}
pub struct SignedInt {
pub width: usize,
pub precision: usize,
pub positive_sign: PositiveSign,
pub alignment: NumberAlignment,
}
impl Formatter<i64> for SignedInt {
fn fmt(&self, writer: impl Write, x: i64) -> std::io::Result<()> {
let abs = (x as i128).abs();
let s = if self.precision > 0 {
format!("{abs:0>width$}", width = self.precision)
} else {
abs.to_string()
};
let sign_indicator = get_sign_indicator(self.positive_sign, x.is_negative());
write_output(writer, sign_indicator, s, self.width, self.alignment)
}
fn try_from_spec(s: Spec) -> Result<Self, FormatError> {
let Spec::SignedInt {
width,
precision,
positive_sign,
alignment,
position: _position,
} = s
else {
return Err(FormatError::WrongSpecType);
};
let width = match width {
Some(CanAsterisk::Fixed(x)) => x,
None => 0,
Some(CanAsterisk::Asterisk(_)) => return Err(FormatError::WrongSpecType),
};
let precision = match precision {
Some(CanAsterisk::Fixed(x)) => x,
None => 0,
Some(CanAsterisk::Asterisk(_)) => return Err(FormatError::WrongSpecType),
};
Ok(Self {
width,
precision,
positive_sign,
alignment,
})
}
}
pub struct UnsignedInt {
pub variant: UnsignedIntVariant,
pub width: usize,
pub precision: usize,
pub alignment: NumberAlignment,
}
impl Formatter<u64> for UnsignedInt {
fn fmt(&self, writer: impl Write, x: u64) -> std::io::Result<()> {
let mut s = match self.variant {
UnsignedIntVariant::Decimal => format!("{x}"),
UnsignedIntVariant::Octal(_) => format!("{x:o}"),
UnsignedIntVariant::Hexadecimal(case, _) => match case {
Case::Lowercase => format!("{x:x}"),
Case::Uppercase => format!("{x:X}"),
},
};
let prefix = match (x, self.variant) {
(1.., UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::Yes)) => "0x",
(1.., UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::Yes)) => "0X",
(1.., UnsignedIntVariant::Octal(Prefix::Yes)) if s.len() >= self.precision => "0",
_ => "",
};
s = format!("{prefix}{s:0>width$}", width = self.precision);
write_output(writer, String::new(), s, self.width, self.alignment)
}
fn try_from_spec(s: Spec) -> Result<Self, FormatError> {
let s = if let Spec::SignedInt {
width,
precision,
positive_sign: PositiveSign::None,
alignment,
position,
} = s
{
Spec::UnsignedInt {
variant: UnsignedIntVariant::Decimal,
width,
precision,
alignment,
position,
}
} else {
s
};
let Spec::UnsignedInt {
variant,
width,
precision,
alignment,
position: _position,
} = s
else {
return Err(FormatError::WrongSpecType);
};
let width = match width {
Some(CanAsterisk::Fixed(x)) => x,
None => 0,
Some(CanAsterisk::Asterisk(_)) => return Err(FormatError::WrongSpecType),
};
let precision = match precision {
Some(CanAsterisk::Fixed(x)) => x,
None => 0,
Some(CanAsterisk::Asterisk(_)) => return Err(FormatError::WrongSpecType),
};
Ok(Self {
variant,
width,
precision,
alignment,
})
}
}
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: Option<usize>,
}
impl Default for Float {
fn default() -> Self {
Self {
variant: FloatVariant::Decimal,
case: Case::Lowercase,
force_decimal: ForceDecimal::No,
width: 0,
positive_sign: PositiveSign::None,
alignment: NumberAlignment::Left,
precision: None,
}
}
}
impl Formatter<&ExtendedBigDecimal> for Float {
fn fmt(&self, writer: impl Write, e: &ExtendedBigDecimal) -> std::io::Result<()> {
let (abs, negative) = match e {
ExtendedBigDecimal::BigDecimal(bd) => {
(ExtendedBigDecimal::BigDecimal(bd.abs()), bd.is_negative())
}
ExtendedBigDecimal::MinusZero => (ExtendedBigDecimal::zero(), true),
ExtendedBigDecimal::Infinity => (ExtendedBigDecimal::Infinity, false),
ExtendedBigDecimal::MinusInfinity => (ExtendedBigDecimal::Infinity, true),
ExtendedBigDecimal::Nan => (ExtendedBigDecimal::Nan, false),
ExtendedBigDecimal::MinusNan => (ExtendedBigDecimal::Nan, true),
};
let mut alignment = self.alignment;
let s = if let ExtendedBigDecimal::BigDecimal(bd) = abs {
match self.variant {
FloatVariant::Decimal => {
format_float_decimal(&bd, self.precision, self.force_decimal)
}
FloatVariant::Scientific => {
format_float_scientific(&bd, self.precision, self.case, self.force_decimal)
}
FloatVariant::Shortest => {
format_float_shortest(&bd, self.precision, self.case, self.force_decimal)
}
FloatVariant::Hexadecimal => {
format_float_hexadecimal(&bd, self.precision, self.case, self.force_decimal)
}
}
} else {
if alignment == NumberAlignment::RightZero {
alignment = NumberAlignment::RightSpace;
}
format_float_non_finite(&abs, self.case)
};
let sign_indicator = get_sign_indicator(self.positive_sign, negative);
write_output(writer, sign_indicator, s, self.width, alignment)
}
fn try_from_spec(s: Spec) -> Result<Self, FormatError>
where
Self: Sized,
{
let Spec::Float {
variant,
case,
force_decimal,
width,
positive_sign,
alignment,
precision,
position: _position,
} = s
else {
return Err(FormatError::WrongSpecType);
};
let width = match width {
Some(CanAsterisk::Fixed(x)) => x,
None => 0,
Some(CanAsterisk::Asterisk(_)) => return Err(FormatError::WrongSpecType),
};
let precision = match precision {
Some(CanAsterisk::Fixed(x)) => Some(x),
None => None,
Some(CanAsterisk::Asterisk(_)) => return Err(FormatError::WrongSpecType),
};
Ok(Self {
variant,
case,
force_decimal,
width,
positive_sign,
alignment,
precision,
})
}
}
fn get_sign_indicator(sign: PositiveSign, negative: bool) -> String {
if negative {
String::from("-")
} else {
match sign {
PositiveSign::None => String::new(),
PositiveSign::Plus => String::from("+"),
PositiveSign::Space => String::from(" "),
}
}
}
fn format_float_non_finite(e: &ExtendedBigDecimal, case: Case) -> String {
let mut s = match e {
ExtendedBigDecimal::Infinity => String::from("inf"),
ExtendedBigDecimal::Nan => String::from("nan"),
_ => {
debug_assert!(false);
String::from("INVALID")
}
};
if case == Case::Uppercase {
s.make_ascii_uppercase();
}
s
}
fn format_float_decimal(
bd: &BigDecimal,
precision: Option<usize>,
force_decimal: ForceDecimal,
) -> String {
debug_assert!(!bd.is_negative());
let precision = precision.unwrap_or(6);
if precision == 0 {
let (bi, scale) = bd.as_bigint_and_scale();
if scale == 0 && force_decimal != ForceDecimal::Yes {
return bi.to_str_radix(10);
} else if force_decimal == ForceDecimal::Yes {
return format!("{bd:.0}.");
}
}
format!("{bd:.precision$}")
}
fn bd_to_string_exp_with_prec(bd: &BigDecimal, precision: usize) -> (String, i64) {
let bd_round = bd.with_prec(precision as u64);
let (frac, mut p) = bd_round.as_bigint_and_exponent();
let mut digits = frac.to_str_radix(10);
if digits.len() == precision + 1 {
debug_assert!(&digits[precision..] == "0");
digits.truncate(precision);
p -= 1;
}
let exponent = -p + precision as i64 - 1;
(digits, exponent)
}
fn format_float_scientific(
bd: &BigDecimal,
precision: Option<usize>,
case: Case,
force_decimal: ForceDecimal,
) -> String {
debug_assert!(!bd.is_negative());
let precision = precision.unwrap_or(6);
let exp_char = match case {
Case::Lowercase => 'e',
Case::Uppercase => 'E',
};
if BigDecimal::zero().eq(bd) {
return if force_decimal == ForceDecimal::Yes && precision == 0 {
format!("0.{exp_char}+00")
} else {
format!("{:.precision$}{exp_char}+00", 0.0)
};
}
let (digits, exponent) = bd_to_string_exp_with_prec(bd, precision + 1);
let (first_digit, remaining_digits) = digits.split_at(1);
let dot =
if !remaining_digits.is_empty() || (precision == 0 && ForceDecimal::Yes == force_decimal) {
"."
} else {
""
};
format!("{first_digit}{dot}{remaining_digits}{exp_char}{exponent:+03}")
}
fn format_float_shortest(
bd: &BigDecimal,
precision: Option<usize>,
case: Case,
force_decimal: ForceDecimal,
) -> String {
debug_assert!(!bd.is_negative());
let precision = precision.unwrap_or(6);
let precision = precision.max(1);
if BigDecimal::zero().eq(bd) {
return match (force_decimal, precision) {
(ForceDecimal::Yes, 1) => "0.".into(),
(ForceDecimal::Yes, _) => format!("{:.*}", precision - 1, 0.0),
(ForceDecimal::No, _) => "0".into(),
};
}
let mut output = String::with_capacity(precision);
let (digits, exponent) = bd_to_string_exp_with_prec(bd, precision);
if exponent < -4 || exponent >= precision as i64 {
let (first_digit, remaining_digits) = digits.split_at(1);
output.push_str(first_digit);
output.push('.');
output.push_str(remaining_digits);
if force_decimal == ForceDecimal::No {
strip_fractional_zeroes_and_dot(&mut output);
}
output.push(match case {
Case::Lowercase => 'e',
Case::Uppercase => 'E',
});
let exponent_abs = exponent.abs();
output.push(if exponent < 0 { '-' } else { '+' });
if exponent_abs < 10 {
output.push('0');
}
output.push_str(&exponent_abs.to_string());
} else {
if exponent < 0 {
output.push_str("0.");
output.extend(std::iter::repeat_n('0', -exponent as usize - 1));
output.push_str(&digits);
} else {
let (first_digits, remaining_digits) = digits.split_at(exponent as usize + 1);
output.push_str(first_digits);
output.push('.');
output.push_str(remaining_digits);
}
if force_decimal == ForceDecimal::No {
strip_fractional_zeroes_and_dot(&mut output);
}
}
output
}
fn format_float_hexadecimal(
bd: &BigDecimal,
precision: Option<usize>,
case: Case,
force_decimal: ForceDecimal,
) -> String {
const BEFORE_BITS: usize = 4;
debug_assert!(!bd.is_negative());
let max_precision = precision.unwrap_or(15);
let (prefix, exp_char) = match case {
Case::Lowercase => ("0x", 'p'),
Case::Uppercase => ("0X", 'P'),
};
if BigDecimal::zero().eq(bd) {
return if force_decimal == ForceDecimal::Yes && precision.unwrap_or(0) == 0 {
format!("0x0.{exp_char}+0")
} else {
format!("0x{:.*}{exp_char}+0", precision.unwrap_or(0), 0.0)
};
}
let (frac10, p) = bd.as_bigint_and_exponent();
let exp10 = -p;
let (mut frac2, mut exp2) = if exp10 >= 0 {
(frac10 * 5.to_bigint().unwrap().pow(exp10 as u32), exp10)
} else {
let margin =
((max_precision + 1) as i64 * 4 - frac10.bits() as i64).max(0) + -exp10 * 3 + 1;
(
(frac10 << margin) / 5.to_bigint().unwrap().pow(-exp10 as u32),
exp10 - margin,
)
};
let wanted_bits = (BEFORE_BITS + max_precision * 4) as u64;
let bits = frac2.bits();
exp2 += bits as i64 - wanted_bits as i64;
if bits > wanted_bits {
frac2 >>= bits - wanted_bits - 1;
let add = frac2.bit(0);
frac2 >>= 1;
if add {
frac2 += 0x1;
if frac2.bits() > wanted_bits {
frac2 >>= 4;
exp2 += 4;
}
}
} else {
frac2 <<= wanted_bits - bits;
}
let mut digits = frac2.to_str_radix(16);
if case == Case::Uppercase {
digits.make_ascii_uppercase();
}
let (first_digit, remaining_digits) = digits.split_at(1);
let exponent = exp2 + (4 * max_precision) as i64;
let mut remaining_digits = remaining_digits.to_string();
if precision.is_none() {
strip_fractional_zeroes(&mut remaining_digits);
}
let dot = if !remaining_digits.is_empty()
|| (precision.unwrap_or(0) == 0 && ForceDecimal::Yes == force_decimal)
{
"."
} else {
""
};
format!("{prefix}{first_digit}{dot}{remaining_digits}{exp_char}{exponent:+}")
}
fn strip_fractional_zeroes(s: &mut String) {
let mut trim_to = s.len();
for (pos, c) in s.char_indices().rev() {
if pos + c.len_utf8() == trim_to {
if c == '0' {
trim_to = pos;
} else {
break;
}
}
}
s.truncate(trim_to);
}
fn strip_fractional_zeroes_and_dot(s: &mut String) {
let mut trim_to = s.len();
for (pos, c) in s.char_indices().rev() {
if pos + c.len_utf8() == trim_to && (c == '0' || c == '.') {
trim_to = pos;
}
if c == '.' {
s.truncate(trim_to);
break;
}
}
}
fn write_output(
mut writer: impl Write,
sign_indicator: String,
s: String,
width: usize,
alignment: NumberAlignment,
) -> std::io::Result<()> {
if width == 0 {
writer.write_all(sign_indicator.as_bytes())?;
writer.write_all(s.as_bytes())?;
return Ok(());
}
let remaining_width = width - min(width, sign_indicator.len());
super::check_width(remaining_width)?;
match alignment {
NumberAlignment::Left => write!(writer, "{sign_indicator}{s:<remaining_width$}"),
NumberAlignment::RightSpace => {
let is_sign = sign_indicator.starts_with('-') || sign_indicator.starts_with('+');
if is_sign && remaining_width > 0 {
let s = sign_indicator + s.as_str();
write!(writer, "{s:>width$}", width = remaining_width + 1)
} else {
write!(writer, "{sign_indicator}{s:>remaining_width$}")
}
}
NumberAlignment::RightZero => {
let (prefix, rest) = if s.len() >= 2 && s[..2].eq_ignore_ascii_case("0x") {
(&s[..2], &s[2..])
} else {
("", s.as_str())
};
let remaining_width = remaining_width.saturating_sub(prefix.len());
write!(writer, "{sign_indicator}{prefix}{rest:0>remaining_width$}")
}
}
}