1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

Merge pull request #7623 from drinkcat/parse-bigdecimal-smallfixes

uucore: format: Collection of small parser fixes
This commit is contained in:
Dorian Péron 2025-04-01 12:36:28 +02:00 committed by GitHub
commit 17d81bb9a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 337 additions and 104 deletions

View file

@ -105,9 +105,13 @@ fn extract_value<T: Default>(p: Result<T, ExtendedParserError<'_, T>>, input: &s
}, },
); );
match e { match e {
ExtendedParserError::Overflow => { ExtendedParserError::Overflow(v) => {
show_error!("{}: Numerical result out of range", input.quote()); show_error!("{}: Numerical result out of range", input.quote());
Default::default() v
}
ExtendedParserError::Underflow(v) => {
show_error!("{}: Numerical result out of range", input.quote());
v
} }
ExtendedParserError::NotNumeric => { ExtendedParserError::NotNumeric => {
show_error!("{}: expected a numeric value", input.quote()); show_error!("{}: expected a numeric value", input.quote());

View file

@ -23,6 +23,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::Display; use std::fmt::Display;
use std::ops::Add; use std::ops::Add;
use std::ops::Neg;
use bigdecimal::BigDecimal; use bigdecimal::BigDecimal;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@ -227,6 +228,27 @@ impl PartialOrd for ExtendedBigDecimal {
} }
} }
impl Neg for ExtendedBigDecimal {
type Output = Self;
fn neg(self) -> Self::Output {
match self {
Self::BigDecimal(bd) => {
if bd.is_zero() {
Self::MinusZero
} else {
Self::BigDecimal(bd.neg())
}
}
Self::MinusZero => Self::BigDecimal(BigDecimal::zero()),
Self::Infinity => Self::MinusInfinity,
Self::MinusInfinity => Self::Infinity,
Self::Nan => Self::MinusNan,
Self::MinusNan => Self::Nan,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View file

@ -80,10 +80,12 @@ pub struct SignedInt {
impl Formatter<i64> for SignedInt { impl Formatter<i64> for SignedInt {
fn fmt(&self, writer: impl Write, x: i64) -> std::io::Result<()> { fn fmt(&self, writer: impl Write, x: i64) -> std::io::Result<()> {
// -i64::MIN is actually 1 larger than i64::MAX, so we need to cast to i128 first.
let abs = (x as i128).abs();
let s = if self.precision > 0 { let s = if self.precision > 0 {
format!("{:0>width$}", x.abs(), width = self.precision) format!("{:0>width$}", abs, width = self.precision)
} else { } else {
x.abs().to_string() abs.to_string()
}; };
let sign_indicator = get_sign_indicator(self.positive_sign, x.is_negative()); let sign_indicator = get_sign_indicator(self.positive_sign, x.is_negative());
@ -1046,6 +1048,8 @@ mod test {
let format = Format::<SignedInt, i64>::parse("%d").unwrap(); let format = Format::<SignedInt, i64>::parse("%d").unwrap();
assert_eq!(fmt(&format, 123i64), "123"); assert_eq!(fmt(&format, 123i64), "123");
assert_eq!(fmt(&format, -123i64), "-123"); assert_eq!(fmt(&format, -123i64), "-123");
assert_eq!(fmt(&format, i64::MAX), "9223372036854775807");
assert_eq!(fmt(&format, i64::MIN), "-9223372036854775808");
let format = Format::<SignedInt, i64>::parse("%i").unwrap(); let format = Format::<SignedInt, i64>::parse("%i").unwrap();
assert_eq!(fmt(&format, 123i64), "123"); assert_eq!(fmt(&format, 123i64), "123");

View file

@ -5,12 +5,13 @@
//! Utilities for parsing numbers in various formats //! Utilities for parsing numbers in various formats
// spell-checker:ignore powf copysign prec inity bigdecimal extendedbigdecimal biguint // spell-checker:ignore powf copysign prec inity infinit bigdecimal extendedbigdecimal biguint underflowed
use bigdecimal::{ use bigdecimal::{
BigDecimal, BigDecimal,
num_bigint::{BigInt, BigUint, Sign}, num_bigint::{BigInt, BigUint, Sign},
}; };
use num_traits::Signed;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use num_traits::Zero; use num_traits::Zero;
@ -59,21 +60,53 @@ pub enum ExtendedParserError<'a, T> {
/// The beginning of the input made sense and has been parsed, /// The beginning of the input made sense and has been parsed,
/// while the remaining doesn't. /// while the remaining doesn't.
PartialMatch(T, &'a str), PartialMatch(T, &'a str),
/// The integral part has overflowed the requested type, or /// The value has overflowed the type storage. The returned value
/// has overflowed the `u64` internal storage when parsing the /// is saturated (e.g. positive or negative infinity, or min/max
/// integral part of a floating point number. /// value for the integer type).
Overflow, Overflow(T),
// The value has underflowed the float storage (and is now 0.0 or -0.0).
// Does not apply to integer parsing.
Underflow(T),
} }
impl<'a, T> ExtendedParserError<'a, T> { impl<'a, T> ExtendedParserError<'a, T>
where
T: Zero,
{
// Extract the value out of an error, if possible.
fn extract(self) -> T {
match self {
Self::NotNumeric => T::zero(),
Self::PartialMatch(v, _) => v,
Self::Overflow(v) => v,
Self::Underflow(v) => v,
}
}
// Map an error to another, using the provided conversion function.
// The error (self) takes precedence over errors happening during the
// conversion.
fn map<U>( fn map<U>(
self, self,
f: impl FnOnce(T, &'a str) -> ExtendedParserError<'a, U>, f: impl FnOnce(T) -> Result<U, ExtendedParserError<'a, U>>,
) -> ExtendedParserError<'a, U> { ) -> ExtendedParserError<'a, U>
where
U: Zero,
{
fn extract<U>(v: Result<U, ExtendedParserError<'_, U>>) -> U
where
U: Zero,
{
v.unwrap_or_else(|e| e.extract())
}
match self { match self {
Self::NotNumeric => ExtendedParserError::NotNumeric, ExtendedParserError::NotNumeric => ExtendedParserError::NotNumeric,
Self::Overflow => ExtendedParserError::Overflow, ExtendedParserError::PartialMatch(v, rest) => {
Self::PartialMatch(v, s) => f(v, s), ExtendedParserError::PartialMatch(extract(f(v)), rest)
}
ExtendedParserError::Overflow(v) => ExtendedParserError::Overflow(extract(f(v))),
ExtendedParserError::Underflow(v) => ExtendedParserError::Underflow(extract(f(v))),
} }
} }
} }
@ -92,28 +125,34 @@ pub trait ExtendedParser {
impl ExtendedParser for i64 { impl ExtendedParser for i64 {
/// Parse a number as i64. No fractional part is allowed. /// Parse a number as i64. No fractional part is allowed.
fn extended_parse(input: &str) -> Result<i64, ExtendedParserError<'_, i64>> { fn extended_parse(input: &str) -> Result<i64, ExtendedParserError<'_, i64>> {
fn into_i64(ebd: ExtendedBigDecimal) -> Option<i64> { fn into_i64<'a>(ebd: ExtendedBigDecimal) -> Result<i64, ExtendedParserError<'a, i64>> {
match ebd { match ebd {
ExtendedBigDecimal::BigDecimal(bd) => { ExtendedBigDecimal::BigDecimal(bd) => {
let (digits, scale) = bd.into_bigint_and_scale(); let (digits, scale) = bd.into_bigint_and_scale();
if scale == 0 { if scale == 0 {
i64::try_from(digits).ok() let negative = digits.sign() == Sign::Minus;
match i64::try_from(digits) {
Ok(i) => Ok(i),
_ => Err(ExtendedParserError::Overflow(if negative {
i64::MIN
} else { } else {
None i64::MAX
})),
}
} else {
// Should not happen.
Err(ExtendedParserError::NotNumeric)
} }
} }
ExtendedBigDecimal::MinusZero => Some(0), ExtendedBigDecimal::MinusZero => Ok(0),
_ => None, // No other case should not happen.
_ => Err(ExtendedParserError::NotNumeric),
} }
} }
match parse(input, true) { match parse(input, true) {
Ok(v) => into_i64(v).ok_or(ExtendedParserError::Overflow), Ok(v) => into_i64(v),
Err(e) => Err(e.map(|v, rest| { Err(e) => Err(e.map(into_i64)),
into_i64(v)
.map(|v| ExtendedParserError::PartialMatch(v, rest))
.unwrap_or(ExtendedParserError::Overflow)
})),
} }
} }
} }
@ -121,27 +160,35 @@ impl ExtendedParser for i64 {
impl ExtendedParser for u64 { impl ExtendedParser for u64 {
/// Parse a number as u64. No fractional part is allowed. /// Parse a number as u64. No fractional part is allowed.
fn extended_parse(input: &str) -> Result<u64, ExtendedParserError<'_, u64>> { fn extended_parse(input: &str) -> Result<u64, ExtendedParserError<'_, u64>> {
fn into_u64(ebd: ExtendedBigDecimal) -> Option<u64> { fn into_u64<'a>(ebd: ExtendedBigDecimal) -> Result<u64, ExtendedParserError<'a, u64>> {
match ebd { match ebd {
ExtendedBigDecimal::BigDecimal(bd) => { ExtendedBigDecimal::BigDecimal(bd) => {
let (digits, scale) = bd.into_bigint_and_scale(); let (digits, scale) = bd.into_bigint_and_scale();
if scale == 0 { if scale == 0 {
u64::try_from(digits).ok() let negative = digits.sign() == Sign::Minus;
match u64::try_from(digits) {
Ok(i) => Ok(i),
_ => Err(ExtendedParserError::Overflow(if negative {
// TODO: We should wrap around here #7488
0
} else { } else {
None u64::MAX
})),
}
} else {
// Should not happen.
Err(ExtendedParserError::NotNumeric)
} }
} }
_ => None, // TODO: Handle -0 too #7488
// No other case should not happen.
_ => Err(ExtendedParserError::NotNumeric),
} }
} }
match parse(input, true) { match parse(input, true) {
Ok(v) => into_u64(v).ok_or(ExtendedParserError::Overflow), Ok(v) => into_u64(v),
Err(e) => Err(e.map(|v, rest| { Err(e) => Err(e.map(into_u64)),
into_u64(v)
.map(|v| ExtendedParserError::PartialMatch(v, rest))
.unwrap_or(ExtendedParserError::Overflow)
})),
} }
} }
} }
@ -149,21 +196,31 @@ impl ExtendedParser for u64 {
impl ExtendedParser for f64 { impl ExtendedParser for f64 {
/// Parse a number as f64 /// Parse a number as f64
fn extended_parse(input: &str) -> Result<f64, ExtendedParserError<'_, 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<'a>(ebd: ExtendedBigDecimal) -> Result<f64, ExtendedParserError<'a, f64>> {
fn into_f64(ebd: ExtendedBigDecimal) -> f64 { // TODO: _Some_ of this is generic, so this should probably be implemented as an ExtendedBigDecimal trait (ToPrimitive).
match ebd { let v = match ebd {
ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(), ExtendedBigDecimal::BigDecimal(bd) => {
let f = bd.to_f64().unwrap();
if f.is_infinite() {
return Err(ExtendedParserError::Overflow(f));
}
if f.is_zero() && !bd.is_zero() {
return Err(ExtendedParserError::Underflow(f));
}
f
}
ExtendedBigDecimal::MinusZero => -0.0, ExtendedBigDecimal::MinusZero => -0.0,
ExtendedBigDecimal::Nan => f64::NAN, ExtendedBigDecimal::Nan => f64::NAN,
ExtendedBigDecimal::MinusNan => -f64::NAN, ExtendedBigDecimal::MinusNan => -f64::NAN,
ExtendedBigDecimal::Infinity => f64::INFINITY, ExtendedBigDecimal::Infinity => f64::INFINITY,
ExtendedBigDecimal::MinusInfinity => -f64::INFINITY, ExtendedBigDecimal::MinusInfinity => -f64::INFINITY,
} };
Ok(v)
} }
match parse(input, false) { match parse(input, false) {
Ok(v) => Ok(into_f64(v)), Ok(v) => into_f64(v),
Err(e) => Err(e.map(|v, rest| ExtendedParserError::PartialMatch(into_f64(v), rest))), Err(e) => Err(e.map(into_f64)),
} }
} }
} }
@ -181,35 +238,115 @@ fn parse_special_value(
input: &str, input: &str,
negative: bool, negative: bool,
) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> { ) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> {
let prefix = input let input_lc = input.to_ascii_lowercase();
.chars()
.take(3) // Array of ("String to match", return value when sign positive, when sign negative)
.map(|c| c.to_ascii_lowercase()) const MATCH_TABLE: &[(&str, ExtendedBigDecimal)] = &[
.collect::<String>(); ("infinity", ExtendedBigDecimal::Infinity),
let special = match prefix.as_str() { ("inf", ExtendedBigDecimal::Infinity),
"inf" => { ("nan", ExtendedBigDecimal::Nan),
];
for (str, ebd) in MATCH_TABLE.iter() {
if input_lc.starts_with(str) {
let mut special = ebd.clone();
if negative { if negative {
ExtendedBigDecimal::MinusInfinity special = -special;
} else {
ExtendedBigDecimal::Infinity
} }
} let match_len = str.len();
"nan" => { return if input.len() == match_len {
if negative {
ExtendedBigDecimal::MinusNan
} else {
ExtendedBigDecimal::Nan
}
}
_ => return Err(ExtendedParserError::NotNumeric),
};
if input.len() == 3 {
Ok(special) Ok(special)
} else { } else {
Err(ExtendedParserError::PartialMatch(special, &input[3..])) Err(ExtendedParserError::PartialMatch(
special,
&input[match_len..],
))
};
} }
} }
Err(ExtendedParserError::NotNumeric)
}
// Underflow/Overflow errors always contain 0 or infinity.
// overflow: true for overflow, false for underflow.
fn make_error<'a>(overflow: bool, negative: bool) -> ExtendedParserError<'a, ExtendedBigDecimal> {
let mut v = if overflow {
ExtendedBigDecimal::Infinity
} else {
ExtendedBigDecimal::zero()
};
if negative {
v = -v;
}
if overflow {
ExtendedParserError::Overflow(v)
} else {
ExtendedParserError::Underflow(v)
}
}
// Construct an ExtendedBigDecimal based on parsed data
fn construct_extended_big_decimal<'a>(
digits: BigUint,
negative: bool,
base: Base,
scale: u64,
exponent: BigInt,
) -> Result<ExtendedBigDecimal, ExtendedParserError<'a, ExtendedBigDecimal>> {
if digits == BigUint::zero() && negative {
return Ok(ExtendedBigDecimal::MinusZero);
}
let sign = if negative { Sign::Minus } else { Sign::Plus };
let signed_digits = BigInt::from_biguint(sign, digits);
let bd = if scale == 0 && exponent.is_zero() {
BigDecimal::from_bigint(signed_digits, 0)
} else if base == Base::Decimal {
let new_scale = BigInt::from(scale) - exponent;
// BigDecimal "only" supports i64 scale.
// Note that new_scale is a negative exponent: large value causes an underflow, small value an overflow.
if new_scale > i64::MAX.into() {
return Err(make_error(false, negative));
} else if new_scale < i64::MIN.into() {
return Err(make_error(true, negative));
}
BigDecimal::from_bigint(signed_digits, new_scale.to_i64().unwrap())
} else if base == Base::Hexadecimal {
// pow "only" supports u32 values, just error out if given more than 2**32 fractional digits.
if scale > u32::MAX.into() {
return Err(ExtendedParserError::NotNumeric);
}
// 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);
let abs_exponent = exponent.abs();
// Again, pow "only" supports u32 values. Just overflow/underflow if the value provided
// is > 2**32 or < 2**-32.
if abs_exponent > u32::MAX.into() {
return Err(make_error(exponent.is_positive(), negative));
}
// Confusingly, exponent is in base 2 for hex floating point numbers.
// Note: We cannot overflow/underflow BigDecimal here, as we will not be able to reach the
// maximum/minimum scale (i64 range).
let pow2 = BigDecimal::from_bigint(BigInt::from(2).pow(abs_exponent.to_u32().unwrap()), 0);
if !exponent.is_negative() {
bd * pow2
} else {
bd / pow2
}
} else {
// scale != 0, which means that integral_only is not set, so only base 10 and 16 are allowed.
unreachable!();
};
Ok(ExtendedBigDecimal::BigDecimal(bd))
}
// TODO: As highlighted by clippy, this function _is_ high cognitive complexity, jumps // 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. // around between integer and float parsing, and should be split in multiple parts.
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
@ -269,7 +406,7 @@ fn parse(
let mut chars = rest.chars().enumerate().fuse().peekable(); let mut chars = rest.chars().enumerate().fuse().peekable();
let mut digits = BigUint::zero(); let mut digits = BigUint::zero();
let mut scale = 0u64; let mut scale = 0u64;
let mut exponent = 0i64; let mut exponent = BigInt::zero();
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) { while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
chars.next(); chars.next();
digits = digits * base as u8 + d; digits = digits * base as u8 + d;
@ -294,7 +431,10 @@ fn parse(
}; };
// Parse the exponent part, only decimal numbers are allowed. // Parse the exponent part, only decimal numbers are allowed.
if chars.peek().is_some_and(|&(_, c)| c == exp_char) { if chars
.peek()
.is_some_and(|&(_, c)| c.to_ascii_lowercase() == exp_char)
{
chars.next(); chars.next();
let exp_negative = match chars.peek() { let exp_negative = match chars.peek() {
Some((_, '-')) => { Some((_, '-')) => {
@ -326,42 +466,17 @@ fn parse(
} }
} }
// TODO: Might be nice to implement a ExtendedBigDecimal copysign or negation function to move away some of this logic... let ebd_result = construct_extended_big_decimal(digits, negative, base, scale, exponent);
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 // Return what has been parsed so far. It there are extra characters, mark the
// parsing as a partial match. // parsing as a partial match.
if let Some((first_unparsed, _)) = chars.next() { if let Some((first_unparsed, _)) = chars.next() {
Err(ExtendedParserError::PartialMatch( Err(ExtendedParserError::PartialMatch(
ebd, ebd_result.unwrap_or_else(|e| e.extract()),
&rest[first_unparsed..], &rest[first_unparsed..],
)) ))
} else { } else {
Ok(ebd) ebd_result
} }
} }
@ -379,9 +494,10 @@ mod tests {
fn test_decimal_u64() { fn test_decimal_u64() {
assert_eq!(Ok(123), u64::extended_parse("123")); assert_eq!(Ok(123), u64::extended_parse("123"));
assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX))); assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX)));
// TODO: We should wrap around here #7488
assert!(matches!( assert!(matches!(
u64::extended_parse("-123"), u64::extended_parse("-123"),
Err(ExtendedParserError::Overflow) Err(ExtendedParserError::Overflow(0))
)); ));
assert!(matches!( assert!(matches!(
u64::extended_parse(""), u64::extended_parse(""),
@ -410,16 +526,24 @@ mod tests {
assert_eq!(Ok(i64::MIN), i64::extended_parse(&format!("{}", i64::MIN))); assert_eq!(Ok(i64::MIN), i64::extended_parse(&format!("{}", i64::MIN)));
assert!(matches!( assert!(matches!(
i64::extended_parse(&format!("{}", u64::MAX)), i64::extended_parse(&format!("{}", u64::MAX)),
Err(ExtendedParserError::Overflow) Err(ExtendedParserError::Overflow(i64::MAX))
)); ));
assert!(matches!( assert!(matches!(
i64::extended_parse(&format!("{}", i64::MAX as u64 + 1)), i64::extended_parse(&format!("{}", i64::MAX as u64 + 1)),
Err(ExtendedParserError::Overflow) Err(ExtendedParserError::Overflow(i64::MAX))
)); ));
assert!(matches!( assert!(matches!(
i64::extended_parse("-123e10"), i64::extended_parse("-123e10"),
Err(ExtendedParserError::PartialMatch(-123, "e10")) Err(ExtendedParserError::PartialMatch(-123, "e10"))
)); ));
assert!(matches!(
i64::extended_parse(&format!("{}", -(u64::MAX as i128))),
Err(ExtendedParserError::Overflow(i64::MIN))
));
assert!(matches!(
i64::extended_parse(&format!("{}", i64::MIN as i128 - 1)),
Err(ExtendedParserError::Overflow(i64::MIN))
));
} }
#[test] #[test]
@ -448,8 +572,8 @@ mod tests {
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.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(12315000.0), f64::extended_parse("123.15E+5"));
assert_eq!(Ok(0.0012315), f64::extended_parse("123.15e-5")); assert_eq!(Ok(0.0012315), f64::extended_parse("123.15E-5"));
assert_eq!( assert_eq!(
Ok(0.15), Ok(0.15),
f64::extended_parse(".150000000000000000000000000231313") f64::extended_parse(".150000000000000000000000000231313")
@ -467,6 +591,9 @@ mod tests {
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_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_eq!(Ok(f64::INFINITY), f64::extended_parse("infinity"));
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("+infiNIty"));
assert_eq!(Ok(f64::NEG_INFINITY), f64::extended_parse("-INfinity"));
assert!(f64::extended_parse("NaN").unwrap().is_nan()); assert!(f64::extended_parse("NaN").unwrap().is_nan());
assert!(f64::extended_parse("NaN").unwrap().is_sign_positive()); assert!(f64::extended_parse("NaN").unwrap().is_sign_positive());
assert!(f64::extended_parse("+NaN").unwrap().is_nan()); assert!(f64::extended_parse("+NaN").unwrap().is_nan());
@ -477,10 +604,29 @@ mod tests {
assert!(f64::extended_parse("nan").unwrap().is_sign_positive()); assert!(f64::extended_parse("nan").unwrap().is_sign_positive());
assert!(f64::extended_parse("NAN").unwrap().is_nan()); assert!(f64::extended_parse("NAN").unwrap().is_nan());
assert!(f64::extended_parse("NAN").unwrap().is_sign_positive()); assert!(f64::extended_parse("NAN").unwrap().is_sign_positive());
assert!(matches!(f64::extended_parse("-infinity"), assert!(matches!(f64::extended_parse("-infinit"),
Err(ExtendedParserError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY)); Err(ExtendedParserError::PartialMatch(f, "init")) if f == f64::NEG_INFINITY));
assert!(matches!(f64::extended_parse("-infinity00"),
Err(ExtendedParserError::PartialMatch(f, "00")) if f == f64::NEG_INFINITY));
assert!(f64::extended_parse(&format!("{}", u64::MAX)).is_ok()); assert!(f64::extended_parse(&format!("{}", u64::MAX)).is_ok());
assert!(f64::extended_parse(&format!("{}", i64::MIN)).is_ok()); assert!(f64::extended_parse(&format!("{}", i64::MIN)).is_ok());
// f64 overflow/underflow
assert!(matches!(
f64::extended_parse("1.0e9000"),
Err(ExtendedParserError::Overflow(f64::INFINITY))
));
assert!(matches!(
f64::extended_parse("-10.0e9000"),
Err(ExtendedParserError::Overflow(f64::NEG_INFINITY))
));
assert!(matches!(
f64::extended_parse("1.0e-9000"),
Err(ExtendedParserError::Underflow(0.0))
));
assert!(matches!(
f64::extended_parse("-1.0e-9000"),
Err(ExtendedParserError::Underflow(f)) if f == 0.0 && f.is_sign_negative()));
} }
#[test] #[test]
@ -511,7 +657,7 @@ mod tests {
12315.into(), 12315.into(),
102 102
))), ))),
ExtendedBigDecimal::extended_parse("123.15e-100") ExtendedBigDecimal::extended_parse("123.15E-100")
); );
// Very high precision that would not fit in a f64. // Very high precision that would not fit in a f64.
assert_eq!( assert_eq!(
@ -544,6 +690,28 @@ mod tests {
ExtendedBigDecimal::extended_parse("-0.0"), ExtendedBigDecimal::extended_parse("-0.0"),
Ok(ExtendedBigDecimal::MinusZero) Ok(ExtendedBigDecimal::MinusZero)
)); ));
// ExtendedBigDecimal overflow/underflow
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("1e{}", i64::MAX as u64 + 2)),
Err(ExtendedParserError::Overflow(ExtendedBigDecimal::Infinity))
));
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("-0.1e{}", i64::MAX as u64 + 3)),
Err(ExtendedParserError::Overflow(
ExtendedBigDecimal::MinusInfinity
))
));
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("1e{}", i64::MIN)),
Err(ExtendedParserError::Underflow(ebd)) if ebd == ExtendedBigDecimal::zero()
));
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("-0.01e{}", i64::MIN + 2)),
Err(ExtendedParserError::Underflow(
ExtendedBigDecimal::MinusZero
))
));
} }
#[test] #[test]
@ -558,7 +726,7 @@ mod tests {
assert_eq!(Ok(0.0625), f64::extended_parse("0x.1")); 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(15.007_812_5), f64::extended_parse("0xf.02"));
assert_eq!(Ok(16.0), f64::extended_parse("0x0.8p5")); assert_eq!(Ok(16.0), f64::extended_parse("0x0.8p5"));
assert_eq!(Ok(0.0625), f64::extended_parse("0x1p-4")); 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... // 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 // but we can check that the number still gets parsed properly: 0x0.8e5 is 0x8e5 / 16**3
@ -578,6 +746,28 @@ mod tests {
)), )),
ExtendedBigDecimal::extended_parse("0xf.fffffffffffffffffffff") ExtendedBigDecimal::extended_parse("0xf.fffffffffffffffffffff")
); );
// ExtendedBigDecimal overflow/underflow
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("0x1p{}", u32::MAX as u64 + 1)),
Err(ExtendedParserError::Overflow(ExtendedBigDecimal::Infinity))
));
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("-0x100P{}", u32::MAX as u64 + 1)),
Err(ExtendedParserError::Overflow(
ExtendedBigDecimal::MinusInfinity
))
));
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("0x1p-{}", u32::MAX as u64 + 1)),
Err(ExtendedParserError::Underflow(ebd)) if ebd == ExtendedBigDecimal::zero()
));
assert!(matches!(
ExtendedBigDecimal::extended_parse(&format!("-0x0.100p-{}", u32::MAX as u64 + 1)),
Err(ExtendedParserError::Underflow(
ExtendedBigDecimal::MinusZero
))
));
} }
#[test] #[test]

View file

@ -675,6 +675,19 @@ fn test_overflow() {
new_ucmd!() new_ucmd!()
.args(&["%d", "36893488147419103232"]) .args(&["%d", "36893488147419103232"])
.fails_with_code(1) .fails_with_code(1)
.stdout_is("9223372036854775807")
.stderr_is("printf: '36893488147419103232': Numerical result out of range\n");
new_ucmd!()
.args(&["%d", "-36893488147419103232"])
.fails_with_code(1)
.stdout_is("-9223372036854775808")
.stderr_is("printf: '-36893488147419103232': Numerical result out of range\n");
new_ucmd!()
.args(&["%u", "36893488147419103232"])
.fails_with_code(1)
.stdout_is("18446744073709551615")
.stderr_is("printf: '36893488147419103232': Numerical result out of range\n"); .stderr_is("printf: '36893488147419103232': Numerical result out of range\n");
} }