Skip to main content

devela/phys/unit/
bi.rs

1// devela::phys::unit::bi
2//
3//! Binary unit prefixes.
4//
5
6use super::_helper::__phys_unit_impl_try_from;
7#[allow(unused_imports)]
8use crate::FloatExt;
9#[cfg(feature = "alloc")]
10#[allow(unused_imports)]
11use crate::{Vec, vec_ as vec};
12
13#[doc = crate::_tags!(num)]
14/// Binary unit prefixes.
15#[doc = crate::_doc_meta!{location("phys/unit")}]
16///
17/// - <https://en.wikipedia.org/wiki/Binary_prefix>
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum UnitBi {
21    /// 2^80.
22    Yobi = 80,
23    /// 2^70.
24    Zebi = 70,
25    /// 2^60.
26    Exbi = 60,
27    /// 2^50.
28    Pebi = 50,
29    /// 2^40.
30    Tebi = 40,
31    /// 2^30.
32    Gibi = 30,
33    /// 2^20.
34    Mebi = 20,
35    /// 2^10.
36    Kibi = 10,
37    /// 2^0 (no prefix).
38    #[default]
39    None = 0,
40}
41
42/// # Aliases.
43#[allow(non_upper_case_globals)]
44impl UnitBi {
45    /// Alias of `Yobi`.
46    pub const Yi: Self = Self::Yobi;
47    /// Alias of `Yobi`.
48    pub const Y: Self = Self::Yobi;
49    /// Alias of `Zebi`.
50    pub const Zi: Self = Self::Zebi;
51    /// Alias of `Zebi`.
52    pub const Z: Self = Self::Zebi;
53    /// Alias of `Exbi`.
54    pub const Ei: Self = Self::Exbi;
55    /// Alias of `Exbi`.
56    pub const E: Self = Self::Exbi;
57    /// Alias of `Pebi`.
58    pub const Pi: Self = Self::Pebi;
59    /// Alias of `Pebi`.
60    pub const P: Self = Self::Pebi;
61    /// Alias of `Tebi`.
62    pub const Ti: Self = Self::Tebi;
63    /// Alias of `Tebi`.
64    pub const T: Self = Self::Tebi;
65    /// Alias of `Gibi`.
66    pub const Gi: Self = Self::Gibi;
67    /// Alias of `Gibi`.
68    pub const G: Self = Self::Gibi;
69    /// Alias of `Mebi`.
70    pub const Mi: Self = Self::Mebi;
71    /// Alias of `Mebi`.
72    pub const M: Self = Self::Mebi;
73    /// Alias of `Kibi`.
74    pub const Ki: Self = Self::Kibi;
75    /// Alias of `Kibi`.
76    pub const k: Self = Self::Kibi;
77    /// Alias of `Kibi`.
78    pub const K: Self = Self::Kibi;
79}
80
81impl UnitBi {
82    /// Returns the symbol of the prefix.
83    ///
84    /// # Examples
85    /// ```
86    /// # use devela::UnitBi;
87    /// assert_eq![UnitBi::Gibi.symbol(), "Gi"];
88    /// ```
89    #[must_use]
90    pub const fn symbol(&self) -> &str {
91        match self {
92            UnitBi::Yobi => "Yi",
93            UnitBi::Zebi => "Zi",
94            UnitBi::Exbi => "Ei",
95            UnitBi::Pebi => "Pi",
96            UnitBi::Tebi => "Ti",
97            UnitBi::Gibi => "Gi",
98            UnitBi::Mebi => "Mi",
99            UnitBi::Kibi => "Ki",
100            UnitBi::None => "",
101        }
102    }
103    /// Returns the ASCII symbol of the prefix.
104    #[must_use]
105    pub const fn symbol_ascii(&self) -> &str {
106        self.symbol()
107    }
108
109    /// Returns the name of the prefix.
110    #[must_use]
111    pub const fn name(&self) -> &str {
112        match self {
113            UnitBi::Yobi => "yobi",
114            UnitBi::Zebi => "zebi",
115            UnitBi::Exbi => "exbi",
116            UnitBi::Pebi => "pebi",
117            UnitBi::Tebi => "tibi",
118            UnitBi::Gibi => "gibi",
119            UnitBi::Mebi => "mibi",
120            UnitBi::Kibi => "kibi",
121            UnitBi::None => "",
122        }
123    }
124
125    /// The base value for binary unit prefixes.
126    pub const BASE: i32 = 2;
127
128    /// Returns the exponent corresponding to the binary unit prefix.
129    ///
130    /// For example, `Mebi` corresponds to an exponent of 20, meaning
131    /// `Self::BASE^self.exp() = 1_048_576`.
132    #[must_use]
133    pub const fn exp(&self) -> i32 {
134        match self {
135            UnitBi::Yobi => 80,
136            UnitBi::Zebi => 70,
137            UnitBi::Exbi => 60,
138            UnitBi::Pebi => 50,
139            UnitBi::Tebi => 40,
140            UnitBi::Gibi => 30,
141            UnitBi::Mebi => 20,
142            UnitBi::Kibi => 10,
143            UnitBi::None => 0,
144        }
145    }
146
147    /// Returns the multiplication factor for the binary prefix as an `f64`.
148    #[must_use]
149    pub const fn factor(&self) -> f64 {
150        match self {
151            UnitBi::Yobi => 1_208_925_819_614_629_174_706_176.,
152            UnitBi::Zebi => 1_180_591_620_717_411_303_424.,
153            UnitBi::Exbi => 1_152_921_504_606_846_976.,
154            UnitBi::Pebi => 1_125_899_906_842_624.,
155            UnitBi::Tebi => 1_099_511_627_776.,
156            UnitBi::Gibi => 1_073_741_824.,
157            UnitBi::Mebi => 1_048_576.,
158            UnitBi::Kibi => 1_024.,
159            UnitBi::None => 1.,
160        }
161    }
162
163    /// Returns the multiplication factor for the binary prefix as an `i64`.
164    ///
165    /// Only supports the range up to `Exbi`, returning `None` for `Zebi` and `Yobi`.
166    #[must_use]
167    pub const fn factor_i64_checked(&self) -> Option<i64> {
168        match self {
169            UnitBi::Exbi => Some(1_152_921_504_606_846_976),
170            UnitBi::Pebi => Some(1_125_899_906_842_624),
171            UnitBi::Tebi => Some(1_099_511_627_776),
172            UnitBi::Gibi => Some(1_073_741_824),
173            UnitBi::Mebi => Some(1_048_576),
174            UnitBi::Kibi => Some(1_024),
175            UnitBi::None => Some(1),
176            _ => None,
177        }
178    }
179
180    /// Returns the multiplication factor for the binary prefix as an `i64`.
181    ///
182    /// Only supports the range up to `Exbi`, returning 0 for `Zebi` and `Yobi`.
183    #[must_use]
184    pub const fn factor_i64(&self) -> i64 {
185        match self {
186            UnitBi::Exbi => 1_152_921_504_606_846_976,
187            UnitBi::Pebi => 1_125_899_906_842_624,
188            UnitBi::Tebi => 1_099_511_627_776,
189            UnitBi::Gibi => 1_073_741_824,
190            UnitBi::Mebi => 1_048_576,
191            UnitBi::Kibi => 1_024,
192            UnitBi::None => 1,
193            _ => 0,
194        }
195    }
196
197    /// Returns the multiplication factor for the binary prefix as an `i128`.
198    pub const fn factor_i128(&self) -> i128 {
199        match self {
200            UnitBi::Yobi => 1_208_925_819_614_629_174_706_176,
201            UnitBi::Zebi => 1_180_591_620_717_411_303_424,
202            _ => self.factor_i64() as i128,
203        }
204    }
205
206    /// Converts a value from one binary prefix to another,
207    /// returning the converted value.
208    #[must_use]
209    pub fn convert(value: f64, from: Self, to: Self) -> f64 {
210        if from == to {
211            return value;
212        }
213        let (from_factor, to_factor) = (from.factor(), to.factor());
214        value * (from_factor / to_factor)
215    }
216
217    /// Converts a value from one binary prefix to another,
218    /// returning the converted value and the remainder.
219    #[must_use]
220    pub fn convert_i64(value: i64, from: Self, to: Self) -> (i64, i64) {
221        if from == to {
222            return (value, 0);
223        }
224        let (from_factor, to_factor) = (from.factor_i64(), to.factor_i64());
225        let converted = value * from_factor / to_factor;
226        let remainder = value * from_factor % to_factor;
227        (converted, remainder)
228    }
229
230    /// Converts a value from one binary prefix to another,
231    /// returning the converted value and the remainder.
232    #[must_use]
233    pub fn convert_i128(value: i128, from: Self, to: Self) -> (i128, i128) {
234        if from == to {
235            return (value, 0);
236        }
237        let (from_factor, to_factor) = (from.factor_i128(), to.factor_i128());
238        let converted = value * from_factor / to_factor;
239        let remainder = value * from_factor % to_factor;
240        (converted, remainder)
241    }
242
243    /// Reduces the given `value` to the most appropriate binary prefix as an `f64`,
244    /// returning a tuple of the reduced size and the prefix.
245    ///
246    /// The input `value` is assumed to be non-negative, and in base units,
247    /// meaning it has no prefix applied.
248    ///
249    /// This method simplifies large numerical values by scaling them down
250    /// to the largest appropriate binary prefix (e.g., Kibi, Mebi, Gibi, etc.).
251    #[must_use]
252    pub fn reduce(value: f64) -> (f64, Self) {
253        match value.abs() {
254            value if value >= UnitBi::Yobi.factor() => {
255                (value / UnitBi::Yobi.factor(), UnitBi::Yobi)
256            }
257            value if value >= UnitBi::Zebi.factor() => {
258                (value / UnitBi::Zebi.factor(), UnitBi::Zebi)
259            }
260            value if value >= UnitBi::Exbi.factor() => {
261                (value / UnitBi::Exbi.factor(), UnitBi::Exbi)
262            }
263            value if value >= UnitBi::Pebi.factor() => {
264                (value / UnitBi::Pebi.factor(), UnitBi::Pebi)
265            }
266            value if value >= UnitBi::Tebi.factor() => {
267                (value / UnitBi::Tebi.factor(), UnitBi::Tebi)
268            }
269            value if value >= UnitBi::Gibi.factor() => {
270                (value / UnitBi::Gibi.factor(), UnitBi::Gibi)
271            }
272            value if value >= UnitBi::Mebi.factor() => {
273                (value / UnitBi::Mebi.factor(), UnitBi::Mebi)
274            }
275            value if value >= UnitBi::Kibi.factor() => {
276                (value / UnitBi::Kibi.factor(), UnitBi::Kibi)
277            }
278            _ => (value, UnitBi::None),
279        }
280    }
281
282    /// Reduces the given value to the most appropriate binary prefix as an `i64`,
283    /// returning a tuple of the reduced size, the prefix, and the remainder.
284    ///
285    /// The input `value` is assumed to be non-negative, and in base units,
286    /// meaning it has no prefix applied.
287    ///
288    /// This method simplifies large numerical values by scaling them down
289    /// to the largest appropriate binary prefix (e.g., Kibi, Mebi, Gibi, etc.).
290    #[must_use]
291    pub const fn reduce_i64(value: i64) -> (i64, Self, i64) {
292        match value {
293            value if value >= UnitBi::Exbi.factor_i64() => {
294                (value / UnitBi::Exbi.factor_i64(), UnitBi::Exbi, value % UnitBi::Exbi.factor_i64())
295            }
296            value if value >= UnitBi::Pebi.factor_i64() => {
297                (value / UnitBi::Pebi.factor_i64(), UnitBi::Pebi, value % UnitBi::Pebi.factor_i64())
298            }
299            value if value >= UnitBi::Tebi.factor_i64() => {
300                (value / UnitBi::Tebi.factor_i64(), UnitBi::Tebi, value % UnitBi::Tebi.factor_i64())
301            }
302            value if value >= UnitBi::Gibi.factor_i64() => {
303                (value / UnitBi::Gibi.factor_i64(), UnitBi::Gibi, value % UnitBi::Gibi.factor_i64())
304            }
305            value if value >= UnitBi::Mebi.factor_i64() => {
306                (value / UnitBi::Mebi.factor_i64(), UnitBi::Mebi, value % UnitBi::Mebi.factor_i64())
307            }
308            value if value >= UnitBi::Kibi.factor_i64() => {
309                (value / UnitBi::Kibi.factor_i64(), UnitBi::Kibi, value % UnitBi::Kibi.factor_i64())
310            }
311            _ => (value, UnitBi::None, 0),
312        }
313    }
314
315    /// Reduces the given value to the most appropriate binary prefix as an `i128`,
316    /// returning a tuple of the reduced size, the prefix, and the remainder.
317    ///
318    /// The input `value` is assumed to be non-negative, and in base units,
319    /// meaning it has no prefix applied.
320    ///
321    /// This method simplifies large numerical values by scaling them down
322    /// to the largest appropriate binary prefix (e.g., Kibi, Mebi, Gibi, etc.).
323    #[must_use]
324    pub const fn reduce_i128(value: i128) -> (i128, Self, i128) {
325        match value {
326            value if value >= UnitBi::Yobi.factor_i128() => (
327                value / UnitBi::Yobi.factor_i128(),
328                UnitBi::Yobi,
329                value % UnitBi::Yobi.factor_i128(),
330            ),
331            value if value >= UnitBi::Zebi.factor_i128() => (
332                value / UnitBi::Zebi.factor_i128(),
333                UnitBi::Zebi,
334                value % UnitBi::Zebi.factor_i128(),
335            ),
336            value if value >= UnitBi::Exbi.factor_i128() => (
337                value / UnitBi::Exbi.factor_i128(),
338                UnitBi::Exbi,
339                value % UnitBi::Exbi.factor_i128(),
340            ),
341            _ => {
342                let (v, p, r) = Self::reduce_i64(value as i64);
343                (v as i128, p, r as i128)
344            }
345        }
346    }
347
348    /// Reduces the given value to a chain of appropriate binary prefixes as an `f64`,
349    /// stopping when the remainder is less than the given threshold.
350    #[must_use]
351    #[cfg(feature = "alloc")]
352    #[cfg_attr(nightly_doc, doc(cfg(feature = "alloc")))]
353    pub fn reduce_chain(value: f64, threshold: f64) -> Vec<(f64, Self)> {
354        if value == 0.0 {
355            return vec![(0.0, UnitBi::None)];
356        }
357
358        let mut result = Vec::new();
359        let mut remainder = value;
360
361        // Ensure the threshold is positive and above a small epsilon value
362        // in order to prevent infinite loops
363        let effective_threshold =
364            if threshold <= 0.0 { crate::FloatConst::MEDIUM_MARGIN } else { threshold };
365
366        while remainder.abs() > effective_threshold {
367            let (size, unit) = Self::reduce(remainder);
368            let integer_part = size.trunc();
369            let fractional_part = size - integer_part;
370            result.push((integer_part, unit));
371            remainder = fractional_part * unit.factor();
372
373            if remainder.abs() < effective_threshold {
374                break;
375            }
376        }
377        if remainder.abs() >= effective_threshold {
378            result.push((remainder, UnitBi::None));
379        }
380        result
381    }
382
383    /// Reduces the given value to a chain of appropriate binary prefixes as an `i64`,
384    /// stopping when the remainder is less than the given threshold.
385    #[must_use]
386    #[cfg(feature = "alloc")]
387    #[cfg_attr(nightly_doc, doc(cfg(feature = "alloc")))]
388    pub fn reduce_chain_i64(value: i64, threshold: i64) -> Vec<(i64, Self)> {
389        let mut result = Vec::new();
390        let mut remainder = value;
391
392        while remainder > threshold {
393            let (size, unit, new_remainder) = Self::reduce_i64(remainder);
394            result.push((size, unit));
395            remainder = new_remainder;
396
397            if remainder < threshold {
398                break;
399            }
400        }
401        if remainder >= threshold {
402            result.push((remainder, UnitBi::None));
403        }
404        result
405    }
406
407    /// Reduces the given value to a chain of appropriate binary prefixes as an `i128`,
408    /// stopping when the remainder is less than the given threshold.
409    #[must_use]
410    #[cfg(feature = "alloc")]
411    #[cfg_attr(nightly_doc, doc(cfg(feature = "alloc")))]
412    pub fn reduce_chain_i128(value: i128, threshold: i128) -> Vec<(i128, Self)> {
413        let mut result = Vec::new();
414        let mut remainder = value;
415
416        while remainder > threshold {
417            let (size, unit, new_remainder) = Self::reduce_i128(remainder);
418            result.push((size, unit));
419            remainder = new_remainder;
420
421            if remainder < threshold {
422                break;
423            }
424        }
425        if remainder > threshold {
426            result.push((remainder, UnitBi::None));
427        }
428        result
429    }
430}
431
432impl UnitBi {
433    /// Returns an iterator in ascending order of magnitude.
434    pub fn asc_iter() -> impl Iterator<Item = Self> {
435        const UNITS: [UnitBi; 9] = [
436            UnitBi::None,
437            UnitBi::Kibi,
438            UnitBi::Mebi,
439            UnitBi::Gibi,
440            UnitBi::Tebi,
441            UnitBi::Pebi,
442            UnitBi::Exbi,
443            UnitBi::Zebi,
444            UnitBi::Yobi,
445        ];
446        UNITS.iter().copied()
447    }
448
449    /// Returns an iterator in descending order of magnitude.
450    pub fn desc_iter() -> impl Iterator<Item = Self> {
451        const UNITS: [UnitBi; 9] = [
452            UnitBi::Yobi,
453            UnitBi::Zebi,
454            UnitBi::Exbi,
455            UnitBi::Pebi,
456            UnitBi::Tebi,
457            UnitBi::Gibi,
458            UnitBi::Mebi,
459            UnitBi::Kibi,
460            UnitBi::None,
461        ];
462        UNITS.iter().copied()
463    }
464}
465
466impl From<UnitBi> for f32 {
467    fn from(from: UnitBi) -> f32 {
468        from.factor() as f32
469    }
470}
471impl From<UnitBi> for f64 {
472    fn from(from: UnitBi) -> f64 {
473        from.factor()
474    }
475}
476impl From<UnitBi> for i64 {
477    fn from(from: UnitBi) -> i64 {
478        from.factor_i64()
479    }
480}
481impl From<UnitBi> for i128 {
482    fn from(from: UnitBi) -> i128 {
483        from.factor_i128()
484    }
485}
486__phys_unit_impl_try_from![UnitBi, i64 => i32, i16, u64, u32, u16];
487__phys_unit_impl_try_from![UnitBi, i128 => u128];
488
489#[cfg(test)]
490mod tests {
491    use super::{
492        UnitBi,
493        UnitBi::{Exbi, Gibi, Kibi, Mebi, Yobi, Zebi},
494    };
495    #[allow(unused_imports)]
496    use crate::FloatConst;
497    #[cfg(feature = "alloc")]
498    use crate::vec_ as vec;
499
500    /* reduce */
501
502    #[test]
503    fn unit_bi_reduce_i64() {
504        let value = i64::from(Exbi);
505        let (reduced_value, unit, remainder) = UnitBi::reduce_i64(value);
506        assert_eq!(reduced_value, 1);
507        assert_eq!(unit, Exbi);
508        assert_eq!(remainder, 0);
509
510        let value = 2_000_000_000; // Between Gibi and Tebi
511        let (reduced_value, unit, remainder) = UnitBi::reduce_i64(value);
512        assert_eq!(reduced_value, 1);
513        assert_eq!(unit, Gibi);
514        assert_eq!(remainder, 926_258_176); // 2_000_000_000 - 1_073_741_824
515
516        let value = 2 * i64::from(Kibi) + 512;
517        let (reduced_value, unit, remainder) = UnitBi::reduce_i64(value);
518        assert_eq!(reduced_value, 2);
519        assert_eq!(unit, Kibi);
520        assert_eq!(remainder, 512);
521    }
522
523    #[test]
524    fn unit_bi_reduce_i128() {
525        let value = Yobi.factor_i128();
526        let (reduced_value, unit, remainder) = UnitBi::reduce_i128(value);
527        assert_eq!(reduced_value, 1);
528        assert_eq!(unit, Yobi);
529        assert_eq!(remainder, 0);
530
531        let value = i128::from(Zebi) + i128::from(Mebi);
532        let (reduced_value, unit, remainder) = UnitBi::reduce_i128(value);
533        assert_eq!(reduced_value, 1);
534        assert_eq!(unit, Zebi);
535        assert_eq!(remainder, Mebi.factor_i128());
536    }
537
538    /* reduce_chain */
539
540    #[test]
541    #[cfg(feature = "alloc")]
542    #[cfg_attr(nightly_doc, doc(cfg(feature = "alloc")))]
543    fn unit_bi_reduce_chain() {
544        let margin = f64::MEDIUM_MARGIN;
545
546        assert_eq![
547            // single unit: 1Gi
548            UnitBi::reduce_chain(Gibi.factor(), margin),
549            vec![(1.0, Gibi)]
550        ];
551        assert_eq![
552            // multiple unit: 1.5Gi
553            UnitBi::reduce_chain(1.5 * Gibi.factor(), margin),
554            vec![(1.0, Gibi), (512.0, Mebi)]
555        ];
556        assert_eq![
557            // 1Gi + 1Ki
558            UnitBi::reduce_chain(Gibi.factor() + Kibi.factor(), margin),
559            vec![(1., Gibi), (1., Kibi)]
560        ];
561        assert_eq![
562            // Small value (only 512Ki)
563            UnitBi::reduce_chain(Mebi.factor() / 2., margin),
564            vec![(512., Kibi)]
565        ];
566        assert_eq![
567            // Very large value (3Yi + 2Zi + 1Gi)
568            UnitBi::reduce_chain(3. * Yobi.factor() + 2. * Zebi.factor() + Gibi.factor(), margin),
569            vec![(3.0, Yobi), (2.0, Zebi), (1.0, Gibi)]
570        ];
571        assert_eq![
572            // Zero value
573            UnitBi::reduce_chain(0.0, margin),
574            vec![(0.0, UnitBi::None)]
575        ];
576        assert_eq![
577            // Fractional value (0.5 Gi)
578            UnitBi::reduce_chain(Gibi.factor() / 2., margin),
579            vec![(512., Mebi)]
580        ];
581    }
582}