mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
seq:add floating point support (#6959)
* seq:enable parsing of hexadecimal floats Turn on the float parser. Now it's possible to use hexadecimal floats as parameters. For example, cargo run -- 0x1p-1 3 0.5 1.5 2.5 Issue #6935 --------- Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com> Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
parent
6600397a65
commit
05ada0d204
5 changed files with 555 additions and 26 deletions
404
src/uu/seq/src/hexadecimalfloat.rs
Normal file
404
src/uu/seq/src/hexadecimalfloat.rs
Normal file
|
@ -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::<PreciseNumber>().unwrap().number {
|
||||||
|
/// ExtendedBigDecimal::BigDecimal(bd) => assert_eq!(bd.to_f64().unwrap(),expected),
|
||||||
|
/// _ => unreachable!()
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
pub fn parse_number(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||||
|
// 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<usize> {
|
||||||
|
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<f64>, &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<f64>, &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<i32>, &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<BigDecimal, ParseNumberError> {
|
||||||
|
match parse_number(s)?.number {
|
||||||
|
ExtendedBigDecimal::BigDecimal(bd) => Ok(bd),
|
||||||
|
_ => Err(ParseNumberError::Float),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_f64(s: &str) -> Result<f64, ParseNumberError> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||||
pub struct PreciseNumber {
|
pub struct PreciseNumber {
|
||||||
pub number: ExtendedBigDecimal,
|
pub number: ExtendedBigDecimal,
|
||||||
pub num_integral_digits: usize,
|
pub num_integral_digits: usize,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub num_fractional_digits: usize,
|
pub num_fractional_digits: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// 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`.
|
//! Parsing numbers for use in `seq`.
|
||||||
//!
|
//!
|
||||||
//! This module provides an implementation of [`FromStr`] for the
|
//! This module provides an implementation of [`FromStr`] for the
|
||||||
|
@ -16,6 +16,7 @@ use num_traits::Num;
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
|
|
||||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||||
|
use crate::hexadecimalfloat;
|
||||||
use crate::number::PreciseNumber;
|
use crate::number::PreciseNumber;
|
||||||
|
|
||||||
/// An error returned when parsing a number fails.
|
/// An error returned when parsing a number fails.
|
||||||
|
@ -296,6 +297,14 @@ fn parse_decimal_and_exponent(
|
||||||
/// assert_eq!(actual, expected);
|
/// assert_eq!(actual, expected);
|
||||||
/// ```
|
/// ```
|
||||||
fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||||
|
if s.find(['.', 'p', 'P']).is_some() {
|
||||||
|
hexadecimalfloat::parse_number(s)
|
||||||
|
} else {
|
||||||
|
parse_hexadecimal_integer(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hexadecimal_integer(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||||
let (is_neg, s) = if s.starts_with('-') {
|
let (is_neg, s) = if s.starts_with('-') {
|
||||||
(true, &s[3..])
|
(true, &s[3..])
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// 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::ffi::OsString;
|
||||||
use std::io::{stdout, ErrorKind, Write};
|
use std::io::{stdout, ErrorKind, Write};
|
||||||
|
|
||||||
|
@ -10,11 +10,13 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use num_traits::{ToPrimitive, Zero};
|
use num_traits::{ToPrimitive, Zero};
|
||||||
|
|
||||||
use uucore::error::{FromIo, UResult};
|
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};
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod extendedbigdecimal;
|
mod extendedbigdecimal;
|
||||||
|
mod hexadecimalfloat;
|
||||||
|
|
||||||
// public to allow fuzzing
|
// public to allow fuzzing
|
||||||
#[cfg(fuzzing)]
|
#[cfg(fuzzing)]
|
||||||
pub mod number;
|
pub mod number;
|
||||||
|
@ -72,6 +74,18 @@ fn split_short_args_with_value(args: impl uucore::Args) -> impl uucore::Args {
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_precision(
|
||||||
|
first: Option<usize>,
|
||||||
|
increment: Option<usize>,
|
||||||
|
last: Option<usize>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
match (first, increment, last) {
|
||||||
|
(Some(0), Some(0), Some(0)) => Some(0),
|
||||||
|
(Some(f), Some(i), Some(_)) => Some(f.max(i)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app().try_get_matches_from(split_short_args_with_value(args))?;
|
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::<String>(OPT_FORMAT).map(|s| s.as_str()),
|
format: matches.get_one::<String>(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() {
|
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()),
|
Err(e) => return Err(SeqError::ParseError(numbers[0].to_string(), e).into()),
|
||||||
}
|
}
|
||||||
} else {
|
} 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() {
|
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()),
|
Err(e) => return Err(SeqError::ParseError(numbers[1].to_string(), e).into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PreciseNumber::one()
|
(PreciseNumber::one(), Some(0))
|
||||||
};
|
};
|
||||||
if increment.is_zero() {
|
if increment.is_zero() {
|
||||||
return Err(SeqError::ZeroIncrement(numbers[1].to_string()).into());
|
return Err(SeqError::ZeroIncrement(numbers[1].to_string()).into());
|
||||||
}
|
}
|
||||||
let last: PreciseNumber = {
|
let (last, last_precision): (PreciseNumber, Option<usize>) = {
|
||||||
// We are guaranteed that `numbers.len()` is greater than zero
|
// We are guaranteed that `numbers.len()` is greater than zero
|
||||||
// and at most three because of the argument specification in
|
// and at most three because of the argument specification in
|
||||||
// `uu_app()`.
|
// `uu_app()`.
|
||||||
let n: usize = numbers.len();
|
let n: usize = numbers.len();
|
||||||
match numbers[n - 1].parse() {
|
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()),
|
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
|
.num_integral_digits
|
||||||
.max(increment.num_integral_digits)
|
.max(increment.num_integral_digits)
|
||||||
.max(last.num_integral_digits);
|
.max(last.num_integral_digits);
|
||||||
let largest_dec = first
|
|
||||||
.num_fractional_digits
|
let precision = select_precision(first_precision, increment_precision, last_precision);
|
||||||
.max(increment.num_fractional_digits);
|
|
||||||
|
|
||||||
let format = match options.format {
|
let format = match options.format {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
|
@ -146,7 +159,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
};
|
};
|
||||||
let result = print_seq(
|
let result = print_seq(
|
||||||
(first.number, increment.number, last.number),
|
(first.number, increment.number, last.number),
|
||||||
largest_dec,
|
precision,
|
||||||
&options.separator,
|
&options.separator,
|
||||||
&options.terminator,
|
&options.terminator,
|
||||||
options.equal_width,
|
options.equal_width,
|
||||||
|
@ -210,26 +223,42 @@ fn done_printing<T: Zero + PartialOrd>(next: &T, increment: &T, last: &T) -> boo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_bigdecimal(value: &bigdecimal::BigDecimal) -> Option<String> {
|
||||||
|
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.
|
/// Write a big decimal formatted according to the given parameters.
|
||||||
fn write_value_float(
|
fn write_value_float(
|
||||||
writer: &mut impl Write,
|
writer: &mut impl Write,
|
||||||
value: &ExtendedBigDecimal,
|
value: &ExtendedBigDecimal,
|
||||||
width: usize,
|
width: usize,
|
||||||
precision: usize,
|
precision: Option<usize>,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
let value_as_str =
|
let value_as_str = match precision {
|
||||||
if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity {
|
// format with precision: decimal floats and integers
|
||||||
format!("{value:>width$.precision$}")
|
Some(precision) => match value {
|
||||||
} else {
|
ExtendedBigDecimal::Infinity | ExtendedBigDecimal::MinusInfinity => {
|
||||||
format!("{value:>0width$.precision$}")
|
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}")
|
write!(writer, "{value_as_str}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Floating point based code path
|
/// Floating point based code path
|
||||||
fn print_seq(
|
fn print_seq(
|
||||||
range: RangeFloat,
|
range: RangeFloat,
|
||||||
largest_dec: usize,
|
precision: Option<usize>,
|
||||||
separator: &str,
|
separator: &str,
|
||||||
terminator: &str,
|
terminator: &str,
|
||||||
pad: bool,
|
pad: bool,
|
||||||
|
@ -241,7 +270,13 @@ fn print_seq(
|
||||||
let (first, increment, last) = range;
|
let (first, increment, last) = range;
|
||||||
let mut value = first;
|
let mut value = first;
|
||||||
let padding = if pad {
|
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 {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
@ -273,7 +308,7 @@ fn print_seq(
|
||||||
};
|
};
|
||||||
f.fmt(&mut stdout, float)?;
|
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.
|
// TODO Implement augmenting addition.
|
||||||
value = value + increment.clone();
|
value = value + increment.clone();
|
||||||
|
|
|
@ -698,7 +698,7 @@ fn test_parse_error_hex() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("0xlmnop")
|
.arg("0xlmnop")
|
||||||
.fails()
|
.fails()
|
||||||
.usage_error("invalid hexadecimal argument: '0xlmnop'");
|
.usage_error("invalid floating point argument: '0xlmnop'");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -826,3 +826,82 @@ fn test_parse_scientific_zero() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("0\n1\n");
|
.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");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue