Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions src/uu/seq/src/digits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//! 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);
}
}
}
124 changes: 57 additions & 67 deletions src/uu/seq/src/seq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ 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;

use uucore::display::Quotable;

static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT.";
Expand Down Expand Up @@ -62,38 +67,6 @@ impl Number {
Number::F64(n) => n,
}
}

/// Number of characters needed to print the integral part of the number.
///
/// The number of characters includes one character to represent the
/// minus sign ("-") if this number is negative.
///
/// # Examples
///
/// ```rust,ignore
/// use num_bigint::{BigInt, Sign};
///
/// assert_eq!(
/// Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(),
/// 3
/// );
/// assert_eq!(
/// Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(),
/// 4
/// );
/// assert_eq!(Number::F64(123.45).num_digits(), 3);
/// assert_eq!(Number::MinusZero.num_digits(), 2);
/// ```
fn num_digits(&self) -> usize {
match self {
Number::MinusZero => 2,
Number::BigInt(n) => n.to_string().len(),
Number::F64(n) => {
let s = n.to_string();
s.find('.').unwrap_or_else(|| s.len())
}
}
}
}

impl FromStr for Number {
Expand Down Expand Up @@ -155,20 +128,49 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};

let mut largest_dec = 0;
let mut padding = 0;
let first = if numbers.len() > 1 {
let slice = numbers[0];
let len = slice.len();
let dec = slice.find('.').unwrap_or(len);
largest_dec = len - dec;
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())
} else {
Number::BigInt(BigInt::one())
};
let increment = if numbers.len() > 2 {
let slice = numbers[1];
let len = slice.len();
let dec = slice.find('.').unwrap_or(len);
largest_dec = cmp::max(largest_dec, len - dec);
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())
} else {
Number::BigInt(BigInt::one())
Expand All @@ -183,16 +185,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
let last: Number = {
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())
};
if largest_dec > 0 {
largest_dec -= 1;
}

let padding = first
.num_digits()
.max(increment.num_digits())
.max(last.num_digits());
let result = match (first, last, increment) {
(Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers(
(BigInt::zero(), increment, last),
Expand Down Expand Up @@ -286,18 +290,24 @@ fn print_seq(
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 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;
let istr = format!("{:.*}", largest_dec, value);
let ilen = istr.len();
let before_dec = istr.find('.').unwrap_or(ilen);
if pad && before_dec < padding {
for _ in 0..(padding - before_dec) {
if pad && before_dec < width {
for _ in 0..(width - before_dec) {
write!(stdout, "0")?;
}
}
Expand Down Expand Up @@ -362,23 +372,3 @@ fn print_seq_integers(
}
Ok(())
}

#[cfg(test)]
mod tests {
use crate::Number;
use num_bigint::{BigInt, Sign};

#[test]
fn test_number_num_digits() {
assert_eq!(
Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(),
3
);
assert_eq!(
Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(),
4
);
assert_eq!(Number::F64(123.45).num_digits(), 3);
assert_eq!(Number::MinusZero.num_digits(), 2);
}
}
Loading