mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Add comments and an additional test
This commit is contained in:
parent
565af8237b
commit
a0bd88b51b
2 changed files with 93 additions and 40 deletions
|
@ -7,8 +7,18 @@ use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnaly
|
|||
|
||||
const SIGNIFICANT_FIGURES: usize = 6;
|
||||
|
||||
// Parse a numeric string as the nearest integer with a given significance.
|
||||
// This is a helper function for round().
|
||||
// Examples:
|
||||
// round_to_significance("456", 1) == 500
|
||||
// round_to_significance("456", 2) == 460
|
||||
// round_to_significance("456", 9) == 456
|
||||
fn round_to_significance(input: &str, significant_figures: usize) -> u32 {
|
||||
if significant_figures < input.len() {
|
||||
// If the input has too many digits, use a float intermediary
|
||||
// to round it before converting to an integer. Otherwise,
|
||||
// converting straight to integer will truncate.
|
||||
// There might be a cleaner way to do this...
|
||||
let digits = &input[..significant_figures + 1];
|
||||
let float_representation = digits.parse::<f32>().unwrap();
|
||||
(float_representation / 10.0).round() as u32
|
||||
|
@ -17,16 +27,45 @@ fn round_to_significance(input: &str, significant_figures: usize) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
fn round(mut format: FormatPrimitive) -> FormatPrimitive {
|
||||
let mut significant_figures = SIGNIFICANT_FIGURES;
|
||||
// Removing trailing zeroes, expressing the result as an integer where
|
||||
// possible. This is a helper function for round().
|
||||
fn truncate(mut format: FormatPrimitive) -> FormatPrimitive {
|
||||
if let Some(ref post_dec) = format.post_decimal {
|
||||
let trimmed = post_dec.trim_end_matches('0');
|
||||
|
||||
if trimmed.is_empty() {
|
||||
// If there are no nonzero digits after the decimal point,
|
||||
// use integer formatting by clearing post_decimal and suffix.
|
||||
format.post_decimal = Some("".into());
|
||||
if format.suffix == Some("e+00".into()) {
|
||||
format.suffix = Some("".into());
|
||||
}
|
||||
} else if trimmed.len() != post_dec.len() {
|
||||
// Otherwise, update the format to remove only the trailing
|
||||
// zeroes (e.g. "4.50" becomes "4.5", not "4"). If there were
|
||||
// no trailing zeroes, do nothing.
|
||||
format.post_decimal = Some(trimmed.to_owned());
|
||||
}
|
||||
}
|
||||
format
|
||||
}
|
||||
|
||||
// Round a format to six significant figures and remove trailing zeroes.
|
||||
fn round(mut format: FormatPrimitive) -> FormatPrimitive {
|
||||
let mut significant_digits_remaining = SIGNIFICANT_FIGURES;
|
||||
|
||||
// First, take as many significant digits as possible from pre_decimal,
|
||||
if format.pre_decimal.is_some() {
|
||||
let input = format.pre_decimal.as_ref().unwrap();
|
||||
let rounded = round_to_significance(input, significant_figures);
|
||||
let rounded = round_to_significance(input, significant_digits_remaining);
|
||||
let mut rounded_str = rounded.to_string();
|
||||
significant_figures -= rounded_str.len();
|
||||
significant_digits_remaining -= rounded_str.len();
|
||||
|
||||
if significant_figures == 0 {
|
||||
// If the pre_decimal has exactly enough significant digits,
|
||||
// round the input to the nearest integer. If the first
|
||||
// post_decimal digit is 5 or higher, round up by incrementing
|
||||
// the pre_decimal number. Otherwise, use the pre_decimal as-is.
|
||||
if significant_digits_remaining == 0 {
|
||||
if let Some(digits) = &format.post_decimal {
|
||||
if digits.chars().next().unwrap_or('0') >= '5' {
|
||||
let rounded = rounded + 1;
|
||||
|
@ -37,46 +76,51 @@ fn round(mut format: FormatPrimitive) -> FormatPrimitive {
|
|||
format.pre_decimal = Some(rounded_str);
|
||||
}
|
||||
|
||||
if significant_figures == 0 {
|
||||
// If no significant digits remain, or there's no post_decimal to
|
||||
// round, return the rounded pre_decimal value with no post_decimal.
|
||||
// Otherwise, round the post_decimal to the remaining significance.
|
||||
if significant_digits_remaining == 0 {
|
||||
format.post_decimal = Some(String::new());
|
||||
} else if let Some(input) = format.post_decimal {
|
||||
let leading_zeroes = input.len() - input.trim_start_matches('0').len();
|
||||
let digits = &input[leading_zeroes..];
|
||||
|
||||
let rounded_str = if leading_zeroes <= significant_figures {
|
||||
let mut post_decimal = String::with_capacity(significant_figures);
|
||||
for _ in 0..leading_zeroes {
|
||||
post_decimal.push('0');
|
||||
}
|
||||
|
||||
significant_figures -= leading_zeroes;
|
||||
let rounded = round_to_significance(&input[leading_zeroes..], significant_figures);
|
||||
post_decimal.push_str(&rounded.to_string());
|
||||
post_decimal
|
||||
} else {
|
||||
input[..significant_figures].to_string()
|
||||
};
|
||||
format.post_decimal = Some(rounded_str);
|
||||
}
|
||||
format
|
||||
}
|
||||
|
||||
fn truncate(mut format: FormatPrimitive) -> FormatPrimitive {
|
||||
if let Some(ref post_dec) = format.post_decimal {
|
||||
let trimmed = post_dec.trim_end_matches('0');
|
||||
|
||||
if trimmed.is_empty() {
|
||||
format.post_decimal = Some("".into());
|
||||
if format.suffix == Some("e+00".into()) {
|
||||
format.suffix = Some("".into());
|
||||
}
|
||||
} else if trimmed.len() != post_dec.len() {
|
||||
format.post_decimal = Some(trimmed.to_owned());
|
||||
// In the post_decimal, leading zeroes are significant. "01.0010"
|
||||
// has one significant digit in pre_decimal, and 3 from post_decimal.
|
||||
let mut post_decimal_str = String::with_capacity(significant_digits_remaining);
|
||||
for _ in 0..leading_zeroes {
|
||||
post_decimal_str.push('0');
|
||||
}
|
||||
|
||||
if leading_zeroes < significant_digits_remaining {
|
||||
// After significant leading zeroes, round the remaining digits
|
||||
// to any remaining significance.
|
||||
let rounded = round_to_significance(digits, significant_digits_remaining);
|
||||
post_decimal_str.push_str(&rounded.to_string());
|
||||
} else if leading_zeroes == significant_digits_remaining
|
||||
&& digits.chars().next().unwrap_or('0') >= '5'
|
||||
{
|
||||
// If necessary, round up the post_decimal ("1.000009" should
|
||||
// round to 1.00001, instead of truncating after the last
|
||||
// significant leading zero).
|
||||
post_decimal_str.pop();
|
||||
post_decimal_str.push('1');
|
||||
} else {
|
||||
// If the rounded post_decimal is entirely zeroes, discard
|
||||
// it and use integer formatting instead.
|
||||
post_decimal_str = "".into();
|
||||
}
|
||||
|
||||
format.post_decimal = Some(post_decimal_str);
|
||||
}
|
||||
format
|
||||
truncate(format)
|
||||
}
|
||||
|
||||
fn is_float_magnitude(suffix: &Option<String>) -> bool {
|
||||
// Given an exponent used in scientific notation, return whether the
|
||||
// number is small enough to be expressed as a decimal instead. "Small
|
||||
// enough" is based only on the number's magnitude, not the length of
|
||||
// any string representation.
|
||||
fn should_represent_as_decimal(suffix: &Option<String>) -> bool {
|
||||
match suffix {
|
||||
Some(exponent) => {
|
||||
if exponent.chars().nth(1) == Some('-') {
|
||||
|
@ -121,7 +165,9 @@ impl Formatter for Decf {
|
|||
Some(*field.field_char == 'G'),
|
||||
);
|
||||
|
||||
if is_float_magnitude(&f_dec.suffix) {
|
||||
if should_represent_as_decimal(&f_dec.suffix) {
|
||||
// Use decimal formatting instead of scientific notation
|
||||
// if the input's magnitude is small.
|
||||
f_dec = get_primitive_dec(
|
||||
initial_prefix,
|
||||
&str_in[initial_prefix.offset..],
|
||||
|
@ -131,8 +177,7 @@ impl Formatter for Decf {
|
|||
);
|
||||
}
|
||||
|
||||
f_dec = truncate(round(f_dec));
|
||||
Some(f_dec)
|
||||
Some(round(f_dec))
|
||||
}
|
||||
fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String {
|
||||
primitive_to_str_common(prim, &field)
|
||||
|
|
|
@ -514,3 +514,11 @@ fn sub_general_round_float_to_integer() {
|
|||
.succeeds()
|
||||
.stdout_only("123457");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_general_round_float_leading_zeroes() {
|
||||
new_ucmd!()
|
||||
.args(&["%g", "1.000009"])
|
||||
.succeeds()
|
||||
.stdout_only("1.00001");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue