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)
    }
}