1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +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 {
ExtendedParserError::Overflow => {
ExtendedParserError::Overflow(v) => {
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 => {
show_error!("{}: expected a numeric value", input.quote());

View file

@ -23,6 +23,7 @@
use std::cmp::Ordering;
use std::fmt::Display;
use std::ops::Add;
use std::ops::Neg;
use bigdecimal::BigDecimal;
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)]
mod tests {

View file

@ -80,10 +80,12 @@ pub struct SignedInt {
impl Formatter<i64> for SignedInt {
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 {
format!("{:0>width$}", x.abs(), width = self.precision)
format!("{:0>width$}", abs, width = self.precision)
} else {
x.abs().to_string()
abs.to_string()
};
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();
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();
assert_eq!(fmt(&format, 123i64), "123");

View file

@ -5,12 +5,13 @@
//! 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::{
BigDecimal,
num_bigint::{BigInt, BigUint, Sign},
};
use num_traits::Signed;
use num_traits::ToPrimitive;
use num_traits::Zero;
@ -59,21 +60,53 @@ pub enum ExtendedParserError<'a, T> {
/// The beginning of the input made sense and has been parsed,
/// while the remaining doesn't.
PartialMatch(T, &'a str),
/// The integral part has overflowed the requested type, or
/// has overflowed the `u64` internal storage when parsing the
/// integral part of a floating point number.
Overflow,
/// The value has overflowed the type storage. The returned value
/// is saturated (e.g. positive or negative infinity, or min/max
/// value for the integer type).
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>(
self,
f: impl FnOnce(T, &'a str) -> ExtendedParserError<'a, U>,
) -> ExtendedParserError<'a, U> {
f: impl FnOnce(T) -> Result<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 {
Self::NotNumeric => ExtendedParserError::NotNumeric,
Self::Overflow => ExtendedParserError::Overflow,
Self::PartialMatch(v, s) => f(v, s),
ExtendedParserError::NotNumeric => ExtendedParserError::NotNumeric,
ExtendedParserError::PartialMatch(v, rest) => {
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 {
/// Parse a number as i64. No fractional part is allowed.
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 {
ExtendedBigDecimal::BigDecimal(bd) => {
let (digits, scale) = bd.into_bigint_and_scale();
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 {
None
i64::MAX
})),
}
} else {
// Should not happen.
Err(ExtendedParserError::NotNumeric)
}
}
ExtendedBigDecimal::MinusZero => Some(0),
_ => None,
ExtendedBigDecimal::MinusZero => Ok(0),
// No other case should not happen.
_ => Err(ExtendedParserError::NotNumeric),
}
}
match parse(input, true) {
Ok(v) => into_i64(v).ok_or(ExtendedParserError::Overflow),
Err(e) => Err(e.map(|v, rest| {
into_i64(v)
.map(|v| ExtendedParserError::PartialMatch(v, rest))
.unwrap_or(ExtendedParserError::Overflow)
})),
Ok(v) => into_i64(v),
Err(e) => Err(e.map(into_i64)),
}
}
}
@ -121,27 +160,35 @@ impl ExtendedParser for i64 {
impl ExtendedParser for u64 {
/// Parse a number as u64. No fractional part is allowed.
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 {
ExtendedBigDecimal::BigDecimal(bd) => {
let (digits, scale) = bd.into_bigint_and_scale();
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 {
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) {
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)
})),
Ok(v) => into_u64(v),
Err(e) => Err(e.map(into_u64)),
}
}
}
@ -149,21 +196,31 @@ impl ExtendedParser for u64 {
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(),
fn into_f64<'a>(ebd: ExtendedBigDecimal) -> Result<f64, ExtendedParserError<'a, f64>> {
// TODO: _Some_ of this is generic, so this should probably be implemented as an ExtendedBigDecimal trait (ToPrimitive).
let v = match ebd {
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::Nan => f64::NAN,
ExtendedBigDecimal::MinusNan => -f64::NAN,
ExtendedBigDecimal::Infinity => f64::INFINITY,
ExtendedBigDecimal::MinusInfinity => -f64::INFINITY,
}
};
Ok(v)
}
match parse(input, false) {
Ok(v) => Ok(into_f64(v)),
Err(e) => Err(e.map(|v, rest| ExtendedParserError::PartialMatch(into_f64(v), rest))),
Ok(v) => into_f64(v),
Err(e) => Err(e.map(into_f64)),
}
}
}
@ -181,33 +238,113 @@ 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" => {
let input_lc = input.to_ascii_lowercase();
// Array of ("String to match", return value when sign positive, when sign negative)
const MATCH_TABLE: &[(&str, ExtendedBigDecimal)] = &[
("infinity", ExtendedBigDecimal::Infinity),
("inf", ExtendedBigDecimal::Infinity),
("nan", ExtendedBigDecimal::Nan),
];
for (str, ebd) in MATCH_TABLE.iter() {
if input_lc.starts_with(str) {
let mut special = ebd.clone();
if negative {
ExtendedBigDecimal::MinusInfinity
} else {
ExtendedBigDecimal::Infinity
special = -special;
}
}
"nan" => {
if negative {
ExtendedBigDecimal::MinusNan
} else {
ExtendedBigDecimal::Nan
}
}
_ => return Err(ExtendedParserError::NotNumeric),
};
if input.len() == 3 {
let match_len = str.len();
return if input.len() == match_len {
Ok(special)
} 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
@ -269,7 +406,7 @@ fn parse(
let mut chars = rest.chars().enumerate().fuse().peekable();
let mut digits = BigUint::zero();
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)) {
chars.next();
digits = digits * base as u8 + d;
@ -294,7 +431,10 @@ fn parse(
};
// 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();
let exp_negative = match chars.peek() {
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 = 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)
};
let ebd_result = construct_extended_big_decimal(digits, negative, base, scale, exponent);
// Return what has been parsed so far. It there are extra characters, mark the
// parsing as a partial match.
if let Some((first_unparsed, _)) = chars.next() {
Err(ExtendedParserError::PartialMatch(
ebd,
ebd_result.unwrap_or_else(|e| e.extract()),
&rest[first_unparsed..],
))
} else {
Ok(ebd)
ebd_result
}
}
@ -379,9 +494,10 @@ mod tests {
fn test_decimal_u64() {
assert_eq!(Ok(123), u64::extended_parse("123"));
assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX)));
// TODO: We should wrap around here #7488
assert!(matches!(
u64::extended_parse("-123"),
Err(ExtendedParserError::Overflow)
Err(ExtendedParserError::Overflow(0))
));
assert!(matches!(
u64::extended_parse(""),
@ -410,16 +526,24 @@ mod tests {
assert_eq!(Ok(i64::MIN), i64::extended_parse(&format!("{}", i64::MIN)));
assert!(matches!(
i64::extended_parse(&format!("{}", u64::MAX)),
Err(ExtendedParserError::Overflow)
Err(ExtendedParserError::Overflow(i64::MAX))
));
assert!(matches!(
i64::extended_parse(&format!("{}", i64::MAX as u64 + 1)),
Err(ExtendedParserError::Overflow)
Err(ExtendedParserError::Overflow(i64::MAX))
));
assert!(matches!(
i64::extended_parse("-123e10"),
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]
@ -448,8 +572,8 @@ mod tests {
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(12315000.0), f64::extended_parse("123.15E+5"));
assert_eq!(Ok(0.0012315), f64::extended_parse("123.15E-5"));
assert_eq!(
Ok(0.15),
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("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_sign_positive());
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_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!(matches!(f64::extended_parse("-infinit"),
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!("{}", 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]
@ -511,7 +657,7 @@ mod tests {
12315.into(),
102
))),
ExtendedBigDecimal::extended_parse("123.15e-100")
ExtendedBigDecimal::extended_parse("123.15E-100")
);
// Very high precision that would not fit in a f64.
assert_eq!(
@ -544,6 +690,28 @@ mod tests {
ExtendedBigDecimal::extended_parse("-0.0"),
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]
@ -558,7 +726,7 @@ mod tests {
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"));
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
@ -578,6 +746,28 @@ mod tests {
)),
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]

View file

@ -675,6 +675,19 @@ fn test_overflow() {
new_ucmd!()
.args(&["%d", "36893488147419103232"])
.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");
}