Skip to main content

bsize/
display.rs

1// Copyright 2026 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use core::fmt;
16
17use crate::BSize;
18
19/// Display wrapper for [`BSize`].
20///
21/// Supports various styles, see methods. By default, the [`binary`] style is used.
22///
23/// [`binary`]: Display::binary
24///
25/// # Examples
26///
27/// ```
28/// # use bsize::BSize;
29/// assert_eq!(
30///     "1.0 MiB",
31///     BSize::<u64>::mib(1).display().binary().to_string(),
32/// );
33///
34/// assert_eq!(
35///     "42.0 kB",
36///     BSize::<u64>::kb(42).display().decimal().to_string(),
37/// );
38/// ```
39#[derive(Debug, Clone)]
40pub struct Display {
41    size: u64,
42    mode: DisplayMode,
43}
44
45#[derive(Debug, Clone)]
46enum DisplayMode {
47    Binary,
48    Decimal,
49}
50
51impl Display {
52    /// Format using binary units (e.g., `11.8MiB`)
53    pub fn binary(mut self) -> Self {
54        self.mode = DisplayMode::Binary;
55        self
56    }
57
58    /// Format using decimal units (e.g., `11.8MB`)
59    pub fn decimal(mut self) -> Self {
60        self.mode = DisplayMode::Decimal;
61        self
62    }
63
64    fn new(size: u64) -> Self {
65        Self {
66            size,
67            mode: DisplayMode::Binary,
68        }
69    }
70}
71
72impl fmt::Display for Display {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        let bytes = self.size;
75
76        let unit = match self.mode {
77            DisplayMode::Binary => 1024,
78            DisplayMode::Decimal => 1000,
79        };
80
81        let unit_prefixes = match self.mode {
82            DisplayMode::Binary => b"KMGTPE",
83            DisplayMode::Decimal => b"kMGTPE",
84        };
85        let unit_suffix = match self.mode {
86            DisplayMode::Binary => "iB",
87            DisplayMode::Decimal => "B",
88        };
89        let unit_separator = " ";
90        let precision = f.precision().unwrap_or(1);
91
92        if bytes < unit {
93            write!(f, "{bytes}{unit_separator}B")?;
94        } else {
95            let size = bytes as f64;
96
97            let mut ideal_prefix = 0usize;
98            let mut ideal_size = size;
99            loop {
100                ideal_prefix += 1;
101                ideal_size /= unit as f64;
102
103                if ideal_size < unit as f64 {
104                    break;
105                }
106            }
107            let exp = ideal_prefix;
108
109            let unit_prefix = unit_prefixes[exp - 1] as char;
110
111            write!(
112                f,
113                "{:.precision$}{unit_separator}{unit_prefix}{unit_suffix}",
114                size / unit.pow(exp as u32) as f64,
115            )?;
116        }
117
118        Ok(())
119    }
120}
121
122macro_rules! impl_display {
123    ($($ty:ty),* $(,)?) => {
124        $(
125            impl BSize<$ty> {
126                /// Returns a display wrapper.
127                pub fn display(self) -> Display {
128                    Display::new(self.0 as u64)
129                }
130            }
131        )*
132    };
133}
134
135impl_display!(u8, u16, u32, u64, usize);