1use crate::error::Result;
5use crate::ratio::Ratio;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
9#[non_exhaustive]
10pub enum PrefixMode {
11 #[default]
13 None,
14 Si,
16 SiAndBinary,
18}
19
20impl PrefixMode {
21 #[must_use]
23 pub const fn name(self) -> &'static str {
24 match self {
25 Self::None => "none",
26 Self::Si => "si",
27 Self::SiAndBinary => "si_binary",
28 }
29 }
30
31 #[must_use]
33 pub fn from_name(name: &str) -> Option<Self> {
34 match name {
35 "none" => Some(Self::None),
36 "si" => Some(Self::Si),
37 "si_binary" => Some(Self::SiAndBinary),
38 _ => None,
39 }
40 }
41
42 #[must_use]
44 pub const fn accepts(self, family: PrefixFamily) -> bool {
45 matches!(
46 (self, family),
47 (Self::Si | Self::SiAndBinary, PrefixFamily::Si)
48 | (Self::SiAndBinary, PrefixFamily::Binary)
49 )
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55#[non_exhaustive]
56pub enum PrefixFamily {
57 Si,
59 Binary,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct Prefix {
66 pub symbol: &'static str,
68 pub name: &'static str,
70 pub family: PrefixFamily,
72 pub exp: i32,
74}
75
76impl Prefix {
77 pub fn factor(self) -> Result<Ratio> {
84 let base = match self.family {
85 PrefixFamily::Si => 10,
86 PrefixFamily::Binary => 2,
87 };
88 Ratio::from_int(base).checked_pow(self.exp)
89 }
90}
91
92const fn si(symbol: &'static str, name: &'static str, exp: i32) -> Prefix {
93 Prefix {
94 symbol,
95 name,
96 family: PrefixFamily::Si,
97 exp,
98 }
99}
100
101const fn bin(symbol: &'static str, name: &'static str, exp: i32) -> Prefix {
102 Prefix {
103 symbol,
104 name,
105 family: PrefixFamily::Binary,
106 exp,
107 }
108}
109
110pub const SI_PREFIXES: [Prefix; 26] = [
113 si("Q", "quetta", 30),
114 si("R", "ronna", 27),
115 si("Y", "yotta", 24),
116 si("Z", "zetta", 21),
117 si("E", "exa", 18),
118 si("P", "peta", 15),
119 si("T", "tera", 12),
120 si("G", "giga", 9),
121 si("M", "mega", 6),
122 si("k", "kilo", 3),
123 si("h", "hecto", 2),
124 si("da", "deca", 1),
125 si("d", "deci", -1),
126 si("c", "centi", -2),
127 si("m", "milli", -3),
128 si("\u{b5}", "micro", -6),
129 si("\u{3bc}", "micro", -6),
130 si("u", "micro", -6),
131 si("n", "nano", -9),
132 si("p", "pico", -12),
133 si("f", "femto", -15),
134 si("a", "atto", -18),
135 si("z", "zepto", -21),
136 si("y", "yocto", -24),
137 si("r", "ronto", -27),
138 si("q", "quecto", -30),
139];
140
141pub const BINARY_PREFIXES: [Prefix; 8] = [
143 bin("Ki", "kibi", 10),
144 bin("Mi", "mebi", 20),
145 bin("Gi", "gibi", 30),
146 bin("Ti", "tebi", 40),
147 bin("Pi", "pebi", 50),
148 bin("Ei", "exbi", 60),
149 bin("Zi", "zebi", 70),
150 bin("Yi", "yobi", 80),
151];
152
153pub fn all_prefixes() -> impl Iterator<Item = Prefix> {
155 SI_PREFIXES.iter().chain(BINARY_PREFIXES.iter()).copied()
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use std::collections::BTreeSet;
162
163 #[test]
164 fn there_are_24_distinct_si_prefixes() {
165 let exps: BTreeSet<i32> = SI_PREFIXES.iter().map(|p| p.exp).collect();
166 assert_eq!(exps.len(), 24);
168 let names: BTreeSet<&str> = SI_PREFIXES.iter().map(|p| p.name).collect();
169 assert_eq!(names.len(), 24);
170 assert!(names.contains("ronna") && names.contains("quetta"));
171 assert!(names.contains("ronto") && names.contains("quecto"));
172 }
173
174 #[test]
175 fn factors_are_exact() {
176 let q = SI_PREFIXES[0];
177 assert_eq!(q.factor().unwrap(), Ratio::from_int(10_i128.pow(30)));
178 let quecto = SI_PREFIXES[25];
179 assert_eq!(
180 quecto.factor().unwrap(),
181 Ratio::new(1, 10_i128.pow(30)).unwrap()
182 );
183 let yobi = BINARY_PREFIXES[7];
184 assert_eq!(yobi.factor().unwrap(), Ratio::from_int(1_i128 << 80));
185 }
186
187 #[test]
188 fn mode_names() {
189 for m in [PrefixMode::None, PrefixMode::Si, PrefixMode::SiAndBinary] {
190 assert_eq!(PrefixMode::from_name(m.name()), Some(m));
191 }
192 assert!(PrefixMode::Si.accepts(PrefixFamily::Si));
193 assert!(!PrefixMode::Si.accepts(PrefixFamily::Binary));
194 assert!(!PrefixMode::None.accepts(PrefixFamily::Si));
195 assert!(PrefixMode::SiAndBinary.accepts(PrefixFamily::Binary));
196 }
197}