Skip to content

Commit 2ac5dc0

Browse files
committed
seq: compute correct width for scientific notation
Change the way `seq` computes the number of digits needed to print a number so that it works for inputs given in scientific notation. Specifically, this commit parses the input string to determine whether it is an integer, a float in decimal notation, or a float in scientific notation, and then computes the number of integral digits and the number of fractional digits based on that. This also supports floating point negative zero, expressed in both decimal and scientific notation.
1 parent 88a6890 commit 2ac5dc0

File tree

3 files changed

+381
-15
lines changed

3 files changed

+381
-15
lines changed

src/uu/seq/src/digits.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//! Counting number of digits needed to represent a number.
2+
//!
3+
//! The [`num_integral_digits`] and [`num_fractional_digits`] functions
4+
//! count the number of digits needed to represent a number in decimal
5+
//! notation (like "123.456").
6+
use std::convert::TryInto;
7+
use std::num::ParseIntError;
8+
9+
use uucore::display::Quotable;
10+
11+
/// The number of digits after the decimal point in a given number.
12+
///
13+
/// The input `s` is a string representing a number, either an integer
14+
/// or a floating point number in either decimal notation or scientific
15+
/// notation. This function returns the number of digits after the
16+
/// decimal point needed to print the number in decimal notation.
17+
///
18+
/// # Examples
19+
///
20+
/// ```rust,ignore
21+
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
22+
/// ```
23+
pub fn num_fractional_digits(s: &str) -> Result<usize, ParseIntError> {
24+
match (s.find('.'), s.find('e')) {
25+
// For example, "123456".
26+
(None, None) => Ok(0),
27+
28+
// For example, "123e456".
29+
(None, Some(j)) => {
30+
let exponent: i64 = s[j + 1..].parse()?;
31+
if exponent < 0 {
32+
Ok(-exponent as usize)
33+
} else {
34+
Ok(0)
35+
}
36+
}
37+
38+
// For example, "123.456".
39+
(Some(i), None) => Ok(s.len() - (i + 1)),
40+
41+
// For example, "123.456e789".
42+
(Some(i), Some(j)) if i < j => {
43+
// Because of the match guard, this subtraction will not underflow.
44+
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
45+
let exponent: i64 = s[j + 1..].parse()?;
46+
if num_digits_between_decimal_point_and_e < exponent {
47+
Ok(0)
48+
} else {
49+
Ok((num_digits_between_decimal_point_and_e - exponent)
50+
.try_into()
51+
.unwrap())
52+
}
53+
}
54+
_ => crash!(
55+
1,
56+
"invalid floating point argument: {}\n Try '{} --help' for more information.",
57+
s.quote(),
58+
uucore::execution_phrase()
59+
),
60+
}
61+
}
62+
63+
/// The number of digits before the decimal point in a given number.
64+
///
65+
/// The input `s` is a string representing a number, either an integer
66+
/// or a floating point number in either decimal notation or scientific
67+
/// notation. This function returns the number of digits before the
68+
/// decimal point needed to print the number in decimal notation.
69+
///
70+
/// # Examples
71+
///
72+
/// ```rust,ignore
73+
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2);
74+
/// ```
75+
pub fn num_integral_digits(s: &str) -> Result<usize, ParseIntError> {
76+
match (s.find('.'), s.find('e')) {
77+
// For example, "123456".
78+
(None, None) => Ok(s.len()),
79+
80+
// For example, "123e456".
81+
(None, Some(j)) => {
82+
let exponent: i64 = s[j + 1..].parse()?;
83+
let total = j as i64 + exponent;
84+
if total < 1 {
85+
Ok(1)
86+
} else {
87+
Ok(total.try_into().unwrap())
88+
}
89+
}
90+
91+
// For example, "123.456".
92+
(Some(i), None) => Ok(i),
93+
94+
// For example, "123.456e789".
95+
(Some(i), Some(j)) => {
96+
let exponent: i64 = s[j + 1..].parse()?;
97+
let minimum: usize = {
98+
let integral_part: f64 = crash_if_err!(1, s[..j].parse());
99+
if integral_part == -0.0 && integral_part.is_sign_negative() {
100+
2
101+
} else {
102+
1
103+
}
104+
};
105+
106+
let total = i as i64 + exponent;
107+
if total < minimum as i64 {
108+
Ok(minimum)
109+
} else {
110+
Ok(total.try_into().unwrap())
111+
}
112+
}
113+
}
114+
}
115+
116+
#[cfg(test)]
117+
mod tests {
118+
119+
mod test_num_integral_digits {
120+
use crate::num_integral_digits;
121+
122+
#[test]
123+
fn test_integer() {
124+
assert_eq!(num_integral_digits("123").unwrap(), 3);
125+
}
126+
127+
#[test]
128+
fn test_decimal() {
129+
assert_eq!(num_integral_digits("123.45").unwrap(), 3);
130+
}
131+
132+
#[test]
133+
fn test_scientific_no_decimal_positive_exponent() {
134+
assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4);
135+
}
136+
137+
#[test]
138+
fn test_scientific_with_decimal_positive_exponent() {
139+
assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6);
140+
}
141+
142+
#[test]
143+
fn test_scientific_no_decimal_negative_exponent() {
144+
assert_eq!(num_integral_digits("123e-4").unwrap(), 1);
145+
}
146+
147+
#[test]
148+
fn test_scientific_with_decimal_negative_exponent() {
149+
assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1);
150+
assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2);
151+
}
152+
}
153+
154+
mod test_num_fractional_digits {
155+
use crate::num_fractional_digits;
156+
157+
#[test]
158+
fn test_integer() {
159+
assert_eq!(num_fractional_digits("123").unwrap(), 0);
160+
}
161+
162+
#[test]
163+
fn test_decimal() {
164+
assert_eq!(num_fractional_digits("123.45").unwrap(), 2);
165+
}
166+
167+
#[test]
168+
fn test_scientific_no_decimal_positive_exponent() {
169+
assert_eq!(num_fractional_digits("123e4").unwrap(), 0);
170+
}
171+
172+
#[test]
173+
fn test_scientific_with_decimal_positive_exponent() {
174+
assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0);
175+
assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1);
176+
}
177+
178+
#[test]
179+
fn test_scientific_no_decimal_negative_exponent() {
180+
assert_eq!(num_fractional_digits("123e-4").unwrap(), 4);
181+
assert_eq!(num_fractional_digits("123e-1").unwrap(), 1);
182+
}
183+
184+
#[test]
185+
fn test_scientific_with_decimal_negative_exponent() {
186+
assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8);
187+
assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
188+
}
189+
}
190+
}

