1use paste::paste;
2
3use crate::imp::SI_PREFIXES as PREFIXES;
4use crate::Buffer;
5use crate::Error;
6
7pub trait SiFromStr {
9 fn si_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error>
11 where
12 Self: Sized;
13}
14
15pub trait SiDisplay {
17 const MAX_STRING_LEN: usize;
19
20 fn si_display(self, symbol: &str) -> Display<'_, Self>
22 where
23 Self: Sized;
24}
25
26pub struct Display<'a, T> {
28 number: T,
29 symbol: &'a str,
30}
31
32#[deprecated(
34 since = "0.5.3",
35 note = "`format_si` method is generated by `si_unit` macro as `const` without this trait."
36)]
37pub trait FormatSi {
38 fn format_si(&self) -> FormattedUnit<'static>;
42}
43
44pub trait FormatSiUnit {
46 fn format_si_unit(self, symbol: &str) -> FormattedUnit<'_>;
50}
51
52pub struct FormattedUnit<'symbol> {
54 prefix: &'static str,
55 symbol: &'symbol str,
56 integer: u16,
57 fraction: u8,
58}
59
60impl<'symbol> FormattedUnit<'symbol> {
61 pub const fn new(
63 prefix: &'static str,
64 symbol: &'symbol str,
65 integer: u16,
66 fraction: u8,
67 ) -> Self {
68 Self {
69 prefix,
70 symbol,
71 integer,
72 fraction,
73 }
74 }
75
76 pub const fn prefix(&self) -> &'static str {
78 self.prefix
79 }
80
81 pub const fn symbol(&self) -> &'symbol str {
83 self.symbol
84 }
85
86 pub const fn integer(&self) -> u16 {
88 self.integer
89 }
90
91 pub const fn fraction(&self) -> u8 {
93 self.fraction
94 }
95}
96
97impl core::fmt::Display for FormattedUnit<'_> {
98 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
99 let mut buf = Buffer::<MAX_LEN>::new();
100 buf.write_u16(self.integer);
101 if self.fraction != 0 {
102 buf.write_byte(b'.');
103 buf.write_byte(b'0' + self.fraction);
104 }
105 buf.write_byte(b' ');
106 buf.write_str_infallible(self.prefix);
107 buf.write_str_infallible(self.symbol);
108 f.write_str(unsafe { buf.as_str() })
109 }
110}
111
112const MAX_LEN: usize = 64;
113
114#[rustfmt::skip]
115macro_rules! max_string_len {
116 (u128) => {39};
117 (u64) => {20};
118 (u32) => {10};
119 (u16) => {5};
120}
121
122macro_rules! parameterize {
123 ($(($uint: ident
124 $min_prefix: ident
125 $max_prefix: ident
126 ($($ilog: expr)+)))+) => {
127 paste! {
128 $(
129 impl<const N: usize> Buffer<N> {
130 #[doc(hidden)]
131 pub fn [<write_unit_ $uint _1000>]<const MIN: usize, const MAX: usize>(
132 &mut self,
133 value: $uint,
134 symbol: &str,
135 ) {
136 let (value, i) = $crate::imp::[<unitify_ $uint _1000>]::<MIN, MAX>(value);
137 self.[<write_ $uint>](value);
138 self.write_byte(b' ');
139 self.write_str_infallible(PREFIXES[i]);
140 self.write_str_infallible(symbol);
141 }
142 }
143
144 impl SiDisplay for $uint {
145 const MAX_STRING_LEN: usize = 64;
146
147 fn si_display(self, symbol: &str) -> Display<'_, Self> {
148 Display { number: self, symbol }
149 }
150 }
151
152 impl core::fmt::Display for Display<'_, $uint> {
153 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
154 debug_assert!(
155 self.symbol.len() <= <$uint as SiDisplay>::MAX_STRING_LEN - max_string_len!($uint),
156 "The symbol is too long: {} > {}",
157 self.symbol.len(),
158 <$uint as SiDisplay>::MAX_STRING_LEN - max_string_len!($uint),
159 );
160 let mut buffer: Buffer<{ <$uint as SiDisplay>::MAX_STRING_LEN }> = Buffer::new();
161 buffer.[<write_unit_ $uint _1000>]::<
162 { Prefix::$min_prefix as usize},
163 { Prefix::$max_prefix as usize}
164 >(self.number, self.symbol);
165 f.write_str(unsafe { buffer.as_str() })
166 }
167 }
168
169 impl SiFromStr for $uint {
170 fn si_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error> {
171 $crate::imp::[<$uint _unit_from_str>]::<{ 1000 as $uint }>(
172 string,
173 symbol,
174 &PREFIXES[Prefix::$min_prefix as usize..=Prefix::$max_prefix as usize]
175 )
176 }
177 }
178
179 impl FormatSiUnit for $uint {
180 fn format_si_unit(self, symbol: &str) -> FormattedUnit<'_> {
181 $(
182 {
183 const SCALE: $uint = (1000 as $uint).pow($ilog);
184 if self >= SCALE {
185 let integer = self / SCALE;
186 let mut fraction = self % SCALE;
187 if fraction != 0 {
188 fraction /= (SCALE / 10);
190 }
191 debug_assert!(integer <= 999, "integer = {integer}");
192 debug_assert!(fraction <= 9, "fraction = {fraction}");
193 return FormattedUnit {
194 integer: integer as u16,
195 fraction: fraction as u8,
196 prefix: PREFIXES[Prefix::$min_prefix as usize + $ilog],
197 symbol,
198 };
199 }
200 }
201 )+
202 let integer = self;
203 debug_assert!(integer <= 999, "integer = {integer}");
204 FormattedUnit {
205 integer: integer as u16,
206 fraction: 0,
207 prefix: PREFIXES[Prefix::$min_prefix as usize],
208 symbol,
209 }
210 }
211 }
212 )+
213
214 #[cfg(test)]
215 mod unitify_tests {
216 use super::*;
217
218 use arbtest::arbtest;
219 use alloc::format;
220 use alloc::string::String;
221 use alloc::string::ToString;
222
223 extern crate alloc;
224
225
226 $(
227 #[test]
228 fn [<test_unitify_ $uint>]() {
229 arbtest(|u| {
230 let number: $uint = u.arbitrary()?;
231 let (x, prefix) = $crate::imp::[<unitify_ $uint _1000>]::<
232 { Prefix::$min_prefix as usize},
233 { Prefix::$max_prefix as usize}
234 >(number);
235 let p = prefix as u32 - Prefix::$min_prefix as u32;
236 assert_eq!(number, x * (1000 as $uint).pow(p));
237 Ok(())
238 });
239 }
240
241 #[test]
242 fn [<test_max_string_len_ $uint>]() {
243 let string = format!("{}", $uint::MAX);
244 assert_eq!(max_string_len!($uint), string.len());
245 }
246
247 #[test]
248 fn [<test_buffer_io_ $uint>]() {
249 arbtest(|u| {
250 let number: $uint = u.arbitrary()?;
251 let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
252 let mut buffer = Buffer::<MAX_LEN>::new();
253 buffer.[<write_unit_ $uint _1000>]::<
254 { Prefix::$min_prefix as usize},
255 { Prefix::$max_prefix as usize}
256 >(number, &symbol);
257 let actual = $uint::si_unit_from_str(unsafe { buffer.as_str() }, &symbol).unwrap();
258 assert_eq!(number, actual);
259 Ok(())
260 });
261 }
262
263 #[test]
264 fn [<test_string_io_ $uint>]() {
265 arbtest(|u| {
266 let number: $uint = u.arbitrary()?;
267 let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
268 let string = format!("{}", number.si_display(&symbol));
269 let actual = $uint::si_unit_from_str(&string, &symbol).unwrap();
270 assert_eq!(number, actual);
271 Ok(())
272 });
273 }
274
275 #[test]
276 fn [<check_max_prefix_ $uint>]() {
277 const MAX_POW_OF_1000: $uint = (1000 as $uint).pow($uint::MAX.ilog(1000));
278 assert_eq!(None, MAX_POW_OF_1000.checked_mul(1000));
279 assert_eq!(
280 (1, Prefix::Micro as usize),
281 $crate::imp::[<unitify_ $uint _1000>]::<
282 { Prefix::$min_prefix as usize},
283 { Prefix::$max_prefix as usize}
284 >(1000)
285 );
286 assert_eq!(
287 (1, Prefix::$max_prefix as usize),
288 $crate::imp::[<unitify_ $uint _1000>]::<
289 { Prefix::$min_prefix as usize},
290 { Prefix::$max_prefix as usize}
291 >(MAX_POW_OF_1000)
292 );
293 }
294
295 #[test]
296 fn [<test_format_unit_ $uint>]() {
297 arbtest(|u| {
298 let exact: $uint = u.arbitrary()?;
299 let FormattedUnit { integer, fraction, prefix, .. } = exact.format_si_unit("");
300 let i = PREFIXES.iter().position(|p| p == &prefix).unwrap() - Prefix::$min_prefix as usize;
301 let factor = (1000 as $uint).pow(i as u32);
302 let inexact = (integer as $uint) * factor + (fraction as $uint) * (factor / 10);
303 assert!(
304 exact >= inexact && (exact - inexact) < factor,
305 "Exact = {exact}, inexact = {inexact}",
306 );
307 Ok(())
308 });
309 }
310 )+
311 }
312 }
313 };
314}
315
316parameterize! {
317 (u128 Nano Ronna (12 11 10 9 8 7 6 5 4 3 2 1))
318 (u64 Nano Giga (6 5 4 3 2 1))
319 (u32 Nano None (3 2 1))
320 (u16 Nano Micro (1))
321}
322
323#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
325#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
326#[repr(u8)]
327pub enum Prefix {
328 Quecto = 0,
330 Ronto = 1,
332 Yocto = 2,
334 Zepto = 3,
336 Atto = 4,
338 Femto = 5,
340 Pico = 6,
342 Nano = 7,
344 Micro = 8,
346 Milli = 9,
348 #[default]
350 None = 10,
351 Kilo = 11,
353 Mega = 12,
355 Giga = 13,
357 Tera = 14,
359 Peta = 15,
361 Exa = 16,
363 Zetta = 17,
365 Yotta = 18,
367 Ronna = 19,
369 Quetta = 20,
371}
372
373impl Prefix {
374 pub const fn as_str(self) -> &'static str {
376 crate::imp::SI_PREFIXES[self as usize]
377 }
378
379 pub const ALL: [Self; 21] = {
381 use Prefix::*;
382 [
383 Quecto, Ronto, Yocto, Zepto, Atto, Femto, Pico, Nano, Micro, Milli, None, Kilo, Mega,
384 Giga, Tera, Peta, Exa, Zetta, Yotta, Ronna, Quetta,
385 ]
386 };
387}
388
389impl core::fmt::Display for Prefix {
390 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
391 f.write_str(self.as_str())
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 #[test]
398 fn test_min_prefix_len() {
399 assert_ne!(0, u128::MAX % 1000);
400 assert_ne!(0, u64::MAX % 1000);
401 assert_ne!(0, u32::MAX % 1000);
402 assert_ne!(0, u16::MAX % 1000);
403 }
404}