1use std::fmt;
6
7pub trait FloatExt {
8 type Display: fmt::Display;
9 fn to_precision(self, p: u8) -> Self::Display;
10}
11
12const MAX_FRACTION_DIGITS: u8 = 21;
13
14impl FloatExt for f64 {
15 type Display = F64Display;
16 fn to_precision(self, p: u8) -> Self::Display {
17 assert!(
18 1 <= p && p <= MAX_FRACTION_DIGITS,
19 "precision must satisfy 1 <= p ({}) <= {}",
20 p,
21 MAX_FRACTION_DIGITS
22 );
23 F64Display(self, p.into())
24 }
25}
26
27#[derive(Debug)]
29pub struct F64Display(f64, i32);
30
31impl fmt::Display for F64Display {
32 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33 let mut x = self.0;
34
35 if x.is_nan() {
36 return write!(f, "NaN");
37 }
38 if x == 0. {
39 return write!(f, "0");
40 }
41 if x < 0. {
42 x = -x;
43 write!(f, "-")?;
44 }
45 if !x.is_finite() {
46 return write!(f, "∞");
47 }
48 write!(f, "{}", to_sig_figs(self.0, self.1))
50 }
51}
52
53fn to_sig_figs(x: f64, sf: i32) -> f64 {
55 let e = ten_power_leq(x);
57 let p = e - sf + 1;
63 if p < 0 {
64 let tens = (10.0f64).powi(-p);
65 (x * tens).round() / tens
66 } else {
67 let tens = (10.0f64).powi(p);
68 (x / tens).round() * tens
69 }
70}
71
72fn ten_power_leq(x: f64) -> i32 {
74 assert!(x != 0., "power of 10 only makes sense on nonzero numbers");
75 let x = x.abs();
76 let log10 = x.log10().floor();
77 debug_assert!(i32::MIN as f64 <= log10 && log10 <= i32::MAX as f64);
78 debug_assert_eq!(log10 as i32 as f64, log10);
79 let log10 = log10 as i32;
80 if 10.0f64.powi(log10 + 1) < x {
82 log10 + 1
83 } else {
84 log10
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::FloatExt as _;
91 use std::f64;
92
93 #[test]
94 fn ten_power_leq() {
95 for (input, output) in vec![(1., 0), (-1., 0), (0.1, -1), (0.99999999999, -1)] {
96 assert_eq!(super::ten_power_leq(input,), output);
97 }
98 }
99
100 #[test]
101 fn to_sig_figs() {
102 for (x, sf, expected) in vec![
103 (1., 3, 1.),
104 (100., 3, 100.),
105 (1234., 3, 1230.),
106 (9999., 4, 9999.),
107 (9999., 3, 10_000.),
108 (9999., 1, 10_000.),
109 (0.1, 3, 0.1),
110 (0.1234, 3, 0.123),
111 ] {
112 assert_eq!(
113 super::to_sig_figs(x, sf),
114 expected,
115 "to_sig_figs({}, {}) = {}, {}",
116 x,
117 sf,
118 super::to_sig_figs(x, sf),
119 expected
120 );
121 }
122 }
123
124 #[test]
125 #[should_panic]
126 fn bad_precision() {
127 1.0f64.to_precision(0);
128 }
129
130 #[test]
131 fn it_works() {
132 for (input, sf, expected) in vec![
133 (f64::NAN, 3, "NaN"),
134 (f64::INFINITY, 3, "∞"),
135 (f64::NEG_INFINITY, 3, "-∞"),
136 (0., 3, "0"),
137 (-0., 3, "0"),
138 (0.999, 3, "0.999"),
139 (0.9999, 3, "1"),
140 (0.7000000000000002, 5, "0.7"),
141 (f64::from_bits(4603579539098121012), 4, "0.6"),
142 ] {
143 assert_eq!(input.to_precision(sf).to_string(), expected);
144 }
145 }
146}