diff --git a/src/uu/seq/src/hexadecimalfloat.rs b/src/uu/seq/src/hexadecimalfloat.rs new file mode 100644 index 00000000000..e98074dd928 --- /dev/null +++ b/src/uu/seq/src/hexadecimalfloat.rs @@ -0,0 +1,404 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore extendedbigdecimal bigdecimal hexdigit numberparse +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::number::PreciseNumber; +use crate::numberparse::ParseNumberError; +use bigdecimal::BigDecimal; +use num_traits::FromPrimitive; + +/// The base of the hex number system +const HEX_RADIX: u32 = 16; + +/// Parse a number from a floating-point hexadecimal exponent notation. +/// +/// # Errors +/// Returns [`Err`] if: +/// - the input string is not a valid hexadecimal string +/// - the input data can't be interpreted as ['f64'] or ['BigDecimal'] +/// +/// # Examples +/// +/// ```rust,ignore +/// let input = "0x1.4p-2"; +/// let expected = 0.3125; +/// match input.parse_number::().unwrap().number { +/// ExtendedBigDecimal::BigDecimal(bd) => assert_eq!(bd.to_f64().unwrap(),expected), +/// _ => unreachable!() +/// }; +/// ``` +pub fn parse_number(s: &str) -> Result { + // Parse floating point parts + let (sign, remain) = parse_sign_multiplier(s.trim())?; + let remain = parse_hex_prefix(remain)?; + let (integral_part, remain) = parse_integral_part(remain)?; + let (fractional_part, remain) = parse_fractional_part(remain)?; + let (exponent_part, remain) = parse_exponent_part(remain)?; + + // Check parts. Rise error if: + // - The input string is not fully consumed + // - Only integral part is presented + // - Only exponent part is presented + // - All 3 parts are empty + match ( + integral_part, + fractional_part, + exponent_part, + remain.is_empty(), + ) { + (_, _, _, false) + | (Some(_), None, None, _) + | (None, None, Some(_), _) + | (None, None, None, _) => return Err(ParseNumberError::Float), + _ => (), + }; + + // Build a number from parts + let integral_value = integral_part.unwrap_or(0.0); + let fractional_value = fractional_part.unwrap_or(0.0); + let exponent_value = (2.0_f64).powi(exponent_part.unwrap_or(0)); + let value = sign * (integral_value + fractional_value) * exponent_value; + + // Build a PreciseNumber + let number = BigDecimal::from_f64(value).ok_or(ParseNumberError::Float)?; + let num_fractional_digits = number.fractional_digit_count().max(0) as u64; + let num_integral_digits = if value.abs() < 1.0 { + 0 + } else { + number.digits() - num_fractional_digits + }; + let num_integral_digits = num_integral_digits + if sign < 0.0 { 1 } else { 0 }; + + Ok(PreciseNumber::new( + ExtendedBigDecimal::BigDecimal(number), + num_integral_digits as usize, + num_fractional_digits as usize, + )) +} + +// Detect number precision similar to GNU coreutils. Refer to scan_arg in seq.c. There are still +// some differences from the GNU version, but this should be sufficient to test the idea. +pub fn parse_precision(s: &str) -> Option { + let hex_index = s.find(['x', 'X']); + let point_index = s.find('.'); + + if hex_index.is_some() { + // Hex value. Returns: + // - 0 for a hexadecimal integer (filled above) + // - None for a hexadecimal floating-point number (the default value of precision) + let power_index = s.find(['p', 'P']); + if point_index.is_none() && power_index.is_none() { + // No decimal point and no 'p' (power) => integer => precision = 0 + return Some(0); + } else { + return None; + } + } + + // This is a decimal floating point. The precision depends on two parameters: + // - the number of fractional digits + // - the exponent + // Let's detect the number of fractional digits + let fractional_length = if let Some(point_index) = point_index { + s[point_index + 1..] + .chars() + .take_while(|c| c.is_ascii_digit()) + .count() + } else { + 0 + }; + + let mut precision = Some(fractional_length); + + // Let's update the precision if exponent is present + if let Some(exponent_index) = s.find(['e', 'E']) { + let exponent_value: i32 = s[exponent_index + 1..].parse().unwrap_or(0); + if exponent_value < 0 { + precision = precision.map(|p| p + exponent_value.unsigned_abs() as usize); + } else { + precision = precision.map(|p| p - p.min(exponent_value as usize)); + } + } + precision +} + +/// Parse the sign multiplier. +/// +/// If a sign is present, the function reads and converts it into a multiplier. +/// If no sign is present, a multiplier of 1.0 is used. +/// +/// # Errors +/// +/// Returns [`Err`] if the input string does not start with a recognized sign or '0' symbol. +fn parse_sign_multiplier(s: &str) -> Result<(f64, &str), ParseNumberError> { + if let Some(remain) = s.strip_prefix('-') { + Ok((-1.0, remain)) + } else if let Some(remain) = s.strip_prefix('+') { + Ok((1.0, remain)) + } else if s.starts_with('0') { + Ok((1.0, s)) + } else { + Err(ParseNumberError::Float) + } +} + +/// Parses the `0x` prefix in a case-insensitive manner. +/// +/// # Errors +/// +/// Returns [`Err`] if the input string does not contain the required prefix. +fn parse_hex_prefix(s: &str) -> Result<&str, ParseNumberError> { + if !(s.starts_with("0x") || s.starts_with("0X")) { + return Err(ParseNumberError::Float); + } + Ok(&s[2..]) +} + +/// Parse the integral part in hexadecimal notation. +/// +/// The integral part is hexadecimal number located after the '0x' prefix and before '.' or 'p' +/// symbols. For example, the number 0x1.234p2 has an integral part 1. +/// +/// This part is optional. +/// +/// # Errors +/// +/// Returns [`Err`] if the integral part is present but a hexadecimal number cannot be parsed from the input string. +fn parse_integral_part(s: &str) -> Result<(Option, &str), ParseNumberError> { + // This part is optional. Skip parsing if symbol is not a hex digit. + let length = s.chars().take_while(|c| c.is_ascii_hexdigit()).count(); + if length > 0 { + let integer = + u64::from_str_radix(&s[..length], HEX_RADIX).map_err(|_| ParseNumberError::Float)?; + Ok((Some(integer as f64), &s[length..])) + } else { + Ok((None, s)) + } +} + +/// Parse the fractional part in hexadecimal notation. +/// +/// The function calculates the sum of the digits after the '.' (dot) sign. Each Nth digit is +/// interpreted as digit / 16^n, where n represents the position after the dot starting from 1. +/// +/// For example, the number 0x1.234p2 has a fractional part 234, which can be interpreted as +/// 2/16^1 + 3/16^2 + 4/16^3, where 16 is the radix of the hexadecimal number system. This equals +/// 0.125 + 0.01171875 + 0.0009765625 = 0.1376953125 in decimal. And this is exactly what the +/// function does. +/// +/// This part is optional. +/// +/// # Errors +/// +/// Returns [`Err`] if the fractional part is present but a hexadecimal number cannot be parsed from the input string. +fn parse_fractional_part(s: &str) -> Result<(Option, &str), ParseNumberError> { + // This part is optional and follows after the '.' symbol. Skip parsing if the dot is not present. + if !s.starts_with('.') { + return Ok((None, s)); + } + + let s = &s[1..]; + let mut multiplier = 1.0 / HEX_RADIX as f64; + let mut total = 0.0; + let mut length = 0; + + for c in s.chars().take_while(|c| c.is_ascii_hexdigit()) { + let digit = c + .to_digit(HEX_RADIX) + .map(|x| x as u8) + .ok_or(ParseNumberError::Float)?; + total += (digit as f64) * multiplier; + multiplier /= HEX_RADIX as f64; + length += 1; + } + + if length == 0 { + return Err(ParseNumberError::Float); + } + Ok((Some(total), &s[length..])) +} + +/// Parse the exponent part in hexadecimal notation. +/// +/// The exponent part is a decimal number located after the 'p' symbol. +/// For example, the number 0x1.234p2 has an exponent part 2. +/// +/// This part is optional. +/// +/// # Errors +/// +/// Returns [`Err`] if the exponent part is presented but a decimal number cannot be parsed from +/// the input string. +fn parse_exponent_part(s: &str) -> Result<(Option, &str), ParseNumberError> { + // This part is optional and follows after 'p' or 'P' symbols. Skip parsing if the symbols are not present + if !(s.starts_with('p') || s.starts_with('P')) { + return Ok((None, s)); + } + + let s = &s[1..]; + let length = s + .chars() + .take_while(|c| c.is_ascii_digit() || *c == '-' || *c == '+') + .count(); + + if length == 0 { + return Err(ParseNumberError::Float); + } + + let value = s[..length].parse().map_err(|_| ParseNumberError::Float)?; + Ok((Some(value), &s[length..])) +} + +#[cfg(test)] +mod tests { + + use super::{parse_number, parse_precision}; + use crate::{numberparse::ParseNumberError, ExtendedBigDecimal}; + use bigdecimal::BigDecimal; + use num_traits::ToPrimitive; + + fn parse_big_decimal(s: &str) -> Result { + match parse_number(s)?.number { + ExtendedBigDecimal::BigDecimal(bd) => Ok(bd), + _ => Err(ParseNumberError::Float), + } + } + + fn parse_f64(s: &str) -> Result { + parse_big_decimal(s)? + .to_f64() + .ok_or(ParseNumberError::Float) + } + + #[test] + fn test_parse_precise_number_case_insensitive() { + assert_eq!(parse_f64("0x1P1").unwrap(), 2.0); + assert_eq!(parse_f64("0x1p1").unwrap(), 2.0); + } + + #[test] + fn test_parse_precise_number_plus_minus_prefixes() { + assert_eq!(parse_f64("+0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("-0x1p1").unwrap(), -2.0); + } + + #[test] + fn test_parse_precise_number_power_signs() { + assert_eq!(parse_f64("0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("0x1p+1").unwrap(), 2.0); + assert_eq!(parse_f64("0x1p-1").unwrap(), 0.5); + } + + #[test] + fn test_parse_precise_number_hex() { + assert_eq!(parse_f64("0xd.dp-1").unwrap(), 6.90625); + } + + #[test] + fn test_parse_precise_number_no_power() { + assert_eq!(parse_f64("0x123.a").unwrap(), 291.625); + } + + #[test] + fn test_parse_precise_number_no_fractional() { + assert_eq!(parse_f64("0x333p-4").unwrap(), 51.1875); + } + + #[test] + fn test_parse_precise_number_no_integral() { + assert_eq!(parse_f64("0x.9").unwrap(), 0.5625); + assert_eq!(parse_f64("0x.9p2").unwrap(), 2.25); + } + + #[test] + fn test_parse_precise_number_from_valid_values() { + assert_eq!(parse_f64("0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("+0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("-0x1p1").unwrap(), -2.0); + assert_eq!(parse_f64("0x1p-1").unwrap(), 0.5); + assert_eq!(parse_f64("0x1.8").unwrap(), 1.5); + assert_eq!(parse_f64("-0x1.8").unwrap(), -1.5); + assert_eq!(parse_f64("0x1.8p2").unwrap(), 6.0); + assert_eq!(parse_f64("0x1.8p+2").unwrap(), 6.0); + assert_eq!(parse_f64("0x1.8p-2").unwrap(), 0.375); + assert_eq!(parse_f64("0x.8").unwrap(), 0.5); + assert_eq!(parse_f64("0x10p0").unwrap(), 16.0); + assert_eq!(parse_f64("0x0.0").unwrap(), 0.0); + assert_eq!(parse_f64("0x0p0").unwrap(), 0.0); + assert_eq!(parse_f64("0x0.0p0").unwrap(), 0.0); + assert_eq!(parse_f64("-0x.1p-3").unwrap(), -0.0078125); + assert_eq!(parse_f64("-0x.ep-3").unwrap(), -0.109375); + } + + #[test] + fn test_parse_float_from_invalid_values() { + let expected_error = ParseNumberError::Float; + assert_eq!(parse_f64("").unwrap_err(), expected_error); + assert_eq!(parse_f64("1").unwrap_err(), expected_error); + assert_eq!(parse_f64("1p").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x").unwrap_err(), expected_error); + assert_eq!(parse_f64("0xG").unwrap_err(), expected_error); + assert_eq!(parse_f64("0xp").unwrap_err(), expected_error); + assert_eq!(parse_f64("0xp3").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1p").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1p+").unwrap_err(), expected_error); + assert_eq!(parse_f64("-0xx1p1").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.k").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1").unwrap_err(), expected_error); + assert_eq!(parse_f64("-0x1pa").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.1pk").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.8p2z").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1p3.2").unwrap_err(), expected_error); + assert_eq!(parse_f64("-0x.ep-3z").unwrap_err(), expected_error); + } + + #[test] + fn test_parse_precise_number_count_digits() { + let precise_num = parse_number("0x1.2").unwrap(); // 1.125 decimal + assert_eq!(precise_num.num_integral_digits, 1); + assert_eq!(precise_num.num_fractional_digits, 3); + + let precise_num = parse_number("-0x1.2").unwrap(); // -1.125 decimal + assert_eq!(precise_num.num_integral_digits, 2); + assert_eq!(precise_num.num_fractional_digits, 3); + + let precise_num = parse_number("0x123.8").unwrap(); // 291.5 decimal + assert_eq!(precise_num.num_integral_digits, 3); + assert_eq!(precise_num.num_fractional_digits, 1); + + let precise_num = parse_number("-0x123.8").unwrap(); // -291.5 decimal + assert_eq!(precise_num.num_integral_digits, 4); + assert_eq!(precise_num.num_fractional_digits, 1); + } + + #[test] + fn test_parse_precision_valid_values() { + assert_eq!(parse_precision("1"), Some(0)); + assert_eq!(parse_precision("0x1"), Some(0)); + assert_eq!(parse_precision("0x1.1"), None); + assert_eq!(parse_precision("0x1.1p2"), None); + assert_eq!(parse_precision("0x1.1p-2"), None); + assert_eq!(parse_precision(".1"), Some(1)); + assert_eq!(parse_precision("1.1"), Some(1)); + assert_eq!(parse_precision("1.12"), Some(2)); + assert_eq!(parse_precision("1.12345678"), Some(8)); + assert_eq!(parse_precision("1.12345678e-3"), Some(11)); + assert_eq!(parse_precision("1.1e-1"), Some(2)); + assert_eq!(parse_precision("1.1e-3"), Some(4)); + } + + #[test] + fn test_parse_precision_invalid_values() { + // Just to make sure it doesn't crash on incomplete values/bad format + // Good enough for now. + assert_eq!(parse_precision("1."), Some(0)); + assert_eq!(parse_precision("1e"), Some(0)); + assert_eq!(parse_precision("1e-"), Some(0)); + assert_eq!(parse_precision("1e+"), Some(0)); + assert_eq!(parse_precision("1em"), Some(0)); + } +} diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs index 314c842ba15..ec6ac0f1687 100644 --- a/src/uu/seq/src/number.rs +++ b/src/uu/seq/src/number.rs @@ -19,6 +19,8 @@ use crate::extendedbigdecimal::ExtendedBigDecimal; pub struct PreciseNumber { pub number: ExtendedBigDecimal, pub num_integral_digits: usize, + + #[allow(dead_code)] pub num_fractional_digits: usize, } diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index c8dec018041..478622515cc 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore extendedbigdecimal bigdecimal numberparse +// spell-checker:ignore extendedbigdecimal bigdecimal numberparse hexadecimalfloat //! Parsing numbers for use in `seq`. //! //! This module provides an implementation of [`FromStr`] for the @@ -16,6 +16,7 @@ use num_traits::Num; use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::hexadecimalfloat; use crate::number::PreciseNumber; /// An error returned when parsing a number fails. @@ -296,6 +297,14 @@ fn parse_decimal_and_exponent( /// assert_eq!(actual, expected); /// ``` fn parse_hexadecimal(s: &str) -> Result { + if s.find(['.', 'p', 'P']).is_some() { + hexadecimalfloat::parse_number(s) + } else { + parse_hexadecimal_integer(s) + } +} + +fn parse_hexadecimal_integer(s: &str) -> Result { let (is_neg, s) = if s.starts_with('-') { (true, &s[3..]) } else { diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index e14ba35a949..0ee5101d7ef 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) extendedbigdecimal numberparse +// spell-checker:ignore (ToDO) bigdecimal extendedbigdecimal numberparse hexadecimalfloat use std::ffi::OsString; use std::io::{stdout, ErrorKind, Write}; @@ -10,11 +10,13 @@ use clap::{crate_version, Arg, ArgAction, Command}; use num_traits::{ToPrimitive, Zero}; use uucore::error::{FromIo, UResult}; -use uucore::format::{num_format, Format}; +use uucore::format::{num_format, sprintf, Format, FormatArgument}; use uucore::{format_usage, help_about, help_usage}; mod error; mod extendedbigdecimal; +mod hexadecimalfloat; + // public to allow fuzzing #[cfg(fuzzing)] pub mod number; @@ -72,6 +74,18 @@ fn split_short_args_with_value(args: impl uucore::Args) -> impl uucore::Args { v.into_iter() } +fn select_precision( + first: Option, + increment: Option, + last: Option, +) -> Option { + match (first, increment, last) { + (Some(0), Some(0), Some(0)) => Some(0), + (Some(f), Some(i), Some(_)) => Some(f.max(i)), + _ => None, + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(split_short_args_with_value(args))?; @@ -99,32 +113,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { format: matches.get_one::(OPT_FORMAT).map(|s| s.as_str()), }; - let first = if numbers.len() > 1 { + let (first, first_precision) = if numbers.len() > 1 { match numbers[0].parse() { - Ok(num) => num, + Ok(num) => (num, hexadecimalfloat::parse_precision(numbers[0])), Err(e) => return Err(SeqError::ParseError(numbers[0].to_string(), e).into()), } } else { - PreciseNumber::one() + (PreciseNumber::one(), Some(0)) }; - let increment = if numbers.len() > 2 { + let (increment, increment_precision) = if numbers.len() > 2 { match numbers[1].parse() { - Ok(num) => num, + Ok(num) => (num, hexadecimalfloat::parse_precision(numbers[1])), Err(e) => return Err(SeqError::ParseError(numbers[1].to_string(), e).into()), } } else { - PreciseNumber::one() + (PreciseNumber::one(), Some(0)) }; if increment.is_zero() { return Err(SeqError::ZeroIncrement(numbers[1].to_string()).into()); } - let last: PreciseNumber = { + let (last, last_precision): (PreciseNumber, Option) = { // We are guaranteed that `numbers.len()` is greater than zero // and at most three because of the argument specification in // `uu_app()`. let n: usize = numbers.len(); match numbers[n - 1].parse() { - Ok(num) => num, + Ok(num) => (num, hexadecimalfloat::parse_precision(numbers[n - 1])), Err(e) => return Err(SeqError::ParseError(numbers[n - 1].to_string(), e).into()), } }; @@ -133,9 +147,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .num_integral_digits .max(increment.num_integral_digits) .max(last.num_integral_digits); - let largest_dec = first - .num_fractional_digits - .max(increment.num_fractional_digits); + + let precision = select_precision(first_precision, increment_precision, last_precision); let format = match options.format { Some(f) => { @@ -146,7 +159,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let result = print_seq( (first.number, increment.number, last.number), - largest_dec, + precision, &options.separator, &options.terminator, options.equal_width, @@ -210,26 +223,42 @@ fn done_printing(next: &T, increment: &T, last: &T) -> boo } } +fn format_bigdecimal(value: &bigdecimal::BigDecimal) -> Option { + let format_arguments = &[FormatArgument::Float(value.to_f64()?)]; + let value_as_bytes = sprintf("%g", format_arguments).ok()?; + String::from_utf8(value_as_bytes).ok() +} + /// Write a big decimal formatted according to the given parameters. fn write_value_float( writer: &mut impl Write, value: &ExtendedBigDecimal, width: usize, - precision: usize, + precision: Option, ) -> std::io::Result<()> { - let value_as_str = - if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity { - format!("{value:>width$.precision$}") - } else { - format!("{value:>0width$.precision$}") - }; + let value_as_str = match precision { + // format with precision: decimal floats and integers + Some(precision) => match value { + ExtendedBigDecimal::Infinity | ExtendedBigDecimal::MinusInfinity => { + format!("{value:>width$.precision$}") + } + _ => format!("{value:>0width$.precision$}"), + }, + // format without precision: hexadecimal floats + None => match value { + ExtendedBigDecimal::BigDecimal(bd) => { + format_bigdecimal(bd).unwrap_or_else(|| "{value}".to_owned()) + } + _ => format!("{value:>0width$}"), + }, + }; write!(writer, "{value_as_str}") } /// Floating point based code path fn print_seq( range: RangeFloat, - largest_dec: usize, + precision: Option, separator: &str, terminator: &str, pad: bool, @@ -241,7 +270,13 @@ fn print_seq( let (first, increment, last) = range; let mut value = first; let padding = if pad { - padding + if largest_dec > 0 { largest_dec + 1 } else { 0 } + let precision_value = precision.unwrap_or(0); + padding + + if precision_value > 0 { + precision_value + 1 + } else { + 0 + } } else { 0 }; @@ -273,7 +308,7 @@ fn print_seq( }; f.fmt(&mut stdout, float)?; } - None => write_value_float(&mut stdout, &value, padding, largest_dec)?, + None => write_value_float(&mut stdout, &value, padding, precision)?, } // TODO Implement augmenting addition. value = value + increment.clone(); diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 3ff9227345c..78fcb0068c7 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -698,7 +698,7 @@ fn test_parse_error_hex() { new_ucmd!() .arg("0xlmnop") .fails() - .usage_error("invalid hexadecimal argument: '0xlmnop'"); + .usage_error("invalid floating point argument: '0xlmnop'"); } #[test] @@ -826,3 +826,82 @@ fn test_parse_scientific_zero() { .succeeds() .stdout_only("0\n1\n"); } + +#[test] +fn test_parse_valid_hexadecimal_float_two_args() { + let test_cases = [ + (["0x1p-1", "2"], "0.5\n1.5\n"), + (["0x.8p16", "32768"], "32768\n"), + (["0xffff.4p-4", "4096"], "4095.95\n"), + (["0xA.A9p-1", "6"], "5.33008\n"), + (["0xa.a9p-1", "6"], "5.33008\n"), + (["0xffffffffffp-30", "1024"], "1024\n"), // spell-checker:disable-line + ]; + + for (input_arguments, expected_output) in &test_cases { + new_ucmd!() + .args(input_arguments) + .succeeds() + .stdout_only(expected_output); + } +} + +#[test] +fn test_parse_valid_hexadecimal_float_three_args() { + let test_cases = [ + (["0x3.4p-1", "0x4p-1", "4"], "1.625\n3.625\n"), + ( + ["-0x.ep-3", "-0x.1p-3", "-0x.fp-3"], + "-0.109375\n-0.117188\n", + ), + ]; + + for (input_arguments, expected_output) in &test_cases { + new_ucmd!() + .args(input_arguments) + .succeeds() + .stdout_only(expected_output); + } +} + +#[test] +fn test_parse_float_gnu_coreutils() { + // some values from GNU coreutils tests + new_ucmd!() + .args(&[".89999", "1e-7", ".8999901"]) + .succeeds() + .stdout_only("0.8999900\n0.8999901\n"); + + new_ucmd!() + .args(&["0", "0.000001", "0.000003"]) + .succeeds() + .stdout_only("0.000000\n0.000001\n0.000002\n0.000003\n"); +} + +#[ignore] +#[test] +fn test_parse_valid_hexadecimal_float_format_issues() { + // These tests detect differences in the representation of floating-point values with GNU seq. + // There are two key areas to investigate: + // + // 1. GNU seq uses long double (80-bit) types for values, while the current implementation + // relies on f64 (64-bit). This can lead to differences due to varying precision. However, it's + // likely not the primary cause, as even double (64-bit) values can differ when compared to + // f64. + // + // 2. GNU seq uses the %Lg format specifier for printing (see the "get_default_format" function + // ). It appears that Rust lacks a direct equivalent for this format. Additionally, %Lg + // can use %f (floating) or %e (scientific) depending on the precision. There also seem to be + // some differences in the behavior of C and Rust when displaying floating-point or scientific + // notation, at least without additional configuration. + // + // It makes sense to begin by experimenting with formats and attempting to replicate + // the printf("%Lg",...) behavior. Another area worth investigating is glibc, as reviewing its + // code may help uncover additional corner cases or test data that could reveal more issues. + + //Test output: 0.00000000992804416455328464508056640625 + new_ucmd!() + .args(&["0xa.a9p-30", "1"]) + .succeeds() + .stdout_only("9.92804e-09\n1\n"); +}