1use crate::imp::IEC_PREFIXES as PREFIXES;
6use crate::Buffer;
7use crate::Error;
8use paste::paste;
9
10#[cfg(feature = "iec-units")]
11mod units;
12
13#[cfg(feature = "iec-units")]
14pub use self::units::*;
15
16#[cfg_attr(
22 all(feature = "derive", feature = "serde"),
23 doc = " - [`Serialize`](serde::Serialize) and [`Deserialize`](serde::Deserialize) when `serde` feature is enabled."
24)]
25#[cfg_attr(
39 feature = "derive",
40 doc = r##"```rust
41use human_units::iec::iec_unit;
42
43#[iec_unit(symbol = "nit")]
44struct Nit(pub u64);
45```"##
46)]
47#[cfg(feature = "derive")]
48pub use human_units_derive::iec_unit;
49
50pub trait IecDisplay {
52 const MAX_STRING_LEN: usize;
54
55 fn iec_display(self, symbol: &str) -> Display<'_, Self>
57 where
58 Self: Sized;
59}
60
61pub struct Display<'a, T> {
63 number: T,
64 symbol: &'a str,
65}
66
67pub trait IecFromStr {
69 fn iec_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error>
71 where
72 Self: Sized;
73}
74
75#[deprecated(
77 since = "0.5.3",
78 note = "`format_iec` method is generated by `iec_unit` macro as `const` without this trait."
79)]
80pub trait FormatIec {
81 fn format_iec(&self) -> FormattedUnit<'static>;
85}
86
87pub trait FormatIecUnit {
89 fn format_iec_unit(self, symbol: &str) -> FormattedUnit<'_>;
93}
94
95pub struct FormattedUnit<'symbol> {
97 pub(crate) prefix: &'static str,
98 pub(crate) symbol: &'symbol str,
99 pub(crate) integer: u16,
100 pub(crate) fraction: u8,
101}
102
103impl<'symbol> FormattedUnit<'symbol> {
104 pub const fn new(
106 prefix: &'static str,
107 symbol: &'symbol str,
108 integer: u16,
109 fraction: u8,
110 ) -> Self {
111 Self {
112 prefix,
113 symbol,
114 integer,
115 fraction,
116 }
117 }
118
119 pub const fn prefix(&self) -> &'static str {
121 self.prefix
122 }
123
124 pub const fn symbol(&self) -> &'symbol str {
126 self.symbol
127 }
128
129 pub const fn integer(&self) -> u16 {
131 self.integer
132 }
133
134 pub const fn fraction(&self) -> u8 {
136 self.fraction
137 }
138}
139
140impl core::fmt::Display for FormattedUnit<'_> {
141 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
142 let mut buf = Buffer::<MAX_LEN>::new();
143 buf.write_u16(self.integer);
144 if self.fraction != 0 {
145 buf.write_byte(b'.');
146 buf.write_byte(b'0' + self.fraction);
147 }
148 buf.write_byte(b' ');
149 buf.write_str_infallible(self.prefix);
150 buf.write_str_infallible(self.symbol);
151 f.write_str(unsafe { buf.as_str() })
152 }
153}
154
155const MAX_LEN: usize = 64;
156
157#[rustfmt::skip]
158macro_rules! max_string_len {
159 (u128) => {39};
160 (u64) => {20};
161 (u32) => {10};
162 (u16) => {5};
163}
164
165macro_rules! parameterize {
166 ($((
167 $uint: ident
168 $max_prefix: ident
169 ($max_prefix_integer: expr)
170 ($max_integer: expr)
171 ($($ilog: expr)+)
172 ))+) => {
173 paste! {
174 $(
175 impl<const N: usize> Buffer<N> {
176 #[doc(hidden)]
177 pub fn [<write_unit_ $uint _1024>]<const MIN: usize, const MAX: usize>(
178 &mut self,
179 value: $uint,
180 symbol: &str,
181 ) {
182 let (value, i) = $crate::imp::[<unitify_ $uint _1024>]::<MIN, MAX>(value);
183 self.[<write_ $uint>](value);
184 self.write_byte(b' ');
185 self.write_str_infallible(PREFIXES[i]);
186 self.write_str_infallible(symbol);
187 }
188 }
189
190 impl IecDisplay for $uint {
191 const MAX_STRING_LEN: usize = 64;
192
193 fn iec_display(self, symbol: &str) -> Display<'_, Self> {
194 Display { number: self, symbol }
195 }
196 }
197
198 impl core::fmt::Display for Display<'_, $uint> {
199 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
200 debug_assert!(
201 self.symbol.len() <= <$uint as IecDisplay>::MAX_STRING_LEN - max_string_len!($uint),
202 "The symbol is too long: {} > {}",
203 self.symbol.len(),
204 <$uint as IecDisplay>::MAX_STRING_LEN - max_string_len!($uint),
205 );
206 let mut buffer: Buffer<{ <$uint as IecDisplay>::MAX_STRING_LEN }> = Buffer::new();
207 buffer.[<write_unit_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(self.number, self.symbol);
208 f.write_str(unsafe { buffer.as_str() })
209 }
210 }
211
212 impl IecFromStr for $uint {
213 fn iec_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error> {
214 $crate::imp::[<$uint _unit_from_str>]::<1024>(
215 string,
216 symbol,
217 &PREFIXES[..=Prefix::$max_prefix as usize]
218 )
219 }
220 }
221
222 impl FormatIecUnit for $uint {
223 fn format_iec_unit(self, symbol: &str) -> FormattedUnit<'_> {
224 #![allow(clippy::int_plus_one)]
225 $(
226 {
227 const SCALE: $uint = (1024 as $uint).pow($ilog);
228 if self >= SCALE {
229 let integer = self / SCALE;
230 let mut fraction = self % SCALE;
231 if fraction != 0 {
232 fraction = match fraction.checked_mul(5) {
236 Some(numerator) => numerator / (SCALE / 2),
237 None => {
238 debug_assert_eq!(0, SCALE % 16);
239 (fraction / 8) * 5 / (SCALE / 16)
240 }
241 };
242 }
243 debug_assert!(integer <= $max_integer, "integer = {integer}");
244 debug_assert!(fraction <= 9, "fraction = {fraction}");
245 return FormattedUnit {
246 integer: integer as u16,
247 fraction: fraction as u8,
248 prefix: PREFIXES[$ilog],
249 symbol,
250 };
251 }
252 }
253 )+
254 let integer = self;
255 debug_assert!(integer <= $max_integer, "integer = {integer}");
256 FormattedUnit {
257 integer: integer as u16,
258 fraction: 0,
259 prefix: PREFIXES[0],
260 symbol,
261 }
262 }
263 }
264 )+
265
266 #[cfg(test)]
267 mod unitify_tests {
268 use super::*;
269
270 use arbtest::arbtest;
271 use alloc::format;
272 use alloc::string::String;
273 use alloc::string::ToString;
274
275 extern crate alloc;
276
277 $(
278 #[test]
279 fn [<test_unitify_ $uint>]() {
280 arbtest(|u| {
281 let number: $uint = u.arbitrary()?;
282 let (x, prefix) = $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(number);
283 let p = prefix as u32 - Prefix::None as u32;
284 let multiplier = (1024 as $uint).pow(p);
285 assert_eq!(number, x * multiplier, "x = {x}, multiplier = {multiplier}");
286 Ok(())
287 });
288 }
289
290 #[test]
291 fn [<test_max_string_len_ $uint>]() {
292 let string = format!("{}", $uint::MAX);
293 assert_eq!(max_string_len!($uint), string.len());
294 }
295
296 #[test]
297 fn [<test_buffer_io_ $uint>]() {
298 arbtest(|u| {
299 let number: $uint = u.arbitrary()?;
300 let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
301 let mut buffer = Buffer::<MAX_LEN>::new();
302 buffer.[<write_unit_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(number, &symbol);
303 let actual = $uint::iec_unit_from_str(unsafe { buffer.as_str() }, &symbol)
304 .unwrap_or_else(|_| panic!("String = {:?}, number = {number}, symbol = {symbol:?}", unsafe { buffer.as_str() }));
305 assert_eq!(number, actual);
306 Ok(())
307 });
308 }
309
310 #[test]
311 fn [<test_string_io_ $uint>]() {
312 arbtest(|u| {
313 let number: $uint = u.arbitrary()?;
314 let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
315 let string = format!("{}", number.iec_display(&symbol));
316 let actual = $uint::iec_unit_from_str(&string, &symbol).unwrap();
317 assert_eq!(number, actual);
318 Ok(())
319 });
320 }
321
322 #[test]
323 fn [<check_prefix_ $uint>]() {
324 const MAX_POW_OF_1024: $uint = (1024 as $uint).pow($uint::MAX.ilog(1024));
325 assert_eq!(None, MAX_POW_OF_1024.checked_mul(1024));
326 assert_eq!(
327 (1, Prefix::Kibi as usize),
328 $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(1024)
329 );
330 assert_eq!(
331 ($max_prefix_integer, Prefix::$max_prefix as usize),
332 $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(MAX_POW_OF_1024),
333 "MAX_POW_OF_1024 = {MAX_POW_OF_1024}"
334 );
335 }
336
337 #[test]
338 fn [<test_format_unit_ $uint>]() {
339 arbtest(|u| {
340 let exact: $uint = u.arbitrary()?;
341 let FormattedUnit { integer, fraction, prefix, .. } = exact.format_iec_unit("");
342 let i = PREFIXES.iter().position(|p| p == &prefix).unwrap();
343 let factor = (1024 as $uint).pow(i as u32);
344 let inexact = (integer as $uint) * factor + (fraction as $uint) * (factor / 10);
345 assert!(
346 exact >= inexact && (exact - inexact) < factor.saturating_mul($max_integer),
347 "Exact = {exact},\ninexact = {inexact},\nexact - inexact = {}, factor = {factor},\ninteger = {integer}, fraction = {fraction}, prefix = {prefix:?}",
348 exact - inexact,
349 );
350 Ok(())
351 });
352 }
353 )+
354 }
355 }
356 };
357}
358
359parameterize! {
360 (u128 Quebi (1024*1024) (1024*1024*1024 - 1) (10 9 8 7 6 5 4 3 2 1))
361 (u64 Exbi (1) (1023) (6 5 4 3 2 1))
362 (u32 Gibi (1) (1023) (3 2 1))
363 (u16 Kibi (1) (1023) (1))
364}
365
366#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
368#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
369#[repr(u8)]
370pub enum Prefix {
371 #[default]
373 None = 0,
374 Kibi = 1,
376 Mebi = 2,
378 Gibi = 3,
380 Tebi = 4,
382 Pebi = 5,
384 Exbi = 6,
386 Zebi = 7,
388 Yobi = 8,
390 Robi = 9,
392 Quebi = 10,
394}
395
396impl Prefix {
397 pub const fn as_str(self) -> &'static str {
399 crate::imp::IEC_PREFIXES[self as usize]
400 }
401
402 pub const ALL: [Self; 11] = {
404 use Prefix::*;
405 [
406 None, Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi, Robi, Quebi,
407 ]
408 };
409}
410
411impl core::fmt::Display for Prefix {
412 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
413 f.write_str(self.as_str())
414 }
415}
416
417#[cfg(all(test, feature = "std"))]
418mod tests {
419 use super::*;
420
421 use alloc::format;
422 use arbitrary::Arbitrary;
423 use arbitrary::Unstructured;
424 use arbtest::arbtest;
425
426 extern crate alloc;
427
428 #[test]
429 fn test_io() {
430 arbtest(|u| {
431 let expected: FormattedUnit = u.arbitrary()?;
432 let string = expected.to_string();
433 let mut words = string.splitn(2, ' ');
434 let number_str = words.next().unwrap();
435 let unit = words.next().unwrap().to_string();
436 let mut words = number_str.splitn(2, '.');
437 let integer: u16 = words.next().unwrap().parse().unwrap();
438 let fraction: u8 = match words.next() {
439 Some(word) => word.parse().unwrap(),
440 None => 0,
441 };
442 assert_eq!(expected.integer, integer, "string = {string:?}");
443 assert_eq!(expected.fraction, fraction);
444 assert_eq!(
445 format!("{}{}", expected.prefix, expected.symbol),
446 unit,
447 "expected = `{}`",
448 expected
449 );
450 Ok(())
451 });
452 }
453
454 impl<'a> Arbitrary<'a> for FormattedUnit<'static> {
455 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
456 Ok(Self {
457 prefix: *u.choose(&PREFIXES[..])?,
458 symbol: "",
459 integer: u.int_in_range(0..=999)?,
460 fraction: u.int_in_range(0..=9)?,
461 })
462 }
463 }
464}