Contents
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
// 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)
}
}
#[allow(clippy::never_loop)]
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
}
/// Handles printing of floating point numbers
pub struct FloatPrettyPrinter {
/// Whether scientific notation is allowed
pub allow_scientific: bool,
/// Minimum allowed number of decimal digits
pub min_decimal: i32,
/// Maximum allowed number of decimal digits
pub max_decimal: i32,
}
impl FloatPrettyPrinter {
/// Handles printing of floating point numbers
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
/// implement 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");
}
}