1use core::fmt::{self, Display, Formatter};
23
24use num_traits::cast::ToPrimitive;
25
26use crate::{ByteRate, ByteSize, Frequency, Ratio, Time};
27
28const SIZES: &[(i128, &str)] = &[
30 (1_099_511_627_776, "TiB"),
31 (1_073_741_824, "GiB"),
32 (1_048_576, "MiB"),
33 (1024, "KiB"),
34 (1, "B"),
35];
36
37const TIMES: &[(i128, &str)] = &[
39 (86_400_000_000_000, "d"),
40 (3_600_000_000_000, "h"),
41 (60_000_000_000, "m"),
42 (1_000_000_000, "s"),
43 (1_000_000, "ms"),
44 (1_000, "µs"),
45 (1, "ns"),
46];
47
48const NANOS_PER_SECOND: f64 = 1e9;
50
51const MILLI: i128 = 1_000;
53
54const INTEGRAL_TOLERANCE: f64 = 1e-9;
63
64fn write_scaled(
70 f: &mut Formatter<'_>,
71 value: f64,
72 subunits_per_base: f64,
73 units: &[(i128, &str)],
74 base: &str,
75 suffix: &str,
76) -> fmt::Result {
77 if value.is_finite() {
78 let subunits = value * subunits_per_base;
79 let rounded = subunits.round();
80 let drift = (subunits - rounded).abs();
81 if drift <= subunits.abs().max(1.0) * INTEGRAL_TOLERANCE
86 && let Some(count) = rounded.to_i128()
87 && count.checked_mul(MILLI).is_some()
88 {
89 return write_units(f, count, units, base, suffix);
90 }
91 }
92 write!(f, "{value}{base}{suffix}")
93}
94
95fn write_units(
98 f: &mut Formatter<'_>,
99 count: i128,
100 units: &[(i128, &str)],
101 base: &str,
102 suffix: &str,
103) -> fmt::Result {
104 if count == 0 {
105 return write!(f, "0{base}{suffix}");
106 }
107 let scaled = count * MILLI;
108 let picked = units
109 .iter()
110 .copied()
111 .find(|&(scale, _)| count.abs() >= scale && scaled % scale == 0);
112 let (thousandths, unit) = picked.map_or((scaled, base), |(scale, unit)| (scaled / scale, unit));
113 write_thousandths(f, thousandths)?;
114 write!(f, "{unit}{suffix}")
115}
116
117fn write_thousandths(f: &mut Formatter<'_>, thousandths: i128) -> fmt::Result {
119 if thousandths < 0 {
120 f.write_str("-")?;
121 }
122 let magnitude = thousandths.unsigned_abs();
123 write!(f, "{}", magnitude / 1_000)?;
124 let fraction = magnitude % 1_000;
125 if fraction != 0 {
126 let rendered = format!("{fraction:03}");
127 write!(f, ".{}", rendered.trim_end_matches('0'))?;
128 }
129 Ok(())
130}
131
132fn write_trimmed(f: &mut Formatter<'_>, value: f64) -> fmt::Result {
134 let rendered = format!("{value:.9}");
135 let trimmed = rendered.trim_end_matches('0').trim_end_matches('.');
136 f.write_str(trimmed)
137}
138
139pub trait Human: Sized {
149 type Rendered: Display;
151
152 fn human(self) -> Self::Rendered;
154}
155
156#[derive(Debug, Clone, Copy)]
158pub struct HumanByteSize(ByteSize);
159
160impl Display for HumanByteSize {
161 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
162 write_scaled(f, self.0.value, 1.0, SIZES, "B", "")
163 }
164}
165
166impl Human for ByteSize {
167 type Rendered = HumanByteSize;
168
169 fn human(self) -> Self::Rendered {
170 HumanByteSize(self)
171 }
172}
173
174#[derive(Debug, Clone, Copy)]
176pub struct HumanTime(Time);
177
178impl Display for HumanTime {
179 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
180 write_scaled(f, self.0.value, NANOS_PER_SECOND, TIMES, "s", "")
181 }
182}
183
184impl Human for Time {
185 type Rendered = HumanTime;
186
187 fn human(self) -> Self::Rendered {
188 HumanTime(self)
189 }
190}
191
192#[derive(Debug, Clone, Copy)]
194pub struct HumanByteRate(ByteRate);
195
196impl Display for HumanByteRate {
197 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
198 write_scaled(f, self.0.value, 1.0, SIZES, "B", "/s")
199 }
200}
201
202impl Human for ByteRate {
203 type Rendered = HumanByteRate;
204
205 fn human(self) -> Self::Rendered {
206 HumanByteRate(self)
207 }
208}
209
210#[derive(Debug, Clone, Copy)]
212pub struct HumanFrequency(Frequency);
213
214impl Display for HumanFrequency {
215 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
216 if !self.0.value.is_finite() {
217 return write!(f, "{}/s", self.0.value);
218 }
219 write_trimmed(f, self.0.value)?;
220 f.write_str("/s")
221 }
222}
223
224impl Human for Frequency {
225 type Rendered = HumanFrequency;
226
227 fn human(self) -> Self::Rendered {
228 HumanFrequency(self)
229 }
230}
231
232#[derive(Debug, Clone, Copy)]
234pub struct HumanRatio(Ratio);
235
236impl Display for HumanRatio {
237 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
238 if !self.0.value.is_finite() {
239 return write!(f, "{}%", self.0.value);
240 }
241 write_trimmed(f, self.0.value * 100.0)?;
242 f.write_str("%")
243 }
244}
245
246impl Human for Ratio {
247 type Rendered = HumanRatio;
248
249 fn human(self) -> Self::Rendered {
250 HumanRatio(self)
251 }
252}