devela/num/unit/
bi.rs

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