float_pretty_print/
lib.rs1#![deny(missing_docs)]
2#![forbid(unsafe_code)]
3
4use std::fmt::{Display, Formatter, Result};
38
39const DEBUG : bool = false;
40
41pub struct PrettyPrintFloat(pub f64);
49
50#[derive(PartialEq, Eq, Debug)]
51enum NumberClass {
52 Big,
53 Medium,
54 Small,
55 Zero,
56 Special,
57 Unprintable,
58}
59
60impl PrettyPrintFloat {
61 fn cls(&self) -> NumberClass {
62 let mut x = self.0;
63 if !x.is_finite() {
64 return NumberClass::Special;
65 }
66 if x < 0.0 {
67 x = -x;
68 }
69 if x == 0.0 {
70 return NumberClass::Zero;
71 }
72 if x > 99999.0 {
73 return NumberClass::Big;
74 }
75 if x < 0.001 {
76 return NumberClass::Small;
77 }
78 return NumberClass::Medium;
79 }
80}
81
82impl Display for PrettyPrintFloat {
83 fn fmt(&self, fmt: &mut Formatter) -> Result {
84 let mut width_min = fmt.width().unwrap_or(3);
85 let mut width_max = fmt.precision().unwrap_or(12);
86 let x = self.0;
87
88 if width_min == 0 {
89 width_min = 1;
90 }
91
92 if width_max == 0 {
93 return Ok(())
94 }
95
96 if width_min > width_max {
97 width_max = width_min;
98 }
99
100 use NumberClass::*;
101 let mut c = self.cls();
102
103 if DEBUG { eprintln!("Number {} classified as {:?}", x, c); }
104
105 if c == Special {
106 let q = format!("{}", x);
107 return if q.len() <= width_max {
108 write!(fmt, "{:w$}", q, w=width_min)
109 } else {
110 write!(fmt, "{:.p$}", "########", p=width_max)
111 }
112 }
113 if c == Zero {
114 return if width_max < 3 || width_min < 3 {
115 write!(fmt, "{:w$}", "0", w=width_min)
116 } else {
117 write!(fmt, "{:.p$}", 0.0, p=(width_min-2))
118 };
119 }
120
121 let probe_for_medium_mode;
122 if c == Medium {
123 probe_for_medium_mode = format!("{:.0}", x);
124 let length_of_integer_part = probe_for_medium_mode.len();
125
126 if DEBUG { eprintln!(
127 "Probe=`{}`; length of integer part is {}, which width_max is {}",
128 probe_for_medium_mode,
129 length_of_integer_part,
130 width_max,
131 ); }
132
133 match length_of_integer_part {
134 l if l > width_max => {
135 if DEBUG { eprintln!("Too large, switching to Big"); }
136 c = Big;
137 },
138 l if l + 1 >= width_max => {
139 if DEBUG { eprintln!("Almost too large, checking zeroness"); }
140 if probe_for_medium_mode != "0" {
141 if DEBUG { eprintln!("Seems to be OK to print as integer"); }
142 return write!(fmt, "{:w$.0}", x, w=width_min);
144 } else {
145 if DEBUG { eprintln!("Refusing to print it"); }
146 c = Unprintable;
147 }
148 },
149 _ => {
150 if DEBUG { eprintln!("Enough room to consider fractional part"); }
153
154 let probe = format!(
155 "{:.p$}",
156 x,
157 p=(width_max - 1 - length_of_integer_part),
158 );
159
160 let mut num_zeroes = 0;
161 let mut num_digits = 0;
162 let mut significant_zeroes = false;
163
164 for c in probe.chars() {
165 match c {
166 '0' => {
167 num_digits += 1;
168 if ! significant_zeroes {
169 num_zeroes += 1;
170 }
171 },
172 '.' => {
173
174 }
175 '-' => {
176
177 },
178 _ => {
179 num_digits += 1;
180 significant_zeroes = true;
181 },
182 }
183 }
184 if DEBUG { eprintln!(
185 "{} zero of {} digits in the test print",
186 num_zeroes,
187 num_digits,
188 ); }
189
190 assert!(num_digits > 0);
191
192 if (num_zeroes * 100 / num_digits) > 80 {
193 if DEBUG { eprintln!("Too small to print normally, switching to Small"); }
194 c = Small;
196 }
197 },
198 }
199
200 if c == Medium {
201 if DEBUG { eprintln!("Medium mode confirmed"); }
202 let mut b = format!("{:.p$}", x, p=(width_max-1-length_of_integer_part));
204 if DEBUG { eprintln!("Intermediate result: {}", &b); }
205 let first_digit_of_probe = probe_for_medium_mode.bytes().into_iter().next();
206 if first_digit_of_probe == Some(b'1') && first_digit_of_probe != b.bytes().into_iter().next() {
207 if DEBUG { eprintln!("Looks like we have overestimated the integer part size"); }
208
209 let b2 = format!("{:.p$}", x, p=(width_max-1-length_of_integer_part+1));
210 if b2.len() <= width_max {
211 b = b2;
212 }
213 }
214 let mut end = b.len();
215 if b.contains('.') {
216 loop {
217 if end <= width_min { break }
218 if end < 3 { break }
219 if !b[0..end].ends_with('0') { break }
220 if b[0..(end-1)].ends_with('.') {
221 break
223 }
224 if DEBUG { eprintln!("Chipped away some zero"); }
225 end -= 1;
226 }
227 }
228 let b = &b[0..end];
229 for _ in b.len()..width_min {
230 write!(fmt, " ")?;
231 }
232 return write!(fmt, "{}", b);
233 }
234 }
235
236 match c {
237 Zero | Special | Medium => unreachable!(),
238 Big | Small => {
239 let probe = format!("{:.0e}", x);
240 if DEBUG { eprintln!("First probe: {}", &probe); }
241 let mut minimum = probe.len();
242 if minimum > width_max {
243 if DEBUG { eprintln!("Can't fit it"); }
244 if c == Big {
245 c = Unprintable;
246 } else {
247 if DEBUG { eprintln!("Just print zero"); }
248 return write!(fmt, "{:w$}", 0.0, w=width_min);
249 }
250 } else if minimum == width_max {
251 if DEBUG { eprintln!("Fits just right"); }
252 return write!(fmt, "{}", probe);
253 } else if minimum == width_max-1 {
254 if DEBUG { eprintln!("Fits almost just right"); }
255 return write!(fmt, " {}", probe);
257 } else {
258 if DEBUG { eprintln!("There is some space to be more precise"); }
259 let probe2 = format!("{:.p$e}", x, p=(width_max - minimum - 1) );
260 if DEBUG { eprintln!("Second probe: {}", &probe2); }
261 if probe2.len() > width_max {
262 minimum += probe2.len() - width_max;
263 }
264 let mut zeroes_before_e = 0;
265 let mut zeroes_in_a_row = 0;
266 for c in probe2.chars() { match c {
267 '0' => zeroes_in_a_row += 1,
268 'e' | 'E' => {
269 zeroes_before_e = zeroes_in_a_row;
270 },
271 _ => zeroes_in_a_row = 0,
272 } }
273 if DEBUG { eprintln!("{} zeroes before E", zeroes_before_e); }
274 let zeroes_to_chip_away = zeroes_before_e.min(width_max-width_min);
275 if DEBUG { eprintln!("{} zeroes to be removed", zeroes_to_chip_away); }
276 return write!(fmt, "{:.p$e}", x, p=(width_max - minimum - 1 - zeroes_to_chip_away) );
277 }
278 },
279 Unprintable => (),
280 }
281 let _ = c;
282
283 write!(fmt, "{:.p$}", "##################################", p=width_min)
284 }
285}
286
287#[cfg(test)]
288mod test;