mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #7556 from drinkcat/parse-bigdecimal
uucore: format: num_parser: Use ExtendedBigDecimal
This commit is contained in:
commit
ace92dcca1
5 changed files with 522 additions and 260 deletions
|
@ -5,13 +5,15 @@
|
|||
|
||||
use crate::{
|
||||
error::set_exit_code,
|
||||
features::format::num_parser::{ParseError, ParsedNumber},
|
||||
features::format::num_parser::{ExtendedParser, ExtendedParserError},
|
||||
quoting_style::{Quotes, QuotingStyle, escape_name},
|
||||
show_error, show_warning,
|
||||
};
|
||||
use os_display::Quotable;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use super::ExtendedBigDecimal;
|
||||
|
||||
/// An argument for formatting
|
||||
///
|
||||
/// Each of these variants is only accepted by their respective directives. For
|
||||
|
@ -25,7 +27,7 @@ pub enum FormatArgument {
|
|||
String(String),
|
||||
UnsignedInt(u64),
|
||||
SignedInt(i64),
|
||||
Float(f64),
|
||||
Float(ExtendedBigDecimal),
|
||||
/// Special argument that gets coerced into the other variants
|
||||
Unparsed(String),
|
||||
}
|
||||
|
@ -34,7 +36,7 @@ pub trait ArgumentIter<'a>: Iterator<Item = &'a FormatArgument> {
|
|||
fn get_char(&mut self) -> u8;
|
||||
fn get_i64(&mut self) -> i64;
|
||||
fn get_u64(&mut self) -> u64;
|
||||
fn get_f64(&mut self) -> f64;
|
||||
fn get_extended_big_decimal(&mut self) -> ExtendedBigDecimal;
|
||||
fn get_str(&mut self) -> &'a str;
|
||||
}
|
||||
|
||||
|
@ -56,7 +58,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
|||
};
|
||||
match next {
|
||||
FormatArgument::UnsignedInt(n) => *n,
|
||||
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_u64(s), s),
|
||||
FormatArgument::Unparsed(s) => extract_value(u64::extended_parse(s), s),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
@ -67,19 +69,19 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
|||
};
|
||||
match next {
|
||||
FormatArgument::SignedInt(n) => *n,
|
||||
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_i64(s), s),
|
||||
FormatArgument::Unparsed(s) => extract_value(i64::extended_parse(s), s),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_f64(&mut self) -> f64 {
|
||||
fn get_extended_big_decimal(&mut self) -> ExtendedBigDecimal {
|
||||
let Some(next) = self.next() else {
|
||||
return 0.0;
|
||||
return ExtendedBigDecimal::zero();
|
||||
};
|
||||
match next {
|
||||
FormatArgument::Float(n) => *n,
|
||||
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_f64(s), s),
|
||||
_ => 0.0,
|
||||
FormatArgument::Float(n) => n.clone(),
|
||||
FormatArgument::Unparsed(s) => extract_value(ExtendedBigDecimal::extended_parse(s), s),
|
||||
_ => ExtendedBigDecimal::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +93,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
|||
}
|
||||
}
|
||||
|
||||
fn extract_value<T: Default>(p: Result<T, ParseError<'_, T>>, input: &str) -> T {
|
||||
fn extract_value<T: Default>(p: Result<T, ExtendedParserError<'_, T>>, input: &str) -> T {
|
||||
match p {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
|
@ -103,15 +105,15 @@ fn extract_value<T: Default>(p: Result<T, ParseError<'_, T>>, input: &str) -> T
|
|||
},
|
||||
);
|
||||
match e {
|
||||
ParseError::Overflow => {
|
||||
ExtendedParserError::Overflow => {
|
||||
show_error!("{}: Numerical result out of range", input.quote());
|
||||
Default::default()
|
||||
}
|
||||
ParseError::NotNumeric => {
|
||||
ExtendedParserError::NotNumeric => {
|
||||
show_error!("{}: expected a numeric value", input.quote());
|
||||
Default::default()
|
||||
}
|
||||
ParseError::PartialMatch(v, rest) => {
|
||||
ExtendedParserError::PartialMatch(v, rest) => {
|
||||
let bytes = input.as_encoded_bytes();
|
||||
if !bytes.is_empty() && bytes[0] == b'\'' {
|
||||
show_warning!(
|
||||
|
|
|
@ -141,6 +141,12 @@ impl Zero for ExtendedBigDecimal {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for ExtendedBigDecimal {
|
||||
fn default() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ExtendedBigDecimal {
|
||||
type Output = Self;
|
||||
|
||||
|
|
|
@ -5,11 +5,20 @@
|
|||
|
||||
//! Utilities for parsing numbers in various formats
|
||||
|
||||
// spell-checker:ignore powf copysign prec inity
|
||||
// spell-checker:ignore powf copysign prec inity bigdecimal extendedbigdecimal biguint
|
||||
|
||||
use bigdecimal::{
|
||||
BigDecimal,
|
||||
num_bigint::{BigInt, BigUint, Sign},
|
||||
};
|
||||
use num_traits::ToPrimitive;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::format::extendedbigdecimal::ExtendedBigDecimal;
|
||||
|
||||
/// Base for number parsing
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Base {
|
||||
enum Base {
|
||||
/// Binary base
|
||||
Binary = 2,
|
||||
|
||||
|
@ -44,7 +53,7 @@ impl Base {
|
|||
|
||||
/// Type returned if a number could not be parsed in its entirety
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseError<'a, T> {
|
||||
pub enum ExtendedParserError<'a, T> {
|
||||
/// The input as a whole makes no sense
|
||||
NotNumeric,
|
||||
/// The beginning of the input made sense and has been parsed,
|
||||
|
@ -56,11 +65,14 @@ pub enum ParseError<'a, T> {
|
|||
Overflow,
|
||||
}
|
||||
|
||||
impl<'a, T> ParseError<'a, T> {
|
||||
fn map<U>(self, f: impl FnOnce(T, &'a str) -> ParseError<'a, U>) -> ParseError<'a, U> {
|
||||
impl<'a, T> ExtendedParserError<'a, T> {
|
||||
fn map<U>(
|
||||
self,
|
||||
f: impl FnOnce(T, &'a str) -> ExtendedParserError<'a, U>,
|
||||
) -> ExtendedParserError<'a, U> {
|
||||
match self {
|
||||
Self::NotNumeric => ParseError::NotNumeric,
|
||||
Self::Overflow => ParseError::Overflow,
|
||||
Self::NotNumeric => ExtendedParserError::NotNumeric,
|
||||
Self::Overflow => ExtendedParserError::Overflow,
|
||||
Self::PartialMatch(v, s) => f(v, s),
|
||||
}
|
||||
}
|
||||
|
@ -68,120 +80,163 @@ impl<'a, T> ParseError<'a, T> {
|
|||
|
||||
/// A number parser for binary, octal, decimal, hexadecimal and single characters.
|
||||
///
|
||||
/// Internally, in order to get the maximum possible precision and cover the full
|
||||
/// range of u64 and i64 without losing precision for f64, the returned number is
|
||||
/// decomposed into:
|
||||
/// - A `base` value
|
||||
/// - A `neg` sign bit
|
||||
/// - A `integral` positive part
|
||||
/// - A `fractional` positive part
|
||||
/// - A `precision` representing the number of digits in the fractional part
|
||||
///
|
||||
/// If the fractional part cannot be represented on a `u64`, parsing continues
|
||||
/// silently by ignoring non-significant digits.
|
||||
pub struct ParsedNumber {
|
||||
base: Base,
|
||||
negative: bool,
|
||||
integral: u64,
|
||||
fractional: u64,
|
||||
precision: usize,
|
||||
}
|
||||
|
||||
impl ParsedNumber {
|
||||
fn into_i64(self) -> Option<i64> {
|
||||
if self.negative {
|
||||
i64::try_from(-i128::from(self.integral)).ok()
|
||||
} else {
|
||||
i64::try_from(self.integral).ok()
|
||||
}
|
||||
/// It is implemented for `u64`/`i64`, where no fractional part is parsed,
|
||||
/// and `f64` float, where octal and binary formats are not allowed.
|
||||
pub trait ExtendedParser {
|
||||
// We pick a hopefully different name for our parser, to avoid clash with standard traits.
|
||||
fn extended_parse(input: &str) -> Result<Self, ExtendedParserError<'_, Self>>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl ExtendedParser for i64 {
|
||||
/// Parse a number as i64. No fractional part is allowed.
|
||||
pub fn parse_i64(input: &str) -> Result<i64, ParseError<'_, i64>> {
|
||||
match Self::parse(input, true) {
|
||||
Ok(v) => v.into_i64().ok_or(ParseError::Overflow),
|
||||
fn extended_parse(input: &str) -> Result<i64, ExtendedParserError<'_, i64>> {
|
||||
fn into_i64(ebd: ExtendedBigDecimal) -> Option<i64> {
|
||||
match ebd {
|
||||
ExtendedBigDecimal::BigDecimal(bd) => {
|
||||
let (digits, scale) = bd.into_bigint_and_scale();
|
||||
if scale == 0 {
|
||||
i64::try_from(digits).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ExtendedBigDecimal::MinusZero => Some(0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
match parse(input, true) {
|
||||
Ok(v) => into_i64(v).ok_or(ExtendedParserError::Overflow),
|
||||
Err(e) => Err(e.map(|v, rest| {
|
||||
v.into_i64()
|
||||
.map(|v| ParseError::PartialMatch(v, rest))
|
||||
.unwrap_or(ParseError::Overflow)
|
||||
into_i64(v)
|
||||
.map(|v| ExtendedParserError::PartialMatch(v, rest))
|
||||
.unwrap_or(ExtendedParserError::Overflow)
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedParser for u64 {
|
||||
/// Parse a number as u64. No fractional part is allowed.
|
||||
pub fn parse_u64(input: &str) -> Result<u64, ParseError<'_, u64>> {
|
||||
match Self::parse(input, true) {
|
||||
Ok(v) | Err(ParseError::PartialMatch(v, _)) if v.negative => {
|
||||
Err(ParseError::NotNumeric)
|
||||
}
|
||||
Ok(v) => Ok(v.integral),
|
||||
Err(e) => Err(e.map(|v, rest| ParseError::PartialMatch(v.integral, rest))),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_f64(self) -> f64 {
|
||||
let n = self.integral as f64
|
||||
+ (self.fractional as f64) / (self.base as u8 as f64).powf(self.precision as f64);
|
||||
if self.negative { -n } else { n }
|
||||
}
|
||||
|
||||
/// Parse a number as f64
|
||||
pub fn parse_f64(input: &str) -> Result<f64, ParseError<'_, f64>> {
|
||||
match Self::parse(input, false) {
|
||||
Ok(v) => Ok(v.into_f64()),
|
||||
Err(ParseError::NotNumeric) => Self::parse_f64_special_values(input),
|
||||
Err(e) => Err(e.map(|v, rest| ParseError::PartialMatch(v.into_f64(), rest))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_f64_special_values(input: &str) -> Result<f64, ParseError<'_, f64>> {
|
||||
let (sign, rest) = if let Some(input) = input.strip_prefix('-') {
|
||||
(-1.0, input)
|
||||
fn extended_parse(input: &str) -> Result<u64, ExtendedParserError<'_, u64>> {
|
||||
fn into_u64(ebd: ExtendedBigDecimal) -> Option<u64> {
|
||||
match ebd {
|
||||
ExtendedBigDecimal::BigDecimal(bd) => {
|
||||
let (digits, scale) = bd.into_bigint_and_scale();
|
||||
if scale == 0 {
|
||||
u64::try_from(digits).ok()
|
||||
} else {
|
||||
(1.0, input)
|
||||
};
|
||||
let prefix = rest
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
match parse(input, true) {
|
||||
Ok(v) => into_u64(v).ok_or(ExtendedParserError::Overflow),
|
||||
Err(e) => Err(e.map(|v, rest| {
|
||||
into_u64(v)
|
||||
.map(|v| ExtendedParserError::PartialMatch(v, rest))
|
||||
.unwrap_or(ExtendedParserError::Overflow)
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedParser for f64 {
|
||||
/// Parse a number as f64
|
||||
fn extended_parse(input: &str) -> Result<f64, ExtendedParserError<'_, f64>> {
|
||||
// TODO: This is generic, so this should probably be implemented as an ExtendedBigDecimal trait (ToPrimitive).
|
||||
fn into_f64(ebd: ExtendedBigDecimal) -> f64 {
|
||||
match ebd {
|
||||
ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(),
|
||||
ExtendedBigDecimal::MinusZero => -0.0,
|
||||
ExtendedBigDecimal::Nan => f64::NAN,
|
||||
ExtendedBigDecimal::MinusNan => -f64::NAN,
|
||||
ExtendedBigDecimal::Infinity => f64::INFINITY,
|
||||
ExtendedBigDecimal::MinusInfinity => -f64::INFINITY,
|
||||
}
|
||||
}
|
||||
|
||||
match parse(input, false) {
|
||||
Ok(v) => Ok(into_f64(v)),
|
||||
Err(e) => Err(e.map(|v, rest| ExtendedParserError::PartialMatch(into_f64(v), rest))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedParser for ExtendedBigDecimal {
|
||||
/// Parse a number as an ExtendedBigDecimal
|
||||
fn extended_parse(
|
||||
input: &str,
|
||||
) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> {
|
||||
parse(input, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_special_value(
|
||||
input: &str,
|
||||
negative: bool,
|
||||
) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> {
|
||||
let prefix = input
|
||||
.chars()
|
||||
.take(3)
|
||||
.map(|c| c.to_ascii_lowercase())
|
||||
.collect::<String>();
|
||||
let special = match prefix.as_str() {
|
||||
"inf" => f64::INFINITY,
|
||||
"nan" => f64::NAN,
|
||||
_ => return Err(ParseError::NotNumeric),
|
||||
"inf" => {
|
||||
if negative {
|
||||
ExtendedBigDecimal::MinusInfinity
|
||||
} else {
|
||||
ExtendedBigDecimal::Infinity
|
||||
}
|
||||
.copysign(sign);
|
||||
if rest.len() == 3 {
|
||||
}
|
||||
"nan" => {
|
||||
if negative {
|
||||
ExtendedBigDecimal::MinusNan
|
||||
} else {
|
||||
ExtendedBigDecimal::Nan
|
||||
}
|
||||
}
|
||||
_ => return Err(ExtendedParserError::NotNumeric),
|
||||
};
|
||||
if input.len() == 3 {
|
||||
Ok(special)
|
||||
} else {
|
||||
Err(ParseError::PartialMatch(special, &rest[3..]))
|
||||
Err(ExtendedParserError::PartialMatch(special, &input[3..]))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: As highlighted by clippy, this function _is_ high cognitive complexity, jumps
|
||||
// around between integer and float parsing, and should be split in multiple parts.
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn parse(input: &str, integral_only: bool) -> Result<Self, ParseError<'_, Self>> {
|
||||
fn parse(
|
||||
input: &str,
|
||||
integral_only: bool,
|
||||
) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> {
|
||||
// Parse the "'" prefix separately
|
||||
if let Some(rest) = input.strip_prefix('\'') {
|
||||
let mut chars = rest.char_indices().fuse();
|
||||
let v = chars.next().map(|(_, c)| Self {
|
||||
base: Base::Decimal,
|
||||
negative: false,
|
||||
integral: u64::from(c),
|
||||
fractional: 0,
|
||||
precision: 0,
|
||||
});
|
||||
let v = chars
|
||||
.next()
|
||||
.map(|(_, c)| ExtendedBigDecimal::BigDecimal(u32::from(c).into()));
|
||||
return match (v, chars.next()) {
|
||||
(Some(v), None) => Ok(v),
|
||||
(Some(v), Some((i, _))) => Err(ParseError::PartialMatch(v, &rest[i..])),
|
||||
(None, _) => Err(ParseError::NotNumeric),
|
||||
(Some(v), Some((i, _))) => Err(ExtendedParserError::PartialMatch(v, &rest[i..])),
|
||||
(None, _) => Err(ExtendedParserError::NotNumeric),
|
||||
};
|
||||
}
|
||||
|
||||
let trimmed_input = input.trim_ascii_start();
|
||||
|
||||
// Initial minus sign
|
||||
// Initial minus/plus sign
|
||||
let (negative, unsigned) = if let Some(trimmed_input) = trimmed_input.strip_prefix('-') {
|
||||
(true, trimmed_input)
|
||||
} else if let Some(trimmed_input) = trimmed_input.strip_prefix('+') {
|
||||
(false, trimmed_input)
|
||||
} else {
|
||||
(false, trimmed_input)
|
||||
};
|
||||
|
@ -191,12 +246,15 @@ impl ParsedNumber {
|
|||
// will not be consumed in case the parsable string contains only "0": the leading extra "0"
|
||||
// will have no influence on the result.
|
||||
let (base, rest) = if let Some(rest) = unsigned.strip_prefix('0') {
|
||||
if let Some(rest) = rest.strip_prefix(['b', 'B']) {
|
||||
(Base::Binary, rest)
|
||||
} else if let Some(rest) = rest.strip_prefix(['x', 'X']) {
|
||||
if let Some(rest) = rest.strip_prefix(['x', 'X']) {
|
||||
(Base::Hexadecimal, rest)
|
||||
} else if integral_only {
|
||||
// Binary/Octal only allowed for integer parsing.
|
||||
if let Some(rest) = rest.strip_prefix(['b', 'B']) {
|
||||
(Base::Binary, rest)
|
||||
} else {
|
||||
(Base::Octal, unsigned)
|
||||
}
|
||||
} else {
|
||||
(Base::Decimal, unsigned)
|
||||
}
|
||||
|
@ -204,217 +262,404 @@ impl ParsedNumber {
|
|||
(Base::Decimal, unsigned)
|
||||
};
|
||||
if rest.is_empty() {
|
||||
return Err(ParseError::NotNumeric);
|
||||
return Err(ExtendedParserError::NotNumeric);
|
||||
}
|
||||
|
||||
// Parse the integral part of the number
|
||||
let mut chars = rest.chars().enumerate().fuse().peekable();
|
||||
let mut integral = 0u64;
|
||||
let mut digits = BigUint::zero();
|
||||
let mut scale = 0u64;
|
||||
let mut exponent = 0i64;
|
||||
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
||||
chars.next();
|
||||
integral = integral
|
||||
.checked_mul(base as u64)
|
||||
.and_then(|n| n.checked_add(d))
|
||||
.ok_or(ParseError::Overflow)?;
|
||||
digits = digits * base as u8 + d;
|
||||
}
|
||||
|
||||
// Parse fractional/exponent part of the number for supported bases.
|
||||
if matches!(base, Base::Decimal | Base::Hexadecimal) && !integral_only {
|
||||
// Parse the fractional part of the number if there can be one and the input contains
|
||||
// a '.' decimal separator.
|
||||
let (mut fractional, mut precision) = (0u64, 0);
|
||||
if matches!(chars.peek(), Some(&(_, '.')))
|
||||
&& matches!(base, Base::Decimal | Base::Hexadecimal)
|
||||
&& !integral_only
|
||||
{
|
||||
if matches!(chars.peek(), Some(&(_, '.'))) {
|
||||
chars.next();
|
||||
let mut ended = false;
|
||||
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
||||
chars.next();
|
||||
if !ended {
|
||||
if let Some(f) = fractional
|
||||
.checked_mul(base as u64)
|
||||
.and_then(|n| n.checked_add(d))
|
||||
{
|
||||
(fractional, precision) = (f, precision + 1);
|
||||
} else {
|
||||
ended = true;
|
||||
(digits, scale) = (digits * base as u8 + d, scale + 1);
|
||||
}
|
||||
}
|
||||
|
||||
let exp_char = match base {
|
||||
Base::Decimal => 'e',
|
||||
Base::Hexadecimal => 'p',
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// Parse the exponent part, only decimal numbers are allowed.
|
||||
if chars.peek().is_some_and(|&(_, c)| c == exp_char) {
|
||||
chars.next();
|
||||
let exp_negative = match chars.peek() {
|
||||
Some((_, '-')) => {
|
||||
chars.next();
|
||||
true
|
||||
}
|
||||
Some((_, '+')) => {
|
||||
chars.next();
|
||||
false
|
||||
}
|
||||
_ => false, // Something else, or nothing at all: keep going.
|
||||
};
|
||||
while let Some(d) = chars.peek().and_then(|&(_, c)| Base::Decimal.digit(c)) {
|
||||
chars.next();
|
||||
exponent = exponent * 10 + d as i64;
|
||||
}
|
||||
if exp_negative {
|
||||
exponent = -exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing has been parsed, declare the parsing unsuccessful
|
||||
// If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful
|
||||
if let Some((0, _)) = chars.peek() {
|
||||
return Err(ParseError::NotNumeric);
|
||||
if integral_only {
|
||||
return Err(ExtendedParserError::NotNumeric);
|
||||
} else {
|
||||
return parse_special_value(unsigned, negative);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Might be nice to implement a ExtendedBigDecimal copysign or negation function to move away some of this logic...
|
||||
let ebd = if digits == BigUint::zero() && negative {
|
||||
ExtendedBigDecimal::MinusZero
|
||||
} else {
|
||||
let sign = if negative { Sign::Minus } else { Sign::Plus };
|
||||
let signed_digits = BigInt::from_biguint(sign, digits);
|
||||
let bd = if scale == 0 && exponent == 0 {
|
||||
BigDecimal::from_bigint(signed_digits, 0)
|
||||
} else if base == Base::Decimal {
|
||||
BigDecimal::from_bigint(signed_digits, scale as i64 - exponent)
|
||||
} else if base == Base::Hexadecimal {
|
||||
// Base is 16, init at scale 0 then divide by base**scale.
|
||||
let bd = BigDecimal::from_bigint(signed_digits, 0)
|
||||
/ BigDecimal::from_bigint(BigInt::from(16).pow(scale as u32), 0);
|
||||
// Confusingly, exponent is in base 2 for hex floating point numbers.
|
||||
if exponent >= 0 {
|
||||
bd * 2u64.pow(exponent as u32)
|
||||
} else {
|
||||
bd / 2u64.pow(-exponent as u32)
|
||||
}
|
||||
} else {
|
||||
// scale != 0, which means that integral_only is not set, so only base 10 and 16 are allowed.
|
||||
unreachable!();
|
||||
};
|
||||
ExtendedBigDecimal::BigDecimal(bd)
|
||||
};
|
||||
|
||||
// Return what has been parsed so far. It there are extra characters, mark the
|
||||
// parsing as a partial match.
|
||||
let parsed = Self {
|
||||
base,
|
||||
negative,
|
||||
integral,
|
||||
fractional,
|
||||
precision,
|
||||
};
|
||||
if let Some((first_unparsed, _)) = chars.next() {
|
||||
Err(ParseError::PartialMatch(parsed, &rest[first_unparsed..]))
|
||||
Err(ExtendedParserError::PartialMatch(
|
||||
ebd,
|
||||
&rest[first_unparsed..],
|
||||
))
|
||||
} else {
|
||||
Ok(parsed)
|
||||
}
|
||||
Ok(ebd)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ParseError, ParsedNumber};
|
||||
use std::str::FromStr;
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
|
||||
use crate::format::ExtendedBigDecimal;
|
||||
|
||||
use super::{ExtendedParser, ExtendedParserError};
|
||||
|
||||
#[test]
|
||||
fn test_decimal_u64() {
|
||||
assert_eq!(Ok(123), ParsedNumber::parse_u64("123"));
|
||||
assert_eq!(
|
||||
Ok(u64::MAX),
|
||||
ParsedNumber::parse_u64(&format!("{}", u64::MAX))
|
||||
);
|
||||
assert_eq!(Ok(123), u64::extended_parse("123"));
|
||||
assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX)));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_u64("-123"),
|
||||
Err(ParseError::NotNumeric)
|
||||
u64::extended_parse("-123"),
|
||||
Err(ExtendedParserError::Overflow)
|
||||
));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_u64(""),
|
||||
Err(ParseError::NotNumeric)
|
||||
u64::extended_parse(""),
|
||||
Err(ExtendedParserError::NotNumeric)
|
||||
));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_u64("123.15"),
|
||||
Err(ParseError::PartialMatch(123, ".15"))
|
||||
u64::extended_parse("123.15"),
|
||||
Err(ExtendedParserError::PartialMatch(123, ".15"))
|
||||
));
|
||||
assert!(matches!(
|
||||
u64::extended_parse("123e10"),
|
||||
Err(ExtendedParserError::PartialMatch(123, "e10"))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_i64() {
|
||||
assert_eq!(Ok(123), ParsedNumber::parse_i64("123"));
|
||||
assert_eq!(Ok(-123), ParsedNumber::parse_i64("-123"));
|
||||
assert_eq!(Ok(123), i64::extended_parse("123"));
|
||||
assert_eq!(Ok(123), i64::extended_parse("+123"));
|
||||
assert_eq!(Ok(-123), i64::extended_parse("-123"));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_i64("--123"),
|
||||
Err(ParseError::NotNumeric)
|
||||
i64::extended_parse("--123"),
|
||||
Err(ExtendedParserError::NotNumeric)
|
||||
));
|
||||
assert_eq!(
|
||||
Ok(i64::MAX),
|
||||
ParsedNumber::parse_i64(&format!("{}", i64::MAX))
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(i64::MIN),
|
||||
ParsedNumber::parse_i64(&format!("{}", i64::MIN))
|
||||
);
|
||||
assert_eq!(Ok(i64::MAX), i64::extended_parse(&format!("{}", i64::MAX)));
|
||||
assert_eq!(Ok(i64::MIN), i64::extended_parse(&format!("{}", i64::MIN)));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_i64(&format!("{}", u64::MAX)),
|
||||
Err(ParseError::Overflow)
|
||||
i64::extended_parse(&format!("{}", u64::MAX)),
|
||||
Err(ExtendedParserError::Overflow)
|
||||
));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_i64(&format!("{}", i64::MAX as u64 + 1)),
|
||||
Err(ParseError::Overflow)
|
||||
i64::extended_parse(&format!("{}", i64::MAX as u64 + 1)),
|
||||
Err(ExtendedParserError::Overflow)
|
||||
));
|
||||
assert!(matches!(
|
||||
i64::extended_parse("-123e10"),
|
||||
Err(ExtendedParserError::PartialMatch(-123, "e10"))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_f64() {
|
||||
assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123"));
|
||||
assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123"));
|
||||
assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123."));
|
||||
assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123."));
|
||||
assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123.0"));
|
||||
assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123.0"));
|
||||
assert_eq!(Ok(123.15), ParsedNumber::parse_f64("123.15"));
|
||||
assert_eq!(Ok(-123.15), ParsedNumber::parse_f64("-123.15"));
|
||||
assert_eq!(Ok(0.15), ParsedNumber::parse_f64(".15"));
|
||||
assert_eq!(Ok(-0.15), ParsedNumber::parse_f64("-.15"));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("123"));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("+123"));
|
||||
assert_eq!(Ok(-123.0), f64::extended_parse("-123"));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("123."));
|
||||
assert_eq!(Ok(-123.0), f64::extended_parse("-123."));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("123.0"));
|
||||
assert_eq!(Ok(-123.0), f64::extended_parse("-123.0"));
|
||||
assert_eq!(Ok(123.15), f64::extended_parse("123.15"));
|
||||
assert_eq!(Ok(123.15), f64::extended_parse("+123.15"));
|
||||
assert_eq!(Ok(-123.15), f64::extended_parse("-123.15"));
|
||||
assert_eq!(Ok(0.15), f64::extended_parse(".15"));
|
||||
assert_eq!(Ok(-0.15), f64::extended_parse("-.15"));
|
||||
// Leading 0(s) are _not_ octal when parsed as float
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("0123"));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("+0123"));
|
||||
assert_eq!(Ok(-123.0), f64::extended_parse("-0123"));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("00123"));
|
||||
assert_eq!(Ok(123.0), f64::extended_parse("+00123"));
|
||||
assert_eq!(Ok(-123.0), f64::extended_parse("-00123"));
|
||||
assert_eq!(Ok(123.15), f64::extended_parse("0123.15"));
|
||||
assert_eq!(Ok(123.15), f64::extended_parse("+0123.15"));
|
||||
assert_eq!(Ok(-123.15), f64::extended_parse("-0123.15"));
|
||||
assert_eq!(Ok(12315000.0), f64::extended_parse("123.15e5"));
|
||||
assert_eq!(Ok(-12315000.0), f64::extended_parse("-123.15e5"));
|
||||
assert_eq!(Ok(12315000.0), f64::extended_parse("123.15e+5"));
|
||||
assert_eq!(Ok(0.0012315), f64::extended_parse("123.15e-5"));
|
||||
assert_eq!(
|
||||
Ok(0.15),
|
||||
ParsedNumber::parse_f64(".150000000000000000000000000231313")
|
||||
f64::extended_parse(".150000000000000000000000000231313")
|
||||
);
|
||||
assert!(matches!(ParsedNumber::parse_f64("1.2.3"),
|
||||
Err(ParseError::PartialMatch(f, ".3")) if f == 1.2));
|
||||
assert_eq!(Ok(f64::INFINITY), ParsedNumber::parse_f64("inf"));
|
||||
assert_eq!(Ok(f64::NEG_INFINITY), ParsedNumber::parse_f64("-inf"));
|
||||
assert!(ParsedNumber::parse_f64("NaN").unwrap().is_nan());
|
||||
assert!(ParsedNumber::parse_f64("NaN").unwrap().is_sign_positive());
|
||||
assert!(ParsedNumber::parse_f64("-NaN").unwrap().is_nan());
|
||||
assert!(ParsedNumber::parse_f64("-NaN").unwrap().is_sign_negative());
|
||||
assert!(matches!(ParsedNumber::parse_f64("-infinity"),
|
||||
Err(ParseError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY));
|
||||
assert!(ParsedNumber::parse_f64(&format!("{}", u64::MAX)).is_ok());
|
||||
assert!(ParsedNumber::parse_f64(&format!("{}", i64::MIN)).is_ok());
|
||||
assert!(matches!(f64::extended_parse("1.2.3"),
|
||||
Err(ExtendedParserError::PartialMatch(f, ".3")) if f == 1.2));
|
||||
assert!(matches!(f64::extended_parse("123.15p5"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "p5")) if f == 123.15));
|
||||
// Minus zero. 0.0 == -0.0 so we explicitly check the sign.
|
||||
assert_eq!(Ok(0.0), f64::extended_parse("-0.0"));
|
||||
assert!(f64::extended_parse("-0.0").unwrap().is_sign_negative());
|
||||
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("inf"));
|
||||
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("+inf"));
|
||||
assert_eq!(Ok(f64::NEG_INFINITY), f64::extended_parse("-inf"));
|
||||
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("Inf"));
|
||||
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("InF"));
|
||||
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("INF"));
|
||||
assert!(f64::extended_parse("NaN").unwrap().is_nan());
|
||||
assert!(f64::extended_parse("NaN").unwrap().is_sign_positive());
|
||||
assert!(f64::extended_parse("+NaN").unwrap().is_nan());
|
||||
assert!(f64::extended_parse("+NaN").unwrap().is_sign_positive());
|
||||
assert!(f64::extended_parse("-NaN").unwrap().is_nan());
|
||||
assert!(f64::extended_parse("-NaN").unwrap().is_sign_negative());
|
||||
assert!(f64::extended_parse("nan").unwrap().is_nan());
|
||||
assert!(f64::extended_parse("nan").unwrap().is_sign_positive());
|
||||
assert!(f64::extended_parse("NAN").unwrap().is_nan());
|
||||
assert!(f64::extended_parse("NAN").unwrap().is_sign_positive());
|
||||
assert!(matches!(f64::extended_parse("-infinity"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY));
|
||||
assert!(f64::extended_parse(&format!("{}", u64::MAX)).is_ok());
|
||||
assert!(f64::extended_parse(&format!("{}", i64::MIN)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_extended_big_decimal() {
|
||||
// f64 parsing above already tested a lot of these, just do a few.
|
||||
// Careful, we usually cannot use From<f64> to get a precise ExtendedBigDecimal as numbers like 123.15 cannot be exactly represented by a f64.
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(
|
||||
BigDecimal::from_str("123").unwrap()
|
||||
)),
|
||||
ExtendedBigDecimal::extended_parse("123")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(
|
||||
BigDecimal::from_str("123.15").unwrap()
|
||||
)),
|
||||
ExtendedBigDecimal::extended_parse("123.15")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(BigDecimal::from_bigint(
|
||||
12315.into(),
|
||||
-98
|
||||
))),
|
||||
ExtendedBigDecimal::extended_parse("123.15e100")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(BigDecimal::from_bigint(
|
||||
12315.into(),
|
||||
102
|
||||
))),
|
||||
ExtendedBigDecimal::extended_parse("123.15e-100")
|
||||
);
|
||||
// Very high precision that would not fit in a f64.
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(
|
||||
BigDecimal::from_str(".150000000000000000000000000000000000001").unwrap()
|
||||
)),
|
||||
ExtendedBigDecimal::extended_parse(".150000000000000000000000000000000000001")
|
||||
);
|
||||
assert!(matches!(
|
||||
ExtendedBigDecimal::extended_parse("nan"),
|
||||
Ok(ExtendedBigDecimal::Nan)
|
||||
));
|
||||
assert!(matches!(
|
||||
ExtendedBigDecimal::extended_parse("-NAN"),
|
||||
Ok(ExtendedBigDecimal::MinusNan)
|
||||
));
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::Infinity),
|
||||
ExtendedBigDecimal::extended_parse("InF")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::MinusInfinity),
|
||||
ExtendedBigDecimal::extended_parse("-iNf")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::zero()),
|
||||
ExtendedBigDecimal::extended_parse("0.0")
|
||||
);
|
||||
assert!(matches!(
|
||||
ExtendedBigDecimal::extended_parse("-0.0"),
|
||||
Ok(ExtendedBigDecimal::MinusZero)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexadecimal() {
|
||||
assert_eq!(Ok(0x123), ParsedNumber::parse_u64("0x123"));
|
||||
assert_eq!(Ok(0x123), ParsedNumber::parse_u64("0X123"));
|
||||
assert_eq!(Ok(0xfe), ParsedNumber::parse_u64("0xfE"));
|
||||
assert_eq!(Ok(-0x123), ParsedNumber::parse_i64("-0x123"));
|
||||
assert_eq!(Ok(0x123), u64::extended_parse("0x123"));
|
||||
assert_eq!(Ok(0x123), u64::extended_parse("0X123"));
|
||||
assert_eq!(Ok(0x123), u64::extended_parse("+0x123"));
|
||||
assert_eq!(Ok(0xfe), u64::extended_parse("0xfE"));
|
||||
assert_eq!(Ok(-0x123), i64::extended_parse("-0x123"));
|
||||
|
||||
assert_eq!(Ok(0.5), ParsedNumber::parse_f64("0x.8"));
|
||||
assert_eq!(Ok(0.0625), ParsedNumber::parse_f64("0x.1"));
|
||||
assert_eq!(Ok(15.007_812_5), ParsedNumber::parse_f64("0xf.02"));
|
||||
assert_eq!(Ok(0.5), f64::extended_parse("0x.8"));
|
||||
assert_eq!(Ok(0.0625), f64::extended_parse("0x.1"));
|
||||
assert_eq!(Ok(15.007_812_5), f64::extended_parse("0xf.02"));
|
||||
assert_eq!(Ok(16.0), f64::extended_parse("0x0.8p5"));
|
||||
assert_eq!(Ok(0.0625), f64::extended_parse("0x1p-4"));
|
||||
|
||||
// We cannot really check that 'e' is not a valid exponent indicator for hex floats...
|
||||
// but we can check that the number still gets parsed properly: 0x0.8e5 is 0x8e5 / 16**3
|
||||
assert_eq!(Ok(0.555908203125), f64::extended_parse("0x0.8e5"));
|
||||
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(
|
||||
BigDecimal::from_str("0.0625").unwrap()
|
||||
)),
|
||||
ExtendedBigDecimal::extended_parse("0x.1")
|
||||
);
|
||||
|
||||
// Precisely parse very large hexadecimal numbers (i.e. with a large division).
|
||||
assert_eq!(
|
||||
Ok(ExtendedBigDecimal::BigDecimal(
|
||||
BigDecimal::from_str("15.999999999999999999999999948301211715435770320536956745627321652136743068695068359375").unwrap()
|
||||
)),
|
||||
ExtendedBigDecimal::extended_parse("0xf.fffffffffffffffffffff")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_octal() {
|
||||
assert_eq!(Ok(0), ParsedNumber::parse_u64("0"));
|
||||
assert_eq!(Ok(0o123), ParsedNumber::parse_u64("0123"));
|
||||
assert_eq!(Ok(0o123), ParsedNumber::parse_u64("00123"));
|
||||
assert_eq!(Ok(0), ParsedNumber::parse_u64("00"));
|
||||
assert_eq!(Ok(0), u64::extended_parse("0"));
|
||||
assert_eq!(Ok(0o123), u64::extended_parse("0123"));
|
||||
assert_eq!(Ok(0o123), u64::extended_parse("+0123"));
|
||||
assert_eq!(Ok(-0o123), i64::extended_parse("-0123"));
|
||||
assert_eq!(Ok(0o123), u64::extended_parse("00123"));
|
||||
assert_eq!(Ok(0), u64::extended_parse("00"));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_u64("008"),
|
||||
Err(ParseError::PartialMatch(0, "8"))
|
||||
u64::extended_parse("008"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "8"))
|
||||
));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_u64("08"),
|
||||
Err(ParseError::PartialMatch(0, "8"))
|
||||
u64::extended_parse("08"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "8"))
|
||||
));
|
||||
assert!(matches!(
|
||||
ParsedNumber::parse_u64("0."),
|
||||
Err(ParseError::PartialMatch(0, "."))
|
||||
u64::extended_parse("0."),
|
||||
Err(ExtendedParserError::PartialMatch(0, "."))
|
||||
));
|
||||
|
||||
// No float tests, leading zeros get parsed as decimal anyway.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary() {
|
||||
assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0b1011"));
|
||||
assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0B1011"));
|
||||
assert_eq!(Ok(0b1011), u64::extended_parse("0b1011"));
|
||||
assert_eq!(Ok(0b1011), u64::extended_parse("0B1011"));
|
||||
assert_eq!(Ok(0b1011), u64::extended_parse("+0b1011"));
|
||||
assert_eq!(Ok(-0b1011), i64::extended_parse("-0b1011"));
|
||||
|
||||
// Binary not allowed for floats
|
||||
assert!(matches!(
|
||||
f64::extended_parse("0b100"),
|
||||
Err(ExtendedParserError::PartialMatch(0f64, "b100"))
|
||||
));
|
||||
assert!(matches!(
|
||||
f64::extended_parse("0b100.1"),
|
||||
Err(ExtendedParserError::PartialMatch(0f64, "b100.1"))
|
||||
));
|
||||
|
||||
assert!(match ExtendedBigDecimal::extended_parse("0b100.1") {
|
||||
Err(ExtendedParserError::PartialMatch(ebd, "b100.1")) =>
|
||||
ebd == ExtendedBigDecimal::zero(),
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsing_with_leading_whitespace() {
|
||||
assert_eq!(Ok(1), ParsedNumber::parse_u64(" 0x1"));
|
||||
assert_eq!(Ok(-2), ParsedNumber::parse_i64(" -0x2"));
|
||||
assert_eq!(Ok(-3), ParsedNumber::parse_i64(" \t-0x3"));
|
||||
assert_eq!(Ok(-4), ParsedNumber::parse_i64(" \n-0x4"));
|
||||
assert_eq!(Ok(-5), ParsedNumber::parse_i64(" \n\t\u{000d}-0x5"));
|
||||
assert_eq!(Ok(1), u64::extended_parse(" 0x1"));
|
||||
assert_eq!(Ok(-2), i64::extended_parse(" -0x2"));
|
||||
assert_eq!(Ok(-3), i64::extended_parse(" \t-0x3"));
|
||||
assert_eq!(Ok(-4), i64::extended_parse(" \n-0x4"));
|
||||
assert_eq!(Ok(-5), i64::extended_parse(" \n\t\u{000d}-0x5"));
|
||||
|
||||
// Ensure that trailing whitespace is still a partial match
|
||||
assert_eq!(
|
||||
Err(ParseError::PartialMatch(6, " ")),
|
||||
ParsedNumber::parse_u64("0x6 ")
|
||||
Err(ExtendedParserError::PartialMatch(6, " ")),
|
||||
u64::extended_parse("0x6 ")
|
||||
);
|
||||
assert_eq!(
|
||||
Err(ParseError::PartialMatch(7, "\t")),
|
||||
ParsedNumber::parse_u64("0x7\t")
|
||||
Err(ExtendedParserError::PartialMatch(7, "\t")),
|
||||
u64::extended_parse("0x7\t")
|
||||
);
|
||||
assert_eq!(
|
||||
Err(ParseError::PartialMatch(8, "\n")),
|
||||
ParsedNumber::parse_u64("0x8\n")
|
||||
Err(ExtendedParserError::PartialMatch(8, "\n")),
|
||||
u64::extended_parse("0x8\n")
|
||||
);
|
||||
|
||||
// Ensure that unicode non-ascii whitespace is a partial match
|
||||
assert_eq!(
|
||||
Err(ParseError::NotNumeric),
|
||||
ParsedNumber::parse_i64("\u{2029}-0x9")
|
||||
Err(ExtendedParserError::NotNumeric),
|
||||
i64::extended_parse("\u{2029}-0x9")
|
||||
);
|
||||
|
||||
// Ensure that whitespace after the number has "started" is not allowed
|
||||
assert_eq!(
|
||||
Err(ParseError::NotNumeric),
|
||||
ParsedNumber::parse_i64("- 0x9")
|
||||
Err(ExtendedParserError::NotNumeric),
|
||||
i64::extended_parse("- 0x9")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -433,8 +433,7 @@ impl Spec {
|
|||
} => {
|
||||
let width = resolve_asterisk(*width, &mut args).unwrap_or(0);
|
||||
let precision = resolve_asterisk(*precision, &mut args).unwrap_or(6);
|
||||
// TODO: We should implement some get_extendedBigDecimal function in args to avoid losing precision.
|
||||
let f: ExtendedBigDecimal = args.get_f64().into();
|
||||
let f: ExtendedBigDecimal = args.get_extended_big_decimal();
|
||||
|
||||
if precision as u64 > i32::MAX as u64 {
|
||||
return Err(FormatError::InvalidPrecision(precision.to_string()));
|
||||
|
|
|
@ -992,6 +992,16 @@ fn float_flag_position_space_padding() {
|
|||
.stdout_only(" +1.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_large_precision() {
|
||||
// Note: This does not match GNU coreutils output (0.100000000000000000001355252716 on x86),
|
||||
// as we parse and format using ExtendedBigDecimal, which provides arbitrary precision.
|
||||
new_ucmd!()
|
||||
.args(&["%.30f", "0.1"])
|
||||
.succeeds()
|
||||
.stdout_only("0.100000000000000000000000000000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_non_finite_space_padding() {
|
||||
new_ucmd!()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue