seq: use BigDecimal to represent floats (#2698)

* seq: use BigDecimal to represent floats

Use `BigDecimal` to represent arbitrary precision floats in order to
prevent numerical precision issues when iterating over a sequence of
numbers. This commit makes several changes at once to accomplish this
goal.

First, it creates a new struct, `PreciseNumber`, that is responsible for
storing not only the number itself but also the number of digits (both
integer and decimal) needed to display it. This information is collected
at the time of parsing the number, which lives in the new
`numberparse.rs` module.

Second, it uses the `BigDecimal` struct to store arbitrary precision
floating point numbers instead of the previous `f64` primitive
type. This protects against issues of numerical precision when
repeatedly accumulating a very small increment.

Third, since neither the `BigDecimal` nor `BigInt` types have a
representation of infinity, minus infinity, minus zero, or NaN, we add
the `ExtendedBigDecimal` and `ExtendedBigInt` enumerations which extend
the basic types with these concepts.

* fixup! seq: use BigDecimal to represent floats

* fixup! seq: use BigDecimal to represent floats

* fixup! seq: use BigDecimal to represent floats

* fixup! seq: use BigDecimal to represent floats

* fixup! seq: use BigDecimal to represent floats
This commit is contained in:
jfinkels 2021-11-06 10:44:42 -04:00 committed by GitHub
parent a7aa6b8e3a
commit 2e12316ae1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1471 additions and 425 deletions

12
Cargo.lock generated
View file

@ -76,6 +76,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bigdecimal"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "binary-heap-plus"
version = "0.4.1"
@ -2912,6 +2923,7 @@ dependencies = [
name = "uu_seq"
version = "0.0.8"
dependencies = [
"bigdecimal",
"clap",
"num-bigint",
"num-traits",

View file

@ -0,0 +1,19 @@
# Benchmarking to measure performance
To compare the performance of the `uutils` version of `seq` with the
GNU version of `seq`, you can use a benchmarking tool like
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
running
sudo apt-get install hyperfine
Next, build the `seq` binary under the release profile:
cargo build --release -p uu_seq
Finally, you can compare the performance of the two versions of `head`
by running, for example,
hyperfine "seq 1000000" "target/release/seq 1000000"
[0]: https://github.com/sharkdp/hyperfine

View file

@ -1,3 +1,4 @@
# spell-checker:ignore bigdecimal
[package]
name = "uu_seq"
version = "0.0.8"
@ -15,6 +16,7 @@ edition = "2018"
path = "src/seq.rs"
[dependencies]
bigdecimal = "0.3"
clap = { version = "2.33", features = ["wrap_help"] }
num-bigint = "0.4.0"
num-traits = "0.2.14"

View file

@ -1,190 +0,0 @@
//! Counting number of digits needed to represent a number.
//!
//! The [`num_integral_digits`] and [`num_fractional_digits`] functions
//! count the number of digits needed to represent a number in decimal
//! notation (like "123.456").
use std::convert::TryInto;
use std::num::ParseIntError;
use uucore::display::Quotable;
/// The number of digits after the decimal point in a given number.
///
/// The input `s` is a string representing a number, either an integer
/// or a floating point number in either decimal notation or scientific
/// notation. This function returns the number of digits after the
/// decimal point needed to print the number in decimal notation.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
/// ```
pub fn num_fractional_digits(s: &str) -> Result<usize, ParseIntError> {
match (s.find('.'), s.find('e')) {
// For example, "123456".
(None, None) => Ok(0),
// For example, "123e456".
(None, Some(j)) => {
let exponent: i64 = s[j + 1..].parse()?;
if exponent < 0 {
Ok(-exponent as usize)
} else {
Ok(0)
}
}
// For example, "123.456".
(Some(i), None) => Ok(s.len() - (i + 1)),
// For example, "123.456e789".
(Some(i), Some(j)) if i < j => {
// Because of the match guard, this subtraction will not underflow.
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
let exponent: i64 = s[j + 1..].parse()?;
if num_digits_between_decimal_point_and_e < exponent {
Ok(0)
} else {
Ok((num_digits_between_decimal_point_and_e - exponent)
.try_into()
.unwrap())
}
}
_ => crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
s.quote(),
uucore::execution_phrase()
),
}
}
/// The number of digits before the decimal point in a given number.
///
/// The input `s` is a string representing a number, either an integer
/// or a floating point number in either decimal notation or scientific
/// notation. This function returns the number of digits before the
/// decimal point needed to print the number in decimal notation.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2);
/// ```
pub fn num_integral_digits(s: &str) -> Result<usize, ParseIntError> {
match (s.find('.'), s.find('e')) {
// For example, "123456".
(None, None) => Ok(s.len()),
// For example, "123e456".
(None, Some(j)) => {
let exponent: i64 = s[j + 1..].parse()?;
let total = j as i64 + exponent;
if total < 1 {
Ok(1)
} else {
Ok(total.try_into().unwrap())
}
}
// For example, "123.456".
(Some(i), None) => Ok(i),
// For example, "123.456e789".
(Some(i), Some(j)) => {
let exponent: i64 = s[j + 1..].parse()?;
let minimum: usize = {
let integral_part: f64 = crash_if_err!(1, s[..j].parse());
if integral_part == -0.0 && integral_part.is_sign_negative() {
2
} else {
1
}
};
let total = i as i64 + exponent;
if total < minimum as i64 {
Ok(minimum)
} else {
Ok(total.try_into().unwrap())
}
}
}
}
#[cfg(test)]
mod tests {
mod test_num_integral_digits {
use crate::num_integral_digits;
#[test]
fn test_integer() {
assert_eq!(num_integral_digits("123").unwrap(), 3);
}
#[test]
fn test_decimal() {
assert_eq!(num_integral_digits("123.45").unwrap(), 3);
}
#[test]
fn test_scientific_no_decimal_positive_exponent() {
assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4);
}
#[test]
fn test_scientific_with_decimal_positive_exponent() {
assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6);
}
#[test]
fn test_scientific_no_decimal_negative_exponent() {
assert_eq!(num_integral_digits("123e-4").unwrap(), 1);
}
#[test]
fn test_scientific_with_decimal_negative_exponent() {
assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1);
assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2);
}
}
mod test_num_fractional_digits {
use crate::num_fractional_digits;
#[test]
fn test_integer() {
assert_eq!(num_fractional_digits("123").unwrap(), 0);
}
#[test]
fn test_decimal() {
assert_eq!(num_fractional_digits("123.45").unwrap(), 2);
}
#[test]
fn test_scientific_no_decimal_positive_exponent() {
assert_eq!(num_fractional_digits("123e4").unwrap(), 0);
}
#[test]
fn test_scientific_with_decimal_positive_exponent() {
assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0);
assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1);
}
#[test]
fn test_scientific_no_decimal_negative_exponent() {
assert_eq!(num_fractional_digits("123e-4").unwrap(), 4);
assert_eq!(num_fractional_digits("123e-1").unwrap(), 1);
}
#[test]
fn test_scientific_with_decimal_negative_exponent() {
assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8);
assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
}
}
}

View file

@ -0,0 +1,290 @@
// spell-checker:ignore bigdecimal extendedbigdecimal extendedbigint
//! An arbitrary precision float that can also represent infinity, NaN, etc.
//!
//! The finite values are stored as [`BigDecimal`] instances. Because
//! the `bigdecimal` library does not represent infinity, NaN, etc., we
//! need to represent them explicitly ourselves. The
//! [`ExtendedBigDecimal`] enumeration does that.
//!
//! # Examples
//!
//! Addition works for [`ExtendedBigDecimal`] as it does for floats. For
//! example, adding infinity to any finite value results in infinity:
//!
//! ```rust,ignore
//! let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
//! let summand2 = ExtendedBigDecimal::Infinity;
//! assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity);
//! ```
use std::cmp::Ordering;
use std::fmt::Display;
use std::ops::Add;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_bigint::ToBigInt;
use num_traits::One;
use num_traits::Zero;
use crate::extendedbigint::ExtendedBigInt;
#[derive(Debug, Clone)]
pub enum ExtendedBigDecimal {
/// Arbitrary precision floating point number.
BigDecimal(BigDecimal),
/// Floating point positive infinity.
///
/// This is represented as its own enumeration member instead of as
/// a [`BigDecimal`] because the `bigdecimal` library does not
/// support infinity, see [here][0].
///
/// [0]: https://github.com/akubera/bigdecimal-rs/issues/67
Infinity,
/// Floating point negative infinity.
///
/// This is represented as its own enumeration member instead of as
/// a [`BigDecimal`] because the `bigdecimal` library does not
/// support infinity, see [here][0].
///
/// [0]: https://github.com/akubera/bigdecimal-rs/issues/67
MinusInfinity,
/// Floating point negative zero.
///
/// This is represented as its own enumeration member instead of as
/// a [`BigDecimal`] because the `bigdecimal` library does not
/// support negative zero.
MinusZero,
/// Floating point NaN.
///
/// This is represented as its own enumeration member instead of as
/// a [`BigDecimal`] because the `bigdecimal` library does not
/// support NaN, see [here][0].
///
/// [0]: https://github.com/akubera/bigdecimal-rs/issues/67
Nan,
}
/// The smallest integer greater than or equal to this number.
fn ceil(x: BigDecimal) -> BigInt {
if x.is_integer() {
// Unwrapping the Option because it always returns Some
x.to_bigint().unwrap()
} else {
(x + BigDecimal::one().half()).round(0).to_bigint().unwrap()
}
}
/// The largest integer less than or equal to this number.
fn floor(x: BigDecimal) -> BigInt {
if x.is_integer() {
// Unwrapping the Option because it always returns Some
x.to_bigint().unwrap()
} else {
(x - BigDecimal::one().half()).round(0).to_bigint().unwrap()
}
}
impl ExtendedBigDecimal {
/// The smallest integer greater than or equal to this number.
pub fn ceil(self) -> ExtendedBigInt {
match self {
ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)),
other => From::from(other),
}
}
/// The largest integer less than or equal to this number.
pub fn floor(self) -> ExtendedBigInt {
match self {
ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)),
other => From::from(other),
}
}
}
impl From<ExtendedBigInt> for ExtendedBigDecimal {
fn from(big_int: ExtendedBigInt) -> Self {
match big_int {
ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)),
ExtendedBigInt::Infinity => ExtendedBigDecimal::Infinity,
ExtendedBigInt::MinusInfinity => ExtendedBigDecimal::MinusInfinity,
ExtendedBigInt::MinusZero => ExtendedBigDecimal::MinusZero,
ExtendedBigInt::Nan => ExtendedBigDecimal::Nan,
}
}
}
impl Display for ExtendedBigDecimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExtendedBigDecimal::BigDecimal(x) => {
let (n, p) = x.as_bigint_and_exponent();
match p {
0 => ExtendedBigDecimal::BigDecimal(BigDecimal::new(n * 10, 1)).fmt(f),
_ => x.fmt(f),
}
}
ExtendedBigDecimal::Infinity => f32::INFINITY.fmt(f),
ExtendedBigDecimal::MinusInfinity => f32::NEG_INFINITY.fmt(f),
ExtendedBigDecimal::MinusZero => {
// FIXME In Rust version 1.53.0 and later, the display
// of floats was updated to allow displaying negative
// zero. See
// https://github.com/rust-lang/rust/pull/78618. Currently,
// this just formats "0.0".
(0.0f32).fmt(f)
}
ExtendedBigDecimal::Nan => "nan".fmt(f),
}
}
}
impl Zero for ExtendedBigDecimal {
fn zero() -> Self {
ExtendedBigDecimal::BigDecimal(BigDecimal::zero())
}
fn is_zero(&self) -> bool {
match self {
Self::BigDecimal(n) => n.is_zero(),
Self::MinusZero => true,
_ => false,
}
}
}
impl Add for ExtendedBigDecimal {
type Output = Self;
fn add(self, other: Self) -> Self {
match (self, other) {
(Self::BigDecimal(m), Self::BigDecimal(n)) => Self::BigDecimal(m.add(n)),
(Self::BigDecimal(_), Self::MinusInfinity) => Self::MinusInfinity,
(Self::BigDecimal(_), Self::Infinity) => Self::Infinity,
(Self::BigDecimal(_), Self::Nan) => Self::Nan,
(Self::BigDecimal(m), Self::MinusZero) => Self::BigDecimal(m),
(Self::Infinity, Self::BigDecimal(_)) => Self::Infinity,
(Self::Infinity, Self::Infinity) => Self::Infinity,
(Self::Infinity, Self::MinusZero) => Self::Infinity,
(Self::Infinity, Self::MinusInfinity) => Self::Nan,
(Self::Infinity, Self::Nan) => Self::Nan,
(Self::MinusInfinity, Self::BigDecimal(_)) => Self::MinusInfinity,
(Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity,
(Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity,
(Self::MinusInfinity, Self::Infinity) => Self::Nan,
(Self::MinusInfinity, Self::Nan) => Self::Nan,
(Self::Nan, _) => Self::Nan,
(Self::MinusZero, other) => other,
}
}
}
impl PartialEq for ExtendedBigDecimal {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BigDecimal(m), Self::BigDecimal(n)) => m.eq(n),
(Self::BigDecimal(_), Self::MinusInfinity) => false,
(Self::BigDecimal(_), Self::Infinity) => false,
(Self::BigDecimal(_), Self::Nan) => false,
(Self::BigDecimal(_), Self::MinusZero) => false,
(Self::Infinity, Self::BigDecimal(_)) => false,
(Self::Infinity, Self::Infinity) => true,
(Self::Infinity, Self::MinusZero) => false,
(Self::Infinity, Self::MinusInfinity) => false,
(Self::Infinity, Self::Nan) => false,
(Self::MinusInfinity, Self::BigDecimal(_)) => false,
(Self::MinusInfinity, Self::Infinity) => false,
(Self::MinusInfinity, Self::MinusZero) => false,
(Self::MinusInfinity, Self::MinusInfinity) => true,
(Self::MinusInfinity, Self::Nan) => false,
(Self::Nan, _) => false,
(Self::MinusZero, Self::BigDecimal(_)) => false,
(Self::MinusZero, Self::Infinity) => false,
(Self::MinusZero, Self::MinusZero) => true,
(Self::MinusZero, Self::MinusInfinity) => false,
(Self::MinusZero, Self::Nan) => false,
}
}
}
impl PartialOrd for ExtendedBigDecimal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::BigDecimal(m), Self::BigDecimal(n)) => m.partial_cmp(n),
(Self::BigDecimal(_), Self::MinusInfinity) => Some(Ordering::Greater),
(Self::BigDecimal(_), Self::Infinity) => Some(Ordering::Less),
(Self::BigDecimal(_), Self::Nan) => None,
(Self::BigDecimal(m), Self::MinusZero) => m.partial_cmp(&BigDecimal::zero()),
(Self::Infinity, Self::BigDecimal(_)) => Some(Ordering::Greater),
(Self::Infinity, Self::Infinity) => Some(Ordering::Equal),
(Self::Infinity, Self::MinusZero) => Some(Ordering::Greater),
(Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater),
(Self::Infinity, Self::Nan) => None,
(Self::MinusInfinity, Self::BigDecimal(_)) => Some(Ordering::Less),
(Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less),
(Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less),
(Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal),
(Self::MinusInfinity, Self::Nan) => None,
(Self::Nan, _) => None,
(Self::MinusZero, Self::BigDecimal(n)) => BigDecimal::zero().partial_cmp(n),
(Self::MinusZero, Self::Infinity) => Some(Ordering::Less),
(Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal),
(Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater),
(Self::MinusZero, Self::Nan) => None,
}
}
}
#[cfg(test)]
mod tests {
use bigdecimal::BigDecimal;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
#[test]
fn test_addition_infinity() {
let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
let summand2 = ExtendedBigDecimal::Infinity;
assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity);
}
#[test]
fn test_addition_minus_infinity() {
let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
let summand2 = ExtendedBigDecimal::MinusInfinity;
assert_eq!(summand1 + summand2, ExtendedBigDecimal::MinusInfinity);
}
#[test]
fn test_addition_nan() {
let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
let summand2 = ExtendedBigDecimal::Nan;
let sum = summand1 + summand2;
match sum {
ExtendedBigDecimal::Nan => (),
_ => unreachable!(),
}
}
#[test]
fn test_display() {
assert_eq!(
format!("{}", ExtendedBigDecimal::BigDecimal(BigDecimal::zero())),
"0.0"
);
assert_eq!(format!("{}", ExtendedBigDecimal::Infinity), "inf");
assert_eq!(format!("{}", ExtendedBigDecimal::MinusInfinity), "-inf");
assert_eq!(format!("{}", ExtendedBigDecimal::Nan), "nan");
// FIXME In Rust version 1.53.0 and later, the display of floats
// was updated to allow displaying negative zero. Until then, we
// just display `MinusZero` as "0.0".
//
// assert_eq!(format!("{}", ExtendedBigDecimal::MinusZero), "-0.0");
//
}
}

View file

@ -0,0 +1,218 @@
// spell-checker:ignore bigint extendedbigint extendedbigdecimal
//! An arbitrary precision integer that can also represent infinity, NaN, etc.
//!
//! Usually infinity, NaN, and negative zero are only represented for
//! floating point numbers. The [`ExtendedBigInt`] enumeration provides
//! a representation of those things with the set of integers. The
//! finite values are stored as [`BigInt`] instances.
//!
//! # Examples
//!
//! Addition works for [`ExtendedBigInt`] as it does for floats. For
//! example, adding infinity to any finite value results in infinity:
//!
//! ```rust,ignore
//! let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
//! let summand2 = ExtendedBigInt::Infinity;
//! assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity);
//! ```
use std::cmp::Ordering;
use std::fmt::Display;
use std::ops::Add;
use num_bigint::BigInt;
use num_bigint::ToBigInt;
use num_traits::One;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
#[derive(Debug, Clone)]
pub enum ExtendedBigInt {
BigInt(BigInt),
Infinity,
MinusInfinity,
MinusZero,
Nan,
}
impl ExtendedBigInt {
/// The integer number one.
pub fn one() -> Self {
// We would like to implement `num_traits::One`, but it requires
// a multiplication implementation, and we don't want to
// implement that here.
ExtendedBigInt::BigInt(BigInt::one())
}
}
impl From<ExtendedBigDecimal> for ExtendedBigInt {
fn from(big_decimal: ExtendedBigDecimal) -> Self {
match big_decimal {
// TODO When can this fail?
ExtendedBigDecimal::BigDecimal(x) => Self::BigInt(x.to_bigint().unwrap()),
ExtendedBigDecimal::Infinity => ExtendedBigInt::Infinity,
ExtendedBigDecimal::MinusInfinity => ExtendedBigInt::MinusInfinity,
ExtendedBigDecimal::MinusZero => ExtendedBigInt::MinusZero,
ExtendedBigDecimal::Nan => ExtendedBigInt::Nan,
}
}
}
impl Display for ExtendedBigInt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExtendedBigInt::BigInt(n) => n.fmt(f),
ExtendedBigInt::Infinity => f32::INFINITY.fmt(f),
ExtendedBigInt::MinusInfinity => f32::NEG_INFINITY.fmt(f),
ExtendedBigInt::MinusZero => {
// FIXME Come up with a way of formatting this with a
// "-" prefix.
0.fmt(f)
}
ExtendedBigInt::Nan => "nan".fmt(f),
}
}
}
impl Zero for ExtendedBigInt {
fn zero() -> Self {
ExtendedBigInt::BigInt(BigInt::zero())
}
fn is_zero(&self) -> bool {
match self {
Self::BigInt(n) => n.is_zero(),
Self::MinusZero => true,
_ => false,
}
}
}
impl Add for ExtendedBigInt {
type Output = Self;
fn add(self, other: Self) -> Self {
match (self, other) {
(Self::BigInt(m), Self::BigInt(n)) => Self::BigInt(m.add(n)),
(Self::BigInt(_), Self::MinusInfinity) => Self::MinusInfinity,
(Self::BigInt(_), Self::Infinity) => Self::Infinity,
(Self::BigInt(_), Self::Nan) => Self::Nan,
(Self::BigInt(m), Self::MinusZero) => Self::BigInt(m),
(Self::Infinity, Self::BigInt(_)) => Self::Infinity,
(Self::Infinity, Self::Infinity) => Self::Infinity,
(Self::Infinity, Self::MinusZero) => Self::Infinity,
(Self::Infinity, Self::MinusInfinity) => Self::Nan,
(Self::Infinity, Self::Nan) => Self::Nan,
(Self::MinusInfinity, Self::BigInt(_)) => Self::MinusInfinity,
(Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity,
(Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity,
(Self::MinusInfinity, Self::Infinity) => Self::Nan,
(Self::MinusInfinity, Self::Nan) => Self::Nan,
(Self::Nan, _) => Self::Nan,
(Self::MinusZero, other) => other,
}
}
}
impl PartialEq for ExtendedBigInt {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BigInt(m), Self::BigInt(n)) => m.eq(n),
(Self::BigInt(_), Self::MinusInfinity) => false,
(Self::BigInt(_), Self::Infinity) => false,
(Self::BigInt(_), Self::Nan) => false,
(Self::BigInt(_), Self::MinusZero) => false,
(Self::Infinity, Self::BigInt(_)) => false,
(Self::Infinity, Self::Infinity) => true,
(Self::Infinity, Self::MinusZero) => false,
(Self::Infinity, Self::MinusInfinity) => false,
(Self::Infinity, Self::Nan) => false,
(Self::MinusInfinity, Self::BigInt(_)) => false,
(Self::MinusInfinity, Self::Infinity) => false,
(Self::MinusInfinity, Self::MinusZero) => false,
(Self::MinusInfinity, Self::MinusInfinity) => true,
(Self::MinusInfinity, Self::Nan) => false,
(Self::Nan, _) => false,
(Self::MinusZero, Self::BigInt(_)) => false,
(Self::MinusZero, Self::Infinity) => false,
(Self::MinusZero, Self::MinusZero) => true,
(Self::MinusZero, Self::MinusInfinity) => false,
(Self::MinusZero, Self::Nan) => false,
}
}
}
impl PartialOrd for ExtendedBigInt {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::BigInt(m), Self::BigInt(n)) => m.partial_cmp(n),
(Self::BigInt(_), Self::MinusInfinity) => Some(Ordering::Greater),
(Self::BigInt(_), Self::Infinity) => Some(Ordering::Less),
(Self::BigInt(_), Self::Nan) => None,
(Self::BigInt(m), Self::MinusZero) => m.partial_cmp(&BigInt::zero()),
(Self::Infinity, Self::BigInt(_)) => Some(Ordering::Greater),
(Self::Infinity, Self::Infinity) => Some(Ordering::Equal),
(Self::Infinity, Self::MinusZero) => Some(Ordering::Greater),
(Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater),
(Self::Infinity, Self::Nan) => None,
(Self::MinusInfinity, Self::BigInt(_)) => Some(Ordering::Less),
(Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less),
(Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less),
(Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal),
(Self::MinusInfinity, Self::Nan) => None,
(Self::Nan, _) => None,
(Self::MinusZero, Self::BigInt(n)) => BigInt::zero().partial_cmp(n),
(Self::MinusZero, Self::Infinity) => Some(Ordering::Less),
(Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal),
(Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater),
(Self::MinusZero, Self::Nan) => None,
}
}
}
#[cfg(test)]
mod tests {
use num_bigint::BigInt;
use num_traits::Zero;
use crate::extendedbigint::ExtendedBigInt;
#[test]
fn test_addition_infinity() {
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
let summand2 = ExtendedBigInt::Infinity;
assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity);
}
#[test]
fn test_addition_minus_infinity() {
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
let summand2 = ExtendedBigInt::MinusInfinity;
assert_eq!(summand1 + summand2, ExtendedBigInt::MinusInfinity);
}
#[test]
fn test_addition_nan() {
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
let summand2 = ExtendedBigInt::Nan;
let sum = summand1 + summand2;
match sum {
ExtendedBigInt::Nan => (),
_ => unreachable!(),
}
}
#[test]
fn test_display() {
assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0");
assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf");
assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf");
assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan");
// FIXME Come up with a way of displaying negative zero as
// "-0". Currently it displays as just "0".
//
// assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0");
//
}
}

119
src/uu/seq/src/number.rs Normal file
View file

@ -0,0 +1,119 @@
// spell-checker:ignore extendedbigdecimal extendedbigint
//! A type to represent the possible start, increment, and end values for seq.
//!
//! The [`Number`] enumeration represents the possible values for the
//! start, increment, and end values for `seq`. These may be integers,
//! floating point numbers, negative zero, etc. A [`Number`] can be
//! parsed from a string by calling [`parse`].
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
/// An integral or floating point number.
#[derive(Debug, PartialEq)]
pub enum Number {
Int(ExtendedBigInt),
Float(ExtendedBigDecimal),
}
impl Number {
/// Decide whether this number is zero (either positive or negative).
pub fn is_zero(&self) -> bool {
// We would like to implement `num_traits::Zero`, but it
// requires an addition implementation, and we don't want to
// implement that here.
match self {
Number::Int(n) => n.is_zero(),
Number::Float(x) => x.is_zero(),
}
}
/// Convert this number into an `ExtendedBigDecimal`.
pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal {
match self {
Number::Int(n) => ExtendedBigDecimal::from(n),
Number::Float(x) => x,
}
}
/// The integer number one.
pub fn one() -> Self {
// We would like to implement `num_traits::One`, but it requires
// a multiplication implementation, and we don't want to
// implement that here.
Number::Int(ExtendedBigInt::one())
}
/// Round this number towards the given other number.
///
/// If `other` is greater, then round up. If `other` is smaller,
/// then round down.
pub fn round_towards(self, other: &ExtendedBigInt) -> ExtendedBigInt {
match self {
// If this number is already an integer, it is already
// rounded to the nearest integer in the direction of
// `other`.
Number::Int(num) => num,
// Otherwise, if this number is a float, we need to decide
// whether `other` is larger or smaller than it, and thus
// whether to round up or round down, respectively.
Number::Float(num) => {
let other: ExtendedBigDecimal = From::from(other.clone());
if other > num {
num.ceil()
} else {
// If they are equal, then `self` is already an
// integer, so calling `floor()` does no harm and
// will just return that integer anyway.
num.floor()
}
}
}
}
}
/// A number with a specified number of integer and fractional digits.
///
/// This struct can be used to represent a number along with information
/// on how many significant digits to use when displaying the number.
/// The [`num_integral_digits`] field also includes the width needed to
/// display the "-" character for a negative number.
///
/// You can get an instance of this struct by calling [`str::parse`].
#[derive(Debug)]
pub struct PreciseNumber {
pub number: Number,
pub num_integral_digits: usize,
pub num_fractional_digits: usize,
}
impl PreciseNumber {
pub fn new(
number: Number,
num_integral_digits: usize,
num_fractional_digits: usize,
) -> PreciseNumber {
PreciseNumber {
number,
num_integral_digits,
num_fractional_digits,
}
}
/// The integer number one.
pub fn one() -> Self {
// We would like to implement `num_traits::One`, but it requires
// a multiplication implementation, and we don't want to
// implement that here.
PreciseNumber::new(Number::one(), 1, 0)
}
/// Decide whether this number is zero (either positive or negative).
pub fn is_zero(&self) -> bool {
// We would like to implement `num_traits::Zero`, but it
// requires an addition implementation, and we don't want to
// implement that here.
self.number.is_zero()
}
}

