plotters/data/
float.rs

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
// The code that is related to float number handling
fn find_minimal_repr(n: f64, eps: f64) -> (f64, usize) {
    if eps >= 1.0 {
        return (n, 0);
    }
    if n - n.floor() < eps {
        (n.floor(), 0)
    } else if n.ceil() - n < eps {
        (n.ceil(), 0)
    } else {
        let (rem, pre) = find_minimal_repr((n - n.floor()) * 10.0, eps * 10.0);
        (n.floor() + rem / 10.0, pre + 1)
    }
}

fn float_to_string(n: f64, max_precision: usize, min_decimal: usize) -> String {
    let (mut result, mut count) = loop {
        let (sign, n) = if n < 0.0 { ("-", -n) } else { ("", n) };
        let int_part = n.floor();

        let dec_part =
            ((n.abs() - int_part.abs()) * (10.0f64).powi(max_precision as i32)).round() as u64;

        if dec_part == 0 || max_precision == 0 {
            break (format!("{}{:.0}", sign, int_part), 0);
        }

        let mut leading = "".to_string();
        let mut dec_result = format!("{}", dec_part);

        for _ in 0..(max_precision - dec_result.len()) {
            leading.push('0');
        }

        while let Some(c) = dec_result.pop() {
            if c != '0' {
                dec_result.push(c);
                break;
            }
        }

        break (
            format!("{}{:.0}.{}{}", sign, int_part, leading, dec_result),
            leading.len() + dec_result.len(),
        );
    };

    if count == 0 && min_decimal > 0 {
        result.push('.');
    }

    while count < min_decimal {
        result.push('0');
        count += 1;
    }
    result
}

pub struct FloatPrettyPrinter {
    pub allow_scientific: bool,
    pub min_decimal: i32,
    pub max_decimal: i32,
}

impl FloatPrettyPrinter {
    pub fn print(&self, n: f64) -> String {
        let (tn, p) = find_minimal_repr(n, (10f64).powi(-self.max_decimal));
        let d_repr = float_to_string(tn, p, self.min_decimal as usize);
        if !self.allow_scientific {
            d_repr
        } else {
            if n == 0.0 {
                return "0".to_string();
            }

            let mut idx = n.abs().log10().floor();
            let mut exp = (10.0f64).powf(idx);

            if n.abs() / exp + 1e-5 >= 10.0 {
                idx += 1.0;
                exp *= 10.0;
            }

            if idx.abs() < 3.0 {
                return d_repr;
            }

            let (sn, sp) = find_minimal_repr(n / exp, 1e-5);
            let s_repr = format!(
                "{}e{}",
                float_to_string(sn, sp, self.min_decimal as usize),
                float_to_string(idx, 0, 0)
            );
            if s_repr.len() + 1 < d_repr.len() || (tn == 0.0 && n != 0.0) {
                s_repr
            } else {
                d_repr
            }
        }
    }
}

/// The function that pretty prints the floating number
/// Since rust doesn't have anything that can format a float with out appearance, so we just
/// implemnet a float pretty printing function, which finds the shortest representation of a
/// floating point number within the allowed error range.
///
/// - `n`: The float number to pretty-print
/// - `allow_sn`: Should we use scientific notation when possible
/// - **returns**: The pretty printed string
pub fn pretty_print_float(n: f64, allow_sn: bool) -> String {
    (FloatPrettyPrinter {
        allow_scientific: allow_sn,
        min_decimal: 0,
        max_decimal: 10,
    })
    .print(n)
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_pretty_printing() {
        assert_eq!(pretty_print_float(0.99999999999999999999, false), "1");
        assert_eq!(pretty_print_float(0.9999, false), "0.9999");
        assert_eq!(
            pretty_print_float(-1e-5 - 0.00000000000000001, true),
            "-1e-5"
        );
        assert_eq!(
            pretty_print_float(-1e-5 - 0.00000000000000001, false),
            "-0.00001"
        );
        assert_eq!(pretty_print_float(1e100, true), "1e100");
        assert_eq!(pretty_print_float(1234567890f64, true), "1234567890");
        assert_eq!(pretty_print_float(1000000001f64, true), "1e9");
    }
}