src/uu/seq/src/seq.rs

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ use num_traits::{Num, ToPrimitive};
1414
use std::cmp;
1515
use std::io::{stdout, ErrorKind, Write};
1616
use std::str::FromStr;
17+
18+
mod digits;
19+
use crate::digits::num_fractional_digits;
20+
use crate::digits::num_integral_digits;
21+
1722
use uucore::display::Quotable;
1823

1924
static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT.";
@@ -155,20 +160,49 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
155160
};
156161

157162
let mut largest_dec = 0;
163+
let mut padding = 0;
158164
let first = if numbers.len() > 1 {
159165
let slice = numbers[0];
160-
let len = slice.len();
161-
let dec = slice.find('.').unwrap_or(len);
162-
largest_dec = len - dec;
166+
largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| {
167+
crash!(
168+
1,
169+
"invalid floating point argument: {}\n Try '{} --help' for more information.",
170+
slice.quote(),
171+
uucore::execution_phrase()
172+
)
173+
});
174+
padding = num_integral_digits(slice).unwrap_or_else(|_| {
175+
crash!(
176+
1,
177+
"invalid floating point argument: {}\n Try '{} --help' for more information.",
178+
slice.quote(),
179+
uucore::execution_phrase()
180+
)
181+
});
163182
crash_if_err!(1, slice.parse())
164183
} else {
165184
Number::BigInt(BigInt::one())
166185
};
167186
let increment = if numbers.len() > 2 {
168187
let slice = numbers[1];
169-
let len = slice.len();
170-
let dec = slice.find('.').unwrap_or(len);
171-
largest_dec = cmp::max(largest_dec, len - dec);
188+
let dec = num_fractional_digits(slice).unwrap_or_else(|_| {
189+
crash!(
190+
1,
191+
"invalid floating point argument: {}\n Try '{} --help' for more information.",
192+
slice.quote(),
193+
uucore::execution_phrase()
194+
)
195+
});
196+
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
197+
crash!(
198+
1,
199+
"invalid floating point argument: {}\n Try '{} --help' for more information.",
200+
slice.quote(),
201+
uucore::execution_phrase()
202+
)
203+
});
204+
largest_dec = cmp::max(largest_dec, dec);
205+
padding = cmp::max(padding, int_digits);
172206
crash_if_err!(1, slice.parse())
173207
} else {
174208
Number::BigInt(BigInt::one())
@@ -183,16 +217,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
183217
}
184218
let last: Number = {
185219
let slice = numbers[numbers.len() - 1];
220+
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
221+
crash!(
222+
1,
223+
"invalid floating point argument: {}\n Try '{} --help' for more information.",
224+
slice.quote(),
225+
uucore::execution_phrase()
226+
)
227+
});
228+
padding = cmp::max(padding, int_digits);
186229
crash_if_err!(1, slice.parse())
187230
};
188-
if largest_dec > 0 {
189-
largest_dec -= 1;
190-
}
191231

192-
let padding = first
193-
.num_digits()
194-
.max(increment.num_digits())
195-
.max(last.num_digits());
196232
let result = match (first, last, increment) {
197233
(Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers(
198234
(BigInt::zero(), increment, last),
@@ -286,18 +322,24 @@ fn print_seq(
286322
let mut stdout = stdout.lock();
287323
let (first, increment, last) = range;
288324
let mut i = 0isize;
325+
let is_first_minus_zero = first == -0.0 && first.is_sign_negative();
289326
let mut value = first + i as f64 * increment;
290327
let mut is_first_iteration = true;
291328
while !done_printing(&value, &increment, &last) {
292329
if !is_first_iteration {
293330
write!(stdout, "{}", separator)?;
294331
}
332+
let mut width = padding;
333+
if is_first_iteration && is_first_minus_zero {
334+
write!(stdout, "-")?;
335+
width -= 1;
336+
}
295337
is_first_iteration = false;
296338
let istr = format!("{:.*}", largest_dec, value);
297339
let ilen = istr.len();
298340
let before_dec = istr.find('.').unwrap_or(ilen);
299-
if pad && before_dec < padding {
300-
for _ in 0..(padding - before_dec) {
341+
if pad && before_dec < width {
342+
for _ in 0..(width - before_dec) {
301343
write!(stdout, "0")?;
302344
}
303345
}

0 commit comments

Comments
 (0)