View file

@ -0,0 +1,589 @@
// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse
//! Parsing numbers for use in `seq`.
//!
//! This module provides an implementation of [`FromStr`] for the
//! [`PreciseNumber`] struct.
use std::convert::TryInto;
use std::str::FromStr;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_bigint::Sign;
use num_traits::Num;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
/// An error returned when parsing a number fails.
#[derive(Debug, PartialEq)]
pub enum ParseNumberError {
Float,
Nan,
Hex,
}
/// Decide whether a given string and its parsed `BigInt` is negative zero.
fn is_minus_zero_int(s: &str, n: &BigInt) -> bool {
s.starts_with('-') && n == &BigInt::zero()
}
/// Decide whether a given string and its parsed `BigDecimal` is negative zero.
fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool {
s.starts_with('-') && x == &BigDecimal::zero()
}
/// Parse a number with neither a decimal point nor an exponent.
///
/// # Errors
///
/// This function returns an error if the input string is a variant of
/// "NaN" or if no [`BigInt`] could be parsed from the string.
///
/// # Examples
///
/// ```rust,ignore
/// let actual = "0".parse::<Number>().unwrap().number;
/// let expected = Number::BigInt(BigInt::zero());
/// assert_eq!(actual, expected);
/// ```
fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberError> {
match s.parse::<BigInt>() {
Ok(n) => {
// If `s` is '-0', then `parse()` returns `BigInt::zero()`,
// but we need to return `Number::MinusZeroInt` instead.
if is_minus_zero_int(s, &n) {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
s.len(),
0,
))
} else {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(n)),
s.len(),
0,
))
}
}
Err(_) => {
// Possibly "NaN" or "inf".
//
// TODO In Rust v1.53.0, this change
// https://github.com/rust-lang/rust/pull/78618 improves the
// parsing of floats to include being able to parse "NaN"
// and "inf". So when the minimum version of this crate is
// increased to 1.53.0, we should just use the built-in
// `f32` parsing instead.
if s.eq_ignore_ascii_case("inf") {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::Infinity),
0,
0,
))
} else if s.eq_ignore_ascii_case("-inf") {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusInfinity),
0,
0,
))
} else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") {
Err(ParseNumberError::Nan)
} else {
Err(ParseNumberError::Float)
}
}
}
}
/// Parse a number with an exponent but no decimal point.
///
/// # Errors
///
/// This function returns an error if `s` is not a valid number.
///
/// # Examples
///
/// ```rust,ignore
/// let actual = "1e2".parse::<Number>().unwrap().number;
/// let expected = "100".parse::<BigInt>().unwrap();
/// assert_eq!(actual, expected);
/// ```
fn parse_exponent_no_decimal(s: &str, j: usize) -> Result<PreciseNumber, ParseNumberError> {
let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?;
// If the exponent is strictly less than zero, then the number
// should be treated as a floating point number that will be
// displayed in decimal notation. For example, "1e-2" will be
// displayed as "0.01", but "1e2" will be displayed as "100",
// without a decimal point.
let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
let num_integral_digits = if is_minus_zero_float(s, &x) {
2
} else {
let total = j as i64 + exponent;
let result = if total < 1 {
1
} else {
total.try_into().unwrap()
};
if x.sign() == Sign::Minus {
result + 1
} else {
result
}
};
let num_fractional_digits = if exponent < 0 { -exponent as usize } else { 0 };
if exponent < 0 {
if is_minus_zero_float(s, &x) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
num_integral_digits,
num_fractional_digits,
))
}
} else {
let zeros = "0".repeat(exponent.try_into().unwrap());
let expanded = [&s[0..j], &zeros].concat();
parse_no_decimal_no_exponent(&expanded)
}
}
/// Parse a number with a decimal point but no exponent.
///
/// # Errors
///
/// This function returns an error if `s` is not a valid number.
///
/// # Examples
///
/// ```rust,ignore
/// let actual = "1.2".parse::<Number>().unwrap().number;
/// let expected = "1.2".parse::<BigDecimal>().unwrap();
/// assert_eq!(actual, expected);
/// ```
fn parse_decimal_no_exponent(s: &str, i: usize) -> Result<PreciseNumber, ParseNumberError> {
let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
let num_integral_digits = i;
let num_fractional_digits = s.len() - (i + 1);
if is_minus_zero_float(s, &x) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
num_integral_digits,
num_fractional_digits,
))
}
}
/// Parse a number with both a decimal point and an exponent.
///
/// # Errors
///
/// This function returns an error if `s` is not a valid number.
///
/// # Examples
///
/// ```rust,ignore
/// let actual = "1.2e3".parse::<Number>().unwrap().number;
/// let expected = "1200".parse::<BigInt>().unwrap();
/// assert_eq!(actual, expected);
/// ```
fn parse_decimal_and_exponent(
s: &str,
i: usize,
j: usize,
) -> Result<PreciseNumber, ParseNumberError> {
// Because of the match guard, this subtraction will not underflow.
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?;
let val: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
let num_integral_digits = {
let minimum: usize = {
let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?;
if integral_part == -0.0 && integral_part.is_sign_negative() {
2
} else {
1
}
};
let total = i as i64 + exponent;
if total < minimum as i64 {
minimum
} else {
total.try_into().unwrap()
}
};
let num_fractional_digits = if num_digits_between_decimal_point_and_e < exponent {
0
} else {
(num_digits_between_decimal_point_and_e - exponent)
.try_into()
.unwrap()
};
if num_digits_between_decimal_point_and_e <= exponent {
if is_minus_zero_float(s, &val) {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
let zeros: String = "0".repeat(
(exponent - num_digits_between_decimal_point_and_e)
.try_into()
.unwrap(),
);
let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat();
let n = expanded
.parse::<BigInt>()
.map_err(|_| ParseNumberError::Float)?;
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(n)),
num_integral_digits,
num_fractional_digits,
))
}
} else if is_minus_zero_float(s, &val) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(val)),
num_integral_digits,
num_fractional_digits,
))
}
}
/// Parse a hexadecimal integer from a string.
///
/// # Errors
///
/// This function returns an error if no [`BigInt`] could be parsed from
/// the string.
///
/// # Examples
///
/// ```rust,ignore
/// let actual = "0x0".parse::<Number>().unwrap().number;
/// let expected = Number::BigInt(BigInt::zero());
/// assert_eq!(actual, expected);
/// ```
fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
let (is_neg, s) = if s.starts_with('-') {
(true, &s[3..])
} else {
(false, &s[2..])
};
if s.starts_with('-') || s.starts_with('+') {
// Even though this is more like an invalid hexadecimal number,
// GNU reports this as an invalid floating point number, so we
// use `ParseNumberError::Float` to match that behavior.
return Err(ParseNumberError::Float);
}
let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?;
match (is_neg, num == BigInt::zero()) {
(true, true) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
2,
0,
)),
(true, false) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(-num)),
0,
0,
)),
(false, _) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(num)),
0,
0,
)),
}
}
impl FromStr for PreciseNumber {
type Err = ParseNumberError;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
// Trim leading whitespace.
s = s.trim_start();
// Trim a single leading "+" character.
if s.starts_with('+') {
s = &s[1..];
}
// Check if the string seems to be in hexadecimal format.
//
// May be 0x123 or -0x123, so the index `i` may be either 0 or 1.
if let Some(i) = s.to_lowercase().find("0x") {
if i <= 1 {
return parse_hexadecimal(s);
}
}
// Find the decimal point and the exponent symbol. Parse the
// number differently depending on its form. This is important
// because the form of the input dictates how the output will be
// presented.
match (s.find('.'), s.find('e')) {
// For example, "123456" or "inf".
(None, None) => parse_no_decimal_no_exponent(s),
// For example, "123e456" or "1e-2".
(None, Some(j)) => parse_exponent_no_decimal(s, j),
// For example, "123.456".
(Some(i), None) => parse_decimal_no_exponent(s, i),
// For example, "123.456e789".
(Some(i), Some(j)) if i < j => parse_decimal_and_exponent(s, i, j),
// For example, "1e2.3" or "1.2.3".
_ => Err(ParseNumberError::Float),
}
}
}
#[cfg(test)]
mod tests {
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
use crate::numberparse::ParseNumberError;
/// Convenience function for parsing a [`Number`] and unwrapping.
fn parse(s: &str) -> Number {
s.parse::<PreciseNumber>().unwrap().number
}
/// Convenience function for getting the number of integral digits.
fn num_integral_digits(s: &str) -> usize {
s.parse::<PreciseNumber>().unwrap().num_integral_digits
}
/// Convenience function for getting the number of fractional digits.
fn num_fractional_digits(s: &str) -> usize {
s.parse::<PreciseNumber>().unwrap().num_fractional_digits
}
#[test]
fn test_parse_minus_zero_int() {
assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero));
}
#[test]
fn test_parse_minus_zero_float() {
assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero));
assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero));
assert_eq!(
parse("-0.0e-1"),
Number::Float(ExtendedBigDecimal::MinusZero)
);
}
#[test]
fn test_parse_big_int() {
assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero()));
assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one()));
assert_eq!(
parse("1.0e1"),
Number::Int(ExtendedBigInt::BigInt("10".parse::<BigInt>().unwrap()))
);
}
#[test]
fn test_parse_hexadecimal_big_int() {
assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero()));
assert_eq!(
parse("0x10"),
Number::Int(ExtendedBigInt::BigInt("16".parse::<BigInt>().unwrap()))
);
}
#[test]
fn test_parse_big_decimal() {
assert_eq!(
parse("0.0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"0.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse(".0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"0.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse("1.0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"1.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse("10e-1"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"1.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse("-1e-3"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"-0.001".parse::<BigDecimal>().unwrap()
))
);
}
#[test]
fn test_parse_inf() {
assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity));
assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity));
assert_eq!(
parse("-inf"),
Number::Float(ExtendedBigDecimal::MinusInfinity)
);
}
#[test]
fn test_parse_invalid_float() {
assert_eq!(
"1.2.3".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
assert_eq!(
"1e2e3".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
assert_eq!(
"1e2.3".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
assert_eq!(
"-+-1".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
}
#[test]
fn test_parse_invalid_hex() {
assert_eq!(
"0xg".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Hex
);
}
#[test]
fn test_parse_invalid_nan() {
assert_eq!(
"nan".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"NAN".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"NaN".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"nAn".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"-nan".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
}
#[test]
fn test_num_integral_digits() {
// no decimal, no exponent
assert_eq!(num_integral_digits("123"), 3);
// decimal, no exponent
assert_eq!(num_integral_digits("123.45"), 3);
// exponent, no decimal
assert_eq!(num_integral_digits("123e4"), 3 + 4);
assert_eq!(num_integral_digits("123e-4"), 1);
assert_eq!(num_integral_digits("-1e-3"), 2);
// decimal and exponent
assert_eq!(num_integral_digits("123.45e6"), 3 + 6);
assert_eq!(num_integral_digits("123.45e-6"), 1);
assert_eq!(num_integral_digits("123.45e-1"), 2);
// minus zero int
assert_eq!(num_integral_digits("-0e0"), 2);
assert_eq!(num_integral_digits("-0e-0"), 2);
assert_eq!(num_integral_digits("-0e1"), 3);
assert_eq!(num_integral_digits("-0e+1"), 3);
assert_eq!(num_integral_digits("-0.0e1"), 3);
// minus zero float
assert_eq!(num_integral_digits("-0.0"), 2);
assert_eq!(num_integral_digits("-0e-1"), 2);
assert_eq!(num_integral_digits("-0.0e-1"), 2);
// TODO In GNU `seq`, the `-w` option does not seem to work with
// hexadecimal arguments. In order to match that behavior, we
// report the number of integral digits as zero for hexadecimal
// inputs.
assert_eq!(num_integral_digits("0xff"), 0);
}
#[test]
fn test_num_fractional_digits() {
// no decimal, no exponent
assert_eq!(num_fractional_digits("123"), 0);
assert_eq!(num_fractional_digits("0xff"), 0);
// decimal, no exponent
assert_eq!(num_fractional_digits("123.45"), 2);
// exponent, no decimal
assert_eq!(num_fractional_digits("123e4"), 0);
assert_eq!(num_fractional_digits("123e-4"), 4);
assert_eq!(num_fractional_digits("123e-1"), 1);
assert_eq!(num_fractional_digits("-1e-3"), 3);
// decimal and exponent
assert_eq!(num_fractional_digits("123.45e6"), 0);
assert_eq!(num_fractional_digits("123.45e1"), 1);
assert_eq!(num_fractional_digits("123.45e-6"), 8);
assert_eq!(num_fractional_digits("123.45e-1"), 3);
// minus zero int
assert_eq!(num_fractional_digits("-0e0"), 0);
assert_eq!(num_fractional_digits("-0e-0"), 0);
assert_eq!(num_fractional_digits("-0e1"), 0);
assert_eq!(num_fractional_digits("-0e+1"), 0);
assert_eq!(num_fractional_digits("-0.0e1"), 0);
// minus zero float
assert_eq!(num_fractional_digits("-0.0"), 1);
assert_eq!(num_fractional_digits("-0e-1"), 1);
assert_eq!(num_fractional_digits("-0.0e-1"), 2);
}
}

View file

@ -1,23 +1,24 @@
// TODO: Make -w flag work with decimals
// TODO: Support -f flag
// spell-checker:ignore (ToDO) istr chiter argptr ilen
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, AppSettings, Arg};
use num_bigint::BigInt;
use num_traits::One;
use num_traits::Zero;
use num_traits::{Num, ToPrimitive};
use std::cmp;
use std::io::{stdout, ErrorKind, Write};
use std::str::FromStr;
mod digits;
use crate::digits::num_fractional_digits;
use crate::digits::num_integral_digits;
mod extendedbigdecimal;
mod extendedbigint;
mod number;
mod numberparse;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
use crate::numberparse::ParseNumberError;
use uucore::display::Quotable;
@ -43,124 +44,55 @@ struct SeqOptions {
widths: bool,
}
enum Number {
/// Negative zero, as if it were an integer.
MinusZero,
BigInt(BigInt),
F64(f64),
}
impl Number {
fn is_zero(&self) -> bool {
match self {
Number::MinusZero => true,
Number::BigInt(n) => n.is_zero(),
Number::F64(n) => n.is_zero(),
}
}
fn into_f64(self) -> f64 {
match self {
Number::MinusZero => -0.,
// BigInt::to_f64() can not return None.
Number::BigInt(n) => n.to_f64().unwrap(),
Number::F64(n) => n,
}
}
/// Convert this number into a bigint, consuming it.
///
/// For floats, this returns the [`BigInt`] corresponding to the
/// floor of the number.
fn into_bigint(self) -> BigInt {
match self {
Number::MinusZero => BigInt::zero(),
Number::F64(x) => BigInt::from(x.floor() as i64),
Number::BigInt(n) => n,
}
}
}
impl FromStr for Number {
type Err = String;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
s = s.trim_start();
if s.starts_with('+') {
s = &s[1..];
}
let is_neg = s.starts_with('-');
match s.to_lowercase().find("0x") {
Some(i) if i <= 1 => match &s.as_bytes()[i + 2] {
b'-' | b'+' => Err(format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
// TODO: hexadecimal floating point parsing (see #2660)
b'.' => Err(format!(
"NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
_ => {
let num = BigInt::from_str_radix(&s[i + 2..], 16)
.map_err(|_| format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
))?;
match (is_neg, num == BigInt::zero()) {
(true, true) => Ok(Number::MinusZero),
(true, false) => Ok(Number::BigInt(-num)),
(false, _) => Ok(Number::BigInt(num)),
}
}
},
Some(_) => Err(format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
None => match s.parse::<BigInt>() {
Ok(n) => {
// If `s` is '-0', then `parse()` returns
// `BigInt::zero()`, but we need to return
// `Number::MinusZero` instead.
if n == BigInt::zero() && is_neg {
Ok(Number::MinusZero)
} else {
Ok(Number::BigInt(n))
}
}
Err(_) => match s.parse::<f64>() {
Ok(value) if value.is_nan() => Err(format!(
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
Ok(value) => Ok(Number::F64(value)),
Err(_) => Err(format!(
"invalid floating point argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
},
},
}
}
}
/// A range of integers.
///
/// The elements are (first, increment, last).
type RangeInt = (BigInt, BigInt, BigInt);
type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt);
/// A range of f64.
/// A range of floats.
///
/// The elements are (first, increment, last).
type RangeF64 = (f64, f64, f64);
type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal);
/// Terminate the process with error code 1.
///
/// Before terminating the process, this function prints an error
/// message that depends on `arg` and `e`.
///
/// Although the signature of this function states that it returns a
/// [`PreciseNumber`], it never reaches the return statement. It is just
/// there to make it easier to use this function when unwrapping the
/// result of calling [`str::parse`] when attempting to parse a
/// [`PreciseNumber`].
///
/// # Examples
///
/// ```rust,ignore
/// let s = "1.2e-3";
/// s.parse::<PreciseNumber>.unwrap_or_else(|e| exit_with_error(s, e))
/// ```
fn exit_with_error(arg: &str, e: ParseNumberError) -> ! {
match e {
ParseNumberError::Float => crash!(
1,
"invalid floating point argument: {}\nTry '{} --help' for more information.",
arg.quote(),
uucore::execution_phrase()
),
ParseNumberError::Nan => crash!(
1,
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
arg.quote(),
uucore::execution_phrase()
),
ParseNumberError::Hex => crash!(
1,
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
arg.quote(),
uucore::execution_phrase()
),
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = usage();
@ -174,53 +106,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
widths: matches.is_present(OPT_WIDTHS),
};
let mut largest_dec = 0;
let mut padding = 0;
let first = if numbers.len() > 1 {
let slice = numbers[0];
largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
padding = num_integral_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
crash_if_err!(1, slice.parse())
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
} else {
Number::BigInt(BigInt::one())
PreciseNumber::one()
};
let increment = if numbers.len() > 2 {
let slice = numbers[1];
let dec = num_fractional_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
largest_dec = cmp::max(largest_dec, dec);
padding = cmp::max(padding, int_digits);
crash_if_err!(1, slice.parse())
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
} else {
Number::BigInt(BigInt::one())
PreciseNumber::one()
};
if increment.is_zero() {
show_error!(
@ -230,54 +126,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
);
return 1;
}
let last: Number = {
let last: PreciseNumber = {
let slice = numbers[numbers.len() - 1];
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
padding = cmp::max(padding, int_digits);
crash_if_err!(1, slice.parse())
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
};
let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0;
let result = match (first, last, increment) {
// For example, `seq -0 1 2` or `seq -0 1 2.0`.
(Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers(
(BigInt::zero(), increment, last.into_bigint()),
options.separator,
options.terminator,
options.widths,
padding,
true,
),
// For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`.
(Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => {
let padding = first
.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 result = match (first.number, increment.number, last.number) {
(Number::Int(first), Number::Int(increment), last) => {
let last = last.round_towards(&first);
print_seq_integers(
(BigInt::zero(), increment, last.into_bigint()),
(first, increment, last),
options.separator,
options.terminator,
options.widths,
padding,
true,
)
}
// For example, `seq 0 1 2` or `seq 0 1 2.0`.
(Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers(
(first, increment, last.into_bigint()),
options.separator,
options.terminator,
options.widths,
padding,
false,
),
// For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`.
(first, last, increment) => print_seq(
(first.into_f64(), increment.into_f64(), last.into_f64()),
(first, increment, last) => print_seq(
(
first.into_extended_big_decimal(),
increment.into_extended_big_decimal(),
last.into_extended_big_decimal(),
),
largest_dec,
options.separator,
options.terminator,
@ -329,7 +207,7 @@ pub fn uu_app() -> App<'static, 'static> {
)
}
fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
fn done_printing<T: Zero + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
if increment >= &T::zero() {
next > last
} else {
@ -337,9 +215,65 @@ fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool
}
}
/// Write a big decimal formatted according to the given parameters.
///
/// This method is an adapter to support displaying negative zero on
/// Rust versions earlier than 1.53.0. After that version, we should be
/// able to display negative zero using the default formatting provided
/// by `-0.0f32`, for example.
fn write_value_float(
writer: &mut impl Write,
value: &ExtendedBigDecimal,
width: usize,
precision: usize,
is_first_iteration: bool,
) -> std::io::Result<()> {
let value_as_str = if *value == ExtendedBigDecimal::MinusZero && is_first_iteration {
format!(
"-{value:>0width$.precision$}",
value = value,
width = if width > 0 { width - 1 } else { width },
precision = precision,
)
} else {
format!(
"{value:>0width$.precision$}",
value = value,
width = width,
precision = precision,
)
};
write!(writer, "{}", value_as_str)
}
/// Write a big int formatted according to the given parameters.
fn write_value_int(
writer: &mut impl Write,
value: &ExtendedBigInt,
width: usize,
pad: bool,
is_first_iteration: bool,
) -> std::io::Result<()> {
let value_as_str = if pad {
if *value == ExtendedBigInt::MinusZero && is_first_iteration {
format!("-{value:>0width$}", value = value, width = width - 1,)
} else {
format!("{value:>0width$}", value = value, width = width,)
}
} else if *value == ExtendedBigInt::MinusZero && is_first_iteration {
format!("-{}", value)
} else {
format!("{}", value)
};
write!(writer, "{}", value_as_str)
}
// TODO `print_seq()` and `print_seq_integers()` are nearly identical,
// they could be refactored into a single more general function.
/// Floating point based code path
fn print_seq(
range: RangeF64,
range: RangeFloat,
largest_dec: usize,
separator: String,
terminator: String,
@ -349,30 +283,23 @@ fn print_seq(
let stdout = stdout();
let mut stdout = stdout.lock();
let (first, increment, last) = range;
let mut i = 0isize;
let is_first_minus_zero = first == -0.0 && first.is_sign_negative();
let mut value = first + i as f64 * increment;
let mut value = first;
let padding = if pad { padding + 1 + largest_dec } else { 0 };
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
write!(stdout, "{}", separator)?;
}
let mut width = padding;
if is_first_iteration && is_first_minus_zero {
write!(stdout, "-")?;
width -= 1;
}
is_first_iteration = false;
write!(
stdout,
"{value:>0width$.precision$}",
value = value,
width = width,
precision = largest_dec,
write_value_float(
&mut stdout,
&value,
padding,
largest_dec,
is_first_iteration,
)?;
i += 1;
value = first + i as f64 * increment;
// TODO Implement augmenting addition.
value = value + increment.clone();
is_first_iteration = false;
}
if !is_first_iteration {
write!(stdout, "{}", terminator)?;
@ -401,7 +328,6 @@ fn print_seq_integers(
terminator: String,
pad: bool,
padding: usize,
is_first_minus_zero: bool,
) -> std::io::Result<()> {
let stdout = stdout();
let mut stdout = stdout.lock();
@ -412,18 +338,10 @@ fn print_seq_integers(
if !is_first_iteration {
write!(stdout, "{}", separator)?;
}
let mut width = padding;
if is_first_iteration && is_first_minus_zero {
write!(stdout, "-")?;
width -= 1;
}
write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?;
// TODO Implement augmenting addition.
value = value + increment.clone();
is_first_iteration = false;
if pad {
write!(stdout, "{number:>0width$}", number = value, width = width)?;
} else {
write!(stdout, "{}", value)?;
}
value += &increment;
}
if !is_first_iteration {

View file

@ -7,25 +7,25 @@ fn test_hex_rejects_sign_after_identifier() {
.args(&["0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
.stderr_contains("invalid floating point argument: '0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
.stderr_contains("invalid floating point argument: '0x+123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
.stderr_contains("invalid floating point argument: '-0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
.stderr_contains("invalid floating point argument: '-0x+123ABC'")
.stderr_contains("for more information.");
}
@ -60,7 +60,7 @@ fn test_hex_identifier_in_wrong_place() {
.args(&["1234ABCD0x"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
.stderr_contains("invalid floating point argument: '1234ABCD0x'")
.stderr_contains("for more information.");
}
@ -479,6 +479,15 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() {
.no_stderr();
}
#[test]
fn test_width_negative_scientific_notation() {
new_ucmd!()
.args(&["-w", "-1e-3", "1"])
.succeeds()
.stdout_is("-0.001\n00.999\n")
.no_stderr();
}
/// Test that trailing zeros in the end argument do not contribute to width.
#[test]
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
@ -544,3 +553,63 @@ fn test_trailing_whitespace_error() {
// --help' for more information."
.stderr_contains("for more information.");
}
#[test]
fn test_negative_zero_int_start_float_increment() {
new_ucmd!()
.args(&["-0", "0.1", "0.1"])
.succeeds()
.stdout_is("-0.0\n0.1\n")
.no_stderr();
}
#[test]
fn test_float_precision_increment() {
new_ucmd!()
.args(&["999", "0.1", "1000.1"])
.succeeds()
.stdout_is(
"999.0
999.1
999.2
999.3
999.4
999.5
999.6
999.7
999.8
999.9
1000.0
1000.1
",
)
.no_stderr();
}
/// Test for floating point precision issues.
#[test]
fn test_negative_increment_decimal() {
new_ucmd!()
.args(&["0.1", "-0.1", "-0.2"])
.succeeds()
.stdout_is("0.1\n0.0\n-0.1\n-0.2\n")
.no_stderr();
}
#[test]
fn test_zero_not_first() {
new_ucmd!()
.args(&["-w", "-0.1", "0.1", "0.1"])
.succeeds()
.stdout_is("-0.1\n00.0\n00.1\n")
.no_stderr();
}
#[test]
fn test_rounding_end() {
new_ucmd!()
.args(&["1", "-1", "0.1"])
.succeeds()
.stdout_is("1\n")
.no_stderr();
}