1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
// 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 (vars) charf decf floatf intf scif strf Cninety
//! formatter for %g %G decimal subs
use super::super::format_field::FormatField;
use super::super::formatter::{FormatPrimitive, Formatter, InitialPrefix};
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
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
} else {
input.parse::<u32>().unwrap_or(0)
}
}
// 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(String::new());
if format.suffix == Some("e+00".into()) {
format.suffix = Some(String::new());
}
} 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_digits_remaining);
let mut rounded_str = rounded.to_string();
significant_digits_remaining -= rounded_str.len();
// 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;
rounded_str = rounded.to_string();
}
}
}
format.pre_decimal = Some(rounded_str);
}
// 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..];
// 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 = String::new();
}
format.post_decimal = Some(post_decimal_str);
}
truncate(format)
}
// 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('-') {
exponent < &"e-05".into()
} else {
exponent < &"e+06".into()
}
}
None => true,
}
}
pub struct Decf;
impl Decf {
pub fn new() -> Self {
Self
}
}
impl Formatter for Decf {
fn get_primitive(
&self,
field: &FormatField,
initial_prefix: &InitialPrefix,
str_in: &str,
) -> Option<FormatPrimitive> {
let second_field = field.second_field.unwrap_or(6) + 1;
// default to scif interpretation so as to not truncate input vals
// (that would be displayed in scif) based on relation to decimal place
let analysis = FloatAnalysis::analyze(
str_in,
initial_prefix,
Some(second_field as usize + 1),
None,
false,
);
let mut f_dec = get_primitive_dec(
initial_prefix,
&str_in[initial_prefix.offset..],
&analysis,
second_field as usize,
Some(*field.field_char == 'G'),
);
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..],
&analysis,
second_field as usize,
None,
);
}
Some(round(f_dec))
}
fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String {
primitive_to_str_common(prim, &field)
}
}