devela/num/unit/
bi.rs

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