devela/num/unit/
si.rs

1// devela::num::unit::si
2//
3//! SI unit prefixes.
4//
5
6use super::helpers::impl_try_from;
7#[allow(unused_imports)]
8#[cfg(feature = "_float_f64")]
9use crate::ExtFloat;
10#[allow(unused_imports)]
11#[cfg(feature = "alloc")]
12use crate::{vec_ as vec, Vec};
13
14/// SI (metric) unit prefixes.
15///
16/// - <https://en.wikipedia.org/wiki/Metric_prefix>
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum UnitSi {
20    /// 10^30
21    Quetta,
22    /// 10^27
23    Ronna,
24    /// 10^24
25    Yotta,
26    /// 10^21
27    Zetta,
28    /// 10^18
29    Exa,
30    /// 10^15
31    Peta,
32    /// 10^12
33    Tera,
34    /// 10^9
35    Giga,
36    /// 10^6
37    Mega,
38    /// 10^3
39    Kilo,
40    /// 10^2
41    Hecto,
42    /// 10^1
43    Deca,
44
45    /// 10^0 (no prefix)
46    #[default]
47    None,
48
49    /// 10^-1
50    Deci,
51    /// 10^-2
52    Centi,
53    /// 10^-3
54    Milli,
55    /// 10^-6
56    Micro,
57    /// 10^-9
58    Nano,
59    /// 10^-12
60    Pico,
61    /// 10^-15
62    Femto,
63    /// 10^-18
64    Atto,
65    /// 10^-21
66    Zepto,
67    /// 10^-24
68    Yocto,
69    /// 10^-27
70    Ronto,
71    /// 10^-30
72    Quecto,
73}
74
75/// # Aliases.
76#[allow(non_upper_case_globals)]
77impl UnitSi {
78    /// Alias of `Quetta`.
79    pub const Q: Self = Self::Quetta;
80    /// Alias of `Ronna`.
81    pub const R: Self = Self::Ronna;
82    /// Alias of `Yotta`.
83    pub const Y: Self = Self::Yotta;
84    /// Alias of `Zetta`.
85    pub const Z: Self = Self::Zetta;
86    /// Alias of `Exa`.
87    pub const E: Self = Self::Exa;
88    /// Alias of `Peta`.
89    pub const P: Self = Self::Peta;
90    /// Alias of `Tera`.
91    pub const T: Self = Self::Tera;
92    /// Alias of `Giga`.
93    pub const G: Self = Self::Giga;
94    /// Alias of `Mega`.
95    pub const M: Self = Self::Mega;
96    /// Alias of `Kilo`.
97    pub const k: Self = Self::Kilo;
98    /// Alias of `Kilo` (alternative).
99    pub const K: Self = Self::Kilo;
100    /// Alias of `Hecto`.
101    pub const h: Self = Self::Hecto;
102    /// Alias of `Hecto` (alternative).
103    pub const H: Self = Self::Hecto;
104    /// Alias of `Deca`.
105    pub const da: Self = Self::Deca;
106    /// Alias of `Deca` (alternative).
107    pub const D: Self = Self::Deca;
108    //
109    /// Alias of `Deci`.
110    pub const d: Self = Self::Deci;
111    /// Alias of `Centi`.
112    pub const c: Self = Self::Centi;
113    /// Alias of `Milli`.
114    pub const m: Self = Self::Milli;
115    /// Alias of `Micro` (alternative to µ).
116    pub const u: Self = Self::Micro;
117    /// Alias of `Nano`.
118    pub const n: Self = Self::Nano;
119    /// Alias of `Pico`.
120    pub const p: Self = Self::Pico;
121    /// Alias of `Femto`.
122    pub const f: Self = Self::Femto;
123    /// Alias of `Atto`.
124    pub const a: Self = Self::Atto;
125    /// Alias of `Zepto`.
126    pub const z: Self = Self::Zepto;
127    /// Alias of `Yocto`.
128    pub const y: Self = Self::Yocto;
129    /// Alias of `Ronto`.
130    pub const r: Self = Self::Ronto;
131    /// Alias of `Quecto`.
132    pub const q: Self = Self::Quecto;
133}
134
135impl UnitSi {
136    /// Returns the symbol of the prefix.
137    ///
138    /// # Example
139    /// ```
140    /// # use devela::UnitSi;
141    /// assert_eq![UnitSi::Giga.symbol(), "G"];
142    /// assert_eq![UnitSi::Micro.symbol(), "µ"];
143    /// ```
144    #[must_use]
145    pub const fn symbol(&self) -> &str {
146        match self {
147            UnitSi::Quetta => "Q",
148            UnitSi::Ronna => "R",
149            UnitSi::Yotta => "Y",
150            UnitSi::Zetta => "Z",
151            UnitSi::Exa => "E",
152            UnitSi::Peta => "P",
153            UnitSi::Tera => "T",
154            UnitSi::Giga => "G",
155            UnitSi::Mega => "M",
156            UnitSi::Kilo => "k",
157            UnitSi::Hecto => "H",
158            UnitSi::Deca => "D",
159            UnitSi::None => "",
160            UnitSi::Deci => "d",
161            UnitSi::Centi => "c",
162            UnitSi::Milli => "m",
163            UnitSi::Micro => "µ",
164            UnitSi::Nano => "n",
165            UnitSi::Pico => "p",
166            UnitSi::Femto => "f",
167            UnitSi::Atto => "a",
168            UnitSi::Zepto => "z",
169            UnitSi::Yocto => "y",
170            UnitSi::Ronto => "r",
171            UnitSi::Quecto => "q",
172        }
173    }
174    /// Returns the ASCII symbol of the prefix.
175    #[must_use]
176    pub const fn symbol_ascii(&self) -> &str {
177        match self {
178            UnitSi::Micro => "u",
179            _ => self.symbol(),
180        }
181    }
182
183    /// Returns the name of the prefix.
184    #[must_use]
185    pub const fn name(&self) -> &str {
186        match self {
187            UnitSi::Quetta => "quetta",
188            UnitSi::Ronna => "ronna",
189            UnitSi::Yotta => "yotta",
190            UnitSi::Zetta => "zetta",
191            UnitSi::Exa => "exa",
192            UnitSi::Peta => "peta",
193            UnitSi::Tera => "tera",
194            UnitSi::Giga => "giga",
195            UnitSi::Mega => "mega",
196            UnitSi::Kilo => "kilo",
197            UnitSi::Hecto => "hecto",
198            UnitSi::Deca => "deca",
199            UnitSi::None => "",
200            UnitSi::Deci => "deci",
201            UnitSi::Centi => "centi",
202            UnitSi::Milli => "milli",
203            UnitSi::Micro => "micro",
204            UnitSi::Nano => "nano",
205            UnitSi::Pico => "pico",
206            UnitSi::Femto => "femto",
207            UnitSi::Atto => "atoo",
208            UnitSi::Zepto => "zepto",
209            UnitSi::Yocto => "yocto",
210            UnitSi::Ronto => "ronto",
211            UnitSi::Quecto => "quecto",
212        }
213    }
214
215    /// The base value for SI unit prefixes.
216    pub const BASE: i32 = 10;
217
218    /// Returns the exponent associated with the SI unit prefix.
219    pub const fn exp(&self) -> i32 {
220        match self {
221            UnitSi::Quetta => 30,
222            UnitSi::Ronna => 27,
223            UnitSi::Yotta => 24,
224            UnitSi::Zetta => 21,
225            UnitSi::Exa => 18,
226            UnitSi::Peta => 15,
227            UnitSi::Tera => 12,
228            UnitSi::Giga => 9,
229            UnitSi::Mega => 6,
230            UnitSi::Kilo => 3,
231            UnitSi::Hecto => 2,
232            UnitSi::Deca => 1,
233            UnitSi::None => 0,
234            UnitSi::Deci => -1,
235            UnitSi::Centi => -2,
236            UnitSi::Milli => -3,
237            UnitSi::Micro => -6,
238            UnitSi::Nano => -9,
239            UnitSi::Pico => -12,
240            UnitSi::Femto => -15,
241            UnitSi::Atto => -18,
242            UnitSi::Zepto => -21,
243            UnitSi::Yocto => -24,
244            UnitSi::Ronto => -27,
245            UnitSi::Quecto => -30,
246        }
247    }
248
249    /// Returns the multiplication factor for the SI prefix as an `f64`.
250    pub const fn factor(&self) -> f64 {
251        match self {
252            UnitSi::Quetta => 1e30,
253            UnitSi::Ronna => 1e27,
254            UnitSi::Yotta => 1e24,
255            UnitSi::Zetta => 1e21,
256            UnitSi::Exa => 1e18,
257            UnitSi::Peta => 1e15,
258            UnitSi::Tera => 1e12,
259            UnitSi::Giga => 1e9,
260            UnitSi::Kilo => 1e3,
261            UnitSi::Mega => 1e6,
262            UnitSi::Hecto => 1e2,
263            UnitSi::Deca => 1e1,
264            UnitSi::None => 1e0,
265            UnitSi::Deci => 1e-1,
266            UnitSi::Centi => 1e-2,
267            UnitSi::Milli => 1e-3,
268            UnitSi::Micro => 1e-6,
269            UnitSi::Nano => 1e-9,
270            UnitSi::Pico => 1e-12,
271            UnitSi::Femto => 1e-15,
272            UnitSi::Atto => 1e-18,
273            UnitSi::Zepto => 1e-21,
274            UnitSi::Yocto => 1e-24,
275            UnitSi::Ronto => 1e-27,
276            UnitSi::Quecto => 1e-30,
277        }
278    }
279
280    /// Returns the multiplication factor for the SI prefix as an `i64`.
281    ///
282    /// Negative values represent reciprocal factors, indicating
283    /// that the unit corresponds to a fractional multiplier
284    /// (e.g., -1000 represents a factor of 1/1000).
285    ///
286    /// This method only supports the range from `Exa` to `Atto`,
287    /// returning 0 for `>= Zetta` and for `<= Zepto`.
288    pub const fn factor_i64(&self) -> i64 {
289        match self {
290            UnitSi::Exa => 1_000_000_000_000_000_000,
291            UnitSi::Peta => 1_000_000_000_000_000,
292            UnitSi::Tera => 1_000_000_000_000,
293            UnitSi::Giga => 1_000_000_000,
294            UnitSi::Mega => 1_000_000,
295            UnitSi::Kilo => 1_000,
296            UnitSi::Hecto => 100,
297            UnitSi::Deca => 10,
298            UnitSi::None => 1,
299            UnitSi::Deci => -10,
300            UnitSi::Centi => -100,
301            UnitSi::Milli => -1_000,
302            UnitSi::Micro => -1_000_000,
303            UnitSi::Nano => -1_000_000_000,
304            UnitSi::Pico => -1_000_000_000_000,
305            UnitSi::Femto => -1_000_000_000_000_000,
306            UnitSi::Atto => -1_000_000_000_000_000_000,
307            _ => todo![],
308        }
309    }
310
311    /// Returns the multiplication factor for the SI prefix as an `i128`.
312    pub const fn factor_i128(&self) -> i128 {
313        match self {
314            UnitSi::Quetta => 1_000_000_000_000_000_000_000_000_000_000,
315            UnitSi::Ronna => 1_000_000_000_000_000_000_000_000_000,
316            UnitSi::Yotta => 1_000_000_000_000_000_000_000_000,
317            UnitSi::Zetta => 1_000_000_000_000_000_000_000,
318            UnitSi::Zepto => -1_000_000_000_000_000_000_000,
319            UnitSi::Yocto => -1_000_000_000_000_000_000_000_000,
320            UnitSi::Ronto => -1_000_000_000_000_000_000_000_000_000,
321            UnitSi::Quecto => -1_000_000_000_000_000_000_000_000_000_000,
322            _ => self.factor_i64() as i128,
323        }
324    }
325
326    /// Converts a value from one SI prefix to another,
327    /// returning the converted value.
328    #[must_use]
329    pub fn convert(value: f64, from: Self, to: Self) -> f64 {
330        if from == to {
331            return value;
332        }
333        let (from_factor, to_factor) = (from.factor(), to.factor());
334        value * (from_factor / to_factor)
335    }
336
337    /// Converts a value from one SI prefix to another,
338    /// returning the converted value and the remainder.
339    #[must_use]
340    pub fn convert_i64(value: i64, from: Self, to: Self) -> (i64, i64) {
341        if from == to {
342            return (value, 0);
343        }
344        let (from_factor, to_factor) = (from.factor_i64(), to.factor_i64());
345
346        // Determine the absolute conversion and handle positive/negative factors
347        let converted = if from_factor > 0 && to_factor > 0 {
348            value * from_factor / to_factor
349        } else if from_factor > 0 && to_factor < 0 {
350            value * from_factor * to_factor.abs()
351        } else if from_factor < 0 && to_factor > 0 {
352            value * from_factor.abs() / to_factor
353        } else {
354            value * from_factor.abs() * to_factor.abs()
355        };
356
357        let remainder = if from_factor > 0 && to_factor > 0 {
358            value * from_factor % to_factor
359        } else if from_factor > 0 && to_factor < 0 {
360            value * from_factor
361        } else if from_factor < 0 && to_factor > 0 {
362            value % to_factor
363        } else {
364            0 // No remainder calculation needed for reciprocal to reciprocal conversion
365        };
366
367        (converted, remainder)
368    }
369
370    /// Converts a value from one SI prefix to another,
371    /// returning the converted value and the remainder.
372    #[must_use]
373    pub fn convert_i128(value: i128, from: Self, to: Self) -> (i128, i128) {
374        if from == to {
375            return (value, 0);
376        }
377        let (from_factor, to_factor) = (from.factor_i128(), to.factor_i128());
378
379        // Determine the absolute conversion and handle positive/negative factors
380        let converted = if from_factor > 0 && to_factor > 0 {
381            value * from_factor / to_factor
382        } else if from_factor > 0 && to_factor < 0 {
383            value * from_factor * to_factor.abs()
384        } else if from_factor < 0 && to_factor > 0 {
385            value * from_factor.abs() / to_factor
386        } else {
387            value * from_factor.abs() * to_factor.abs()
388        };
389
390        let remainder = if from_factor > 0 && to_factor > 0 {
391            value * from_factor % to_factor
392        } else if from_factor > 0 && to_factor < 0 {
393            value * from_factor
394        } else if from_factor < 0 && to_factor > 0 {
395            value % to_factor
396        } else {
397            0 // No remainder calculation needed for reciprocal to reciprocal conversion
398        };
399
400        (converted, remainder)
401    }
402
403    /// Reduces the given value to the most appropriate SI prefix as an `f64`,
404    /// returning a tuple of the reduced size and the prefix.
405    #[cfg(any(feature = "std", feature = "_float_f64"))]
406    #[cfg_attr(feature = "nightly_doc", doc(cfg(any(feature = "std", feature = "_float_f64"))))]
407    pub fn reduce(value: f64) -> (f64, Self) {
408        match value.abs() {
409            value if value >= UnitSi::Quetta.factor() => {
410                (value / UnitSi::Quetta.factor(), UnitSi::Quetta)
411            }
412            value if value >= UnitSi::Ronna.factor() => {
413                (value / UnitSi::Ronna.factor(), UnitSi::Ronna)
414            }
415            value if value >= UnitSi::Yotta.factor() => {
416                (value / UnitSi::Yotta.factor(), UnitSi::Yotta)
417            }
418            value if value >= UnitSi::Zetta.factor() => {
419                (value / UnitSi::Zetta.factor(), UnitSi::Zetta)
420            }
421            value if value >= UnitSi::Exa.factor() => (value / UnitSi::Exa.factor(), UnitSi::Exa),
422            value if value >= UnitSi::Peta.factor() => {
423                (value / UnitSi::Peta.factor(), UnitSi::Peta)
424            }
425            value if value >= UnitSi::Tera.factor() => {
426                (value / UnitSi::Tera.factor(), UnitSi::Tera)
427            }
428            value if value >= UnitSi::Giga.factor() => {
429                (value / UnitSi::Giga.factor(), UnitSi::Giga)
430            }
431            value if value >= UnitSi::Mega.factor() => {
432                (value / UnitSi::Mega.factor(), UnitSi::Mega)
433            }
434            value if value >= UnitSi::Kilo.factor() => {
435                (value / UnitSi::Kilo.factor(), UnitSi::Kilo)
436            }
437            value if value >= UnitSi::Hecto.factor() => {
438                (value / UnitSi::Hecto.factor(), UnitSi::Hecto)
439            }
440            value if value >= UnitSi::Deca.factor() => {
441                (value / UnitSi::Deca.factor(), UnitSi::Deca)
442            }
443            //
444            value if value >= UnitSi::Deci.factor() => {
445                (value / UnitSi::Deci.factor(), UnitSi::Deci)
446            }
447            value if value >= UnitSi::Centi.factor() => {
448                (value / UnitSi::Centi.factor(), UnitSi::Centi)
449            }
450            value if value >= UnitSi::Milli.factor() => {
451                (value / UnitSi::Milli.factor(), UnitSi::Milli)
452            }
453            value if value >= UnitSi::Micro.factor() => {
454                (value / UnitSi::Micro.factor(), UnitSi::Micro)
455            }
456            value if value >= UnitSi::Nano.factor() => {
457                (value / UnitSi::Nano.factor(), UnitSi::Nano)
458            }
459            value if value >= UnitSi::Pico.factor() => {
460                (value / UnitSi::Pico.factor(), UnitSi::Pico)
461            }
462            value if value >= UnitSi::Femto.factor() => {
463                (value / UnitSi::Femto.factor(), UnitSi::Femto)
464            }
465            value if value >= UnitSi::Atto.factor() => {
466                (value / UnitSi::Atto.factor(), UnitSi::Atto)
467            }
468            value if value >= UnitSi::Zepto.factor() => {
469                (value / UnitSi::Zepto.factor(), UnitSi::Zepto)
470            }
471            value if value >= UnitSi::Yocto.factor() => {
472                (value / UnitSi::Yocto.factor(), UnitSi::Yocto)
473            }
474            value if value >= UnitSi::Ronto.factor() => {
475                (value / UnitSi::Ronto.factor(), UnitSi::Ronto)
476            }
477            value if value >= UnitSi::Quecto.factor() => {
478                (value / UnitSi::Quecto.factor(), UnitSi::Quecto)
479            }
480            _ => (value, UnitSi::None),
481        }
482    }
483
484    /// Reduces the given value to the most appropriate SI prefix as an `i64`,
485    /// returning a tuple of the reduced size, the prefix, and the remainder.
486    pub const fn reduce_i64(value: i64) -> (i64, Self, i64) {
487        match value {
488            value if value >= UnitSi::Exa.factor_i64() => {
489                (value / UnitSi::Exa.factor_i64(), UnitSi::Exa, value % UnitSi::Exa.factor_i64())
490            }
491            value if value >= UnitSi::Peta.factor_i64() => {
492                (value / UnitSi::Peta.factor_i64(), UnitSi::Peta, value % UnitSi::Peta.factor_i64())
493            }
494            value if value >= UnitSi::Tera.factor_i64() => {
495                (value / UnitSi::Tera.factor_i64(), UnitSi::Tera, value % UnitSi::Tera.factor_i64())
496            }
497            value if value >= UnitSi::Giga.factor_i64() => {
498                (value / UnitSi::Giga.factor_i64(), UnitSi::Giga, value % UnitSi::Giga.factor_i64())
499            }
500            value if value >= UnitSi::Mega.factor_i64() => {
501                (value / UnitSi::Mega.factor_i64(), UnitSi::Mega, value % UnitSi::Mega.factor_i64())
502            }
503            value if value >= UnitSi::Kilo.factor_i64() => {
504                (value / UnitSi::Kilo.factor_i64(), UnitSi::Kilo, value % UnitSi::Kilo.factor_i64())
505            }
506            value if value >= UnitSi::Hecto.factor_i64() => (
507                value / UnitSi::Hecto.factor_i64(),
508                UnitSi::Hecto,
509                value % UnitSi::Hecto.factor_i64(),
510            ),
511            value if value >= UnitSi::Deca.factor_i64() => {
512                (value / UnitSi::Deca.factor_i64(), UnitSi::Deca, value % UnitSi::Deca.factor_i64())
513            }
514            value if value <= UnitSi::Atto.factor_i64() => {
515                (value * UnitSi::Atto.factor_i64().abs(), UnitSi::Atto, 0)
516            }
517            value if value <= UnitSi::Femto.factor_i64() => {
518                (value * UnitSi::Femto.factor_i64().abs(), UnitSi::Femto, 0)
519            }
520            value if value <= UnitSi::Pico.factor_i64() => {
521                (value * UnitSi::Pico.factor_i64().abs(), UnitSi::Pico, 0)
522            }
523            value if value <= UnitSi::Nano.factor_i64() => {
524                (value * UnitSi::Nano.factor_i64().abs(), UnitSi::Nano, 0)
525            }
526            value if value <= UnitSi::Micro.factor_i64() => {
527                (value * UnitSi::Micro.factor_i64().abs(), UnitSi::Micro, 0)
528            }
529            value if value <= UnitSi::Milli.factor_i64() => {
530                (value * UnitSi::Milli.factor_i64().abs(), UnitSi::Milli, 0)
531            }
532            value if value <= UnitSi::Centi.factor_i64() => {
533                (value * UnitSi::Centi.factor_i64().abs(), UnitSi::Centi, 0)
534            }
535            value if value <= UnitSi::Deci.factor_i64() => {
536                (value * UnitSi::Deci.factor_i64().abs(), UnitSi::Deci, 0)
537            }
538            _ => (value, UnitSi::None, 0),
539        }
540    }
541
542    /// Reduces the given value to the most appropriate SI prefix as an `i128`,
543    /// returning a tuple of the reduced size, the prefix, and the remainder.
544    pub const fn reduce_i128(value: i128) -> (i128, Self, i128) {
545        match value {
546            value if value >= UnitSi::Quetta.factor_i128() => (
547                value / UnitSi::Quetta.factor_i128(),
548                UnitSi::Quetta,
549                value % UnitSi::Quetta.factor_i128(),
550            ),
551            value if value >= UnitSi::Ronna.factor_i128() => (
552                value / UnitSi::Ronna.factor_i128(),
553                UnitSi::Ronna,
554                value % UnitSi::Ronna.factor_i128(),
555            ),
556            value if value >= UnitSi::Yotta.factor_i128() => (
557                value / UnitSi::Yotta.factor_i128(),
558                UnitSi::Yotta,
559                value % UnitSi::Yotta.factor_i128(),
560            ),
561            value if value >= UnitSi::Zetta.factor_i128() => (
562                value / UnitSi::Zetta.factor_i128(),
563                UnitSi::Zetta,
564                value % UnitSi::Zetta.factor_i128(),
565            ),
566            value if value <= UnitSi::Quecto.factor_i128() => {
567                (value * UnitSi::Quecto.factor_i128().abs(), UnitSi::Quecto, 0)
568            }
569            value if value <= UnitSi::Ronto.factor_i128() => {
570                (value * UnitSi::Ronto.factor_i128().abs(), UnitSi::Ronto, 0)
571            }
572            value if value <= UnitSi::Yocto.factor_i128() => {
573                (value * UnitSi::Yocto.factor_i128().abs(), UnitSi::Yocto, 0)
574            }
575            value if value <= UnitSi::Zepto.factor_i128() => {
576                (value * UnitSi::Zepto.factor_i128().abs(), UnitSi::Zepto, 0)
577            }
578            _ => {
579                let (v, p, r) = Self::reduce_i64(value as i64);
580                (v as i128, p, r as i128)
581            }
582        }
583    }
584
585    /// Reduces the given value to a chain of appropriate SI prefixes as an `f64`,
586    /// stopping when the remainder is less than the given threshold.
587    #[must_use]
588    #[cfg(any(feature = "std", all(feature = "alloc", feature = "_float_f64")))]
589    #[cfg_attr(
590        feature = "nightly_doc",
591        doc(cfg(any(feature = "std", all(feature = "alloc", feature = "_float_f64"))))
592    )]
593    pub fn reduce_chain(value: f64, threshold: f64) -> Vec<(f64, Self)> {
594        if value == 0.0 {
595            return vec![(0.0, UnitSi::None)];
596        }
597
598        let mut result = Vec::new();
599        let mut remainder = value;
600
601        // Ensure the threshold is positive and above a small epsilon value
602        // in order to prevent infinite loops
603        let effective_threshold =
604            if threshold <= 0.0 { crate::ExtFloatConst::MEDIUM_MARGIN } else { threshold };
605
606        while remainder.abs() > effective_threshold {
607            let (size, unit) = Self::reduce(remainder);
608            let integer_part = size.trunc();
609            let fractional_part = size - integer_part;
610            result.push((integer_part, unit));
611            remainder = fractional_part * unit.factor();
612
613            if remainder.abs() < effective_threshold {
614                break;
615            }
616        }
617        if remainder.abs() >= effective_threshold {
618            result.push((remainder, UnitSi::None));
619        }
620        result
621    }
622
623    /// Reduces the given value to a chain of appropriate SI prefixes as an `i64`,
624    /// stopping when the remainder is less than the given threshold.
625    #[must_use]
626    #[cfg(feature = "alloc")]
627    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "alloc")))]
628    pub fn reduce_chain_i64(value: i64, threshold: i64) -> Vec<(i64, Self)> {
629        let mut result = Vec::new();
630        let mut remainder = value;
631
632        while remainder.abs() > threshold.abs() {
633            let (size, unit, new_remainder) = Self::reduce_i64(remainder);
634            result.push((size, unit));
635            remainder = new_remainder;
636
637            if remainder.abs() < threshold.abs() {
638                break;
639            }
640        }
641        if remainder.abs() >= threshold.abs() {
642            result.push((remainder, UnitSi::None));
643        }
644        result
645    }
646
647    /// Reduces the given value to a chain of appropriate SI prefixes as an `i128`,
648    /// stopping when the remainder is less than the given threshold.
649    #[must_use]
650    #[cfg(feature = "alloc")]
651    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "alloc")))]
652    pub fn reduce_chain_i128(value: i128, threshold: i128) -> Vec<(i128, Self)> {
653        let mut result = Vec::new();
654        let mut remainder = value;
655
656        while remainder.abs() > threshold.abs() {
657            let (size, unit, new_remainder) = Self::reduce_i128(remainder);
658            result.push((size, unit));
659            remainder = new_remainder;
660
661            if remainder.abs() < threshold.abs() {
662                break;
663            }
664        }
665        if remainder.abs() >= threshold.abs() {
666            result.push((remainder, UnitSi::None));
667        }
668        result
669    }
670}
671
672impl UnitSi {
673    /// Returns an iterator in ascending order of magnitude.
674    pub fn asc_iter() -> impl Iterator<Item = Self> {
675        const UNITS: [UnitSi; 25] = [
676            UnitSi::Quecto,
677            UnitSi::Ronto,
678            UnitSi::Yocto,
679            UnitSi::Zepto,
680            UnitSi::Atto,
681            UnitSi::Femto,
682            UnitSi::Pico,
683            UnitSi::Nano,
684            UnitSi::Micro,
685            UnitSi::Milli,
686            UnitSi::Centi,
687            UnitSi::Deci,
688            UnitSi::None,
689            UnitSi::Deca,
690            UnitSi::Hecto,
691            UnitSi::Kilo,
692            UnitSi::Mega,
693            UnitSi::Giga,
694            UnitSi::Tera,
695            UnitSi::Peta,
696            UnitSi::Exa,
697            UnitSi::Zetta,
698            UnitSi::Yotta,
699            UnitSi::Ronna,
700            UnitSi::Quetta,
701        ];
702        UNITS.iter().copied()
703    }
704
705    /// Returns an iterator in descending order of magnitude.
706    pub fn desc_iter() -> impl Iterator<Item = Self> {
707        const UNITS: [UnitSi; 25] = [
708            UnitSi::Quetta,
709            UnitSi::Ronna,
710            UnitSi::Yotta,
711            UnitSi::Zetta,
712            UnitSi::Exa,
713            UnitSi::Peta,
714            UnitSi::Tera,
715            UnitSi::Giga,
716            UnitSi::Mega,
717            UnitSi::Kilo,
718            UnitSi::Hecto,
719            UnitSi::Deca,
720            UnitSi::None,
721            UnitSi::Deci,
722            UnitSi::Centi,
723            UnitSi::Milli,
724            UnitSi::Micro,
725            UnitSi::Nano,
726            UnitSi::Pico,
727            UnitSi::Femto,
728            UnitSi::Atto,
729            UnitSi::Zepto,
730            UnitSi::Yocto,
731            UnitSi::Ronto,
732            UnitSi::Quecto,
733        ];
734        UNITS.iter().copied()
735    }
736}
737
738impl From<UnitSi> for f32 {
739    fn from(from: UnitSi) -> f32 {
740        from.factor() as f32
741    }
742}
743impl From<UnitSi> for f64 {
744    fn from(from: UnitSi) -> f64 {
745        from.factor()
746    }
747}
748impl From<UnitSi> for i64 {
749    fn from(from: UnitSi) -> i64 {
750        from.factor_i64()
751    }
752}
753impl From<UnitSi> for i128 {
754    fn from(from: UnitSi) -> i128 {
755        from.factor_i128()
756    }
757}
758impl_try_from![UnitSi, i64 => i32, i16, u64, u32, u16];
759impl_try_from![UnitSi, i128 => u128];
760
761#[cfg(test)]
762mod tests {
763    #![allow(unused_imports)]
764    use super::{
765        UnitSi,
766        UnitSi::{
767            Centi, Deca, Deci, Giga, Hecto, Kilo, Mega, Micro, Milli, Nano, Quetta, Ronna, Tera,
768            Yotta, Zetta,
769        },
770    };
771    #[cfg(any(feature = "std", all(feature = "alloc", feature = "_float_f64")))]
772    use crate::vec_ as vec;
773    use crate::{code::sf, num::ExtFloatConst};
774
775    /* reduce */
776
777    #[test]
778    fn unit_si_reduce_i64() {
779        let value = 4 * i64::from(Tera);
780        let (reduced_value, unit, remainder) = UnitSi::reduce_i64(value);
781        assert_eq!(reduced_value, 4);
782        assert_eq!(unit, Tera);
783        assert_eq!(remainder, 0);
784
785        let value = 15 * i64::from(Kilo) + 500; // 15.5 k
786        let (reduced_value, unit, remainder) = UnitSi::reduce_i64(value);
787        assert_eq!(reduced_value, 15);
788        assert_eq!(unit, Kilo);
789        assert_eq!(remainder, 500);
790
791        let value = 1; // Base unit
792        let (reduced_value, unit, remainder) = UnitSi::reduce_i64(value);
793        assert_eq!(reduced_value, 1);
794        assert_eq!(unit, UnitSi::None);
795        assert_eq!(remainder, 0);
796
797        let value = 999;
798        let (reduced_value, unit, remainder) = UnitSi::reduce_i64(value);
799        assert_eq!(reduced_value, 9);
800        assert_eq!(unit, Hecto);
801        assert_eq!(remainder, 99);
802    }
803
804    #[test]
805    fn unit_si_reduce_i128() {
806        let value = 3000 * i128::from(Ronna) + 5 * i128::from(Mega);
807        let (reduced_value, unit, remainder) = UnitSi::reduce_i128(value);
808        assert_eq!(reduced_value, 3);
809        assert_eq!(unit, Quetta);
810        assert_eq!(remainder, 5 * i128::from(Mega));
811
812        let value = 900_033; // Between Kilo and Mega
813        let (reduced_value, unit, remainder) = UnitSi::reduce_i128(value);
814        assert_eq!(reduced_value, 900);
815        assert_eq!(unit, Kilo);
816        assert_eq!(remainder, 33);
817    }
818
819    /* reduce_chain */
820
821    #[test]
822    #[cfg(any(feature = "std", all(feature = "alloc", feature = "_float_f64")))]
823    #[cfg_attr(
824        feature = "nightly_doc",
825        doc(cfg(any(feature = "std", all(feature = "alloc", feature = "_float_f64"))))
826    )]
827    fn unit_si_reduce_chain() {
828        let margin = f64::MEDIUM_MARGIN;
829
830        assert_eq![
831            // single unit: 1G
832            UnitSi::reduce_chain(Giga.factor(), margin),
833            vec![(1.0, Giga)]
834        ];
835        assert_eq![
836            // multiple units: 1.5G
837            UnitSi::reduce_chain(1.5 * Giga.factor(), margin),
838            vec![(1.0, Giga), (500.0, Mega)]
839        ];
840        assert_eq![
841            // 1G + 1K
842            UnitSi::reduce_chain(Giga.factor() + Kilo.factor(), margin),
843            // vec![(1.0, Giga), (1.0, Kilo)] NOTE: rounding error:
844            sf! { vec![
845                (1.0, Giga), (9.0, Hecto), (9.0, Deca), (99.0, Deci),
846                (9.0, Centi), (9.0, Milli), (999.0, Micro), (917.0, Nano)
847            ]}
848        ];
849        assert_eq![
850            // Small value (only 512K)
851            UnitSi::reduce_chain(Mega.factor() / 2.0, margin),
852            vec![(500.0, Kilo)]
853        ];
854        assert_eq![
855            // Very large value (1Y + 1Z + 1G)
856            UnitSi::reduce_chain(Yotta.factor() + Zetta.factor() + Giga.factor(), margin),
857            // vec![(1.0, Yotta), (1.0, Zetta), (1.0, Giga)] // NOTE: rounding error:
858            sf! { vec![
859                (1.0, Yotta), (1.0, Zetta), (1.0, Giga), (88.0, Kilo), (9.0, Hecto),
860                (5.0, Deci), (8.0, Centi), (2.0, Milli), (341.0, Micro), (11.0, Nano)
861            ]}
862        ];
863        assert_eq![
864            // Zero value
865            UnitSi::reduce_chain(0.0, margin),
866            vec![(0.0, UnitSi::None)]
867        ];
868        assert_eq![
869            // Fractional value (0.5G)
870            UnitSi::reduce_chain(Giga.factor() / 2., margin),
871            vec![(500., Mega)]
872        ];
873    }
874}