1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/// SI prefix information.
///
/// These SI prefixes are defined by
/// [BIPM/CGPM](https://www.bipm.org/en/measurement-units/si-prefixes).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SiPrefix {
// We make these private since the constants define all instances.
factor_log10: i16,
name: &'static str,
symbol: &'static str,
}
impl SiPrefix {
/// The multiplying factor exponent.
///
/// For example, a multiplying factor of `10^12` would make this field `12`.
pub const fn factor_log10(&self) -> i16 {
self.factor_log10
}
/// Name of the prefix.
pub const fn name(&self) -> &'static str {
self.name
}
/// Prefix symbol.
///
/// A symbol is not always a single character. Deca's symbol `da` is the exception.
pub const fn symbol(&self) -> &'static str {
self.symbol
}
/// Alternative symbols for this prefix.
///
/// Returns a slice of alternative ASCII representations of the prefix symbol.
/// For example, the micro prefix (symbol "µ") has "u" as an alternative.
pub const fn alternative_symbols(&self) -> &'static [&'static str] {
match self.factor_log10 {
-6 => &["u"], // Micro: "µ" can be represented as "u"
_ => &[],
}
}
/// List of all SI prefix definitions.
pub const ALL: &[SiPrefix] = &Self::ALL_ARRAY;
/// This is seperate from `ALL` so we can make sure all the prefixes are there.
/// If we included the array size in the public constant then it would be a
/// breaking change to add prefixes.
const ALL_ARRAY: [SiPrefix; 24] = [
// Small prefixes (negative powers of 10) - submultiple
Self::QUECTO,
Self::RONTO,
Self::YOCTO,
Self::ZEPTO,
Self::ATTO,
Self::FEMTO,
Self::PICO,
Self::NANO,
Self::MICRO,
Self::MILLI,
Self::CENTI,
Self::DECI,
// Large prefixes (positive powers of 10) - multiple
Self::DECA,
Self::HECTO,
Self::KILO,
Self::MEGA,
Self::GIGA,
Self::TERA,
Self::PETA,
Self::EXA,
Self::ZETTA,
Self::YOTTA,
Self::RONNA,
Self::QUETTA,
];
/// Look up SI prefix by symbol.
///
/// Checks both primary symbols and alternative symbols.
pub fn from_symbol(symbol: &str) -> Option<&'static Self> {
Self::ALL.iter().find(|prefix| {
prefix.symbol == symbol || prefix.alternative_symbols().contains(&symbol)
})
}
/// Strip the prefix name from a string.
pub fn strip_prefix_name<'r>(&self, s: &'r str) -> Option<&'r str> {
// Bail out if the string isnt even long enough to have the prefix.
if s.len() < self.name.len() {
return None;
}
// Check the first character. We know all prefixes have ASCII names.
// We allow the first character to be uppercase or lowercase.
let first_char = self.name.as_bytes()[0];
if !s.starts_with([first_char as char, first_char.to_ascii_uppercase() as char]) {
return None;
}
// We then check the rest of the prefix name to be lowercase.
if s.as_bytes()[1..self.name.len()] == self.name.as_bytes()[1..] {
// Since we now know that the multiple starts with a prefix name,
// we know that we can index directly after without a panic.
Some(&s[self.name.len()..])
} else {
None
}
}
/// Strip the prefix symbol from a string.
///
/// Checks both the primary symbol and any alternative symbols.
pub fn strip_prefix_symbol<'r>(&self, s: &'r str) -> Option<&'r str> {
// Try the primary symbol first
if s.len() >= self.symbol.len() {
if &s.as_bytes()[..self.symbol.len()] == self.symbol.as_bytes() {
return Some(&s[self.symbol.len()..]);
}
}
// Try alternative symbols
for alt_symbol in self.alternative_symbols() {
if s.len() >= alt_symbol.len() {
if &s.as_bytes()[..alt_symbol.len()] == alt_symbol.as_bytes() {
return Some(&s[alt_symbol.len()..]);
}
}
}
None
}
/// Strip any prefix name from a string.
///
/// The stripped prefix is returned along with the base unit string.
pub fn strip_any_prefix_name(s: &str) -> Option<(&'static Self, &str)> {
Self::ALL
.iter()
.find_map(|prefix| prefix.strip_prefix_name(s).map(|s| (prefix, s)))
}
/// Strip any prefix symbol from a string.
///
/// The stripped prefix is returned along with the base unit string.
pub fn strip_any_prefix_symbol(s: &str) -> Option<(&'static Self, &str)> {
Self::ALL.iter().find_map(|prefix| {
prefix.strip_prefix_symbol(s).and_then(|base| {
// Only return a prefix if there's actually a base unit after the prefix
if !base.is_empty() {
Some((prefix, base))
} else {
None
}
})
})
}
}
impl SiPrefix {
/// 10⁻³⁰
pub const QUECTO: Self = Self {
symbol: "q",
factor_log10: -30,
name: "quecto",
};
/// 10⁻²⁷
pub const RONTO: Self = Self {
symbol: "r",
factor_log10: -27,
name: "ronto",
};
/// 10⁻²⁴
pub const YOCTO: Self = Self {
symbol: "y",
factor_log10: -24,
name: "yocto",
};
/// 10⁻²¹
pub const ZEPTO: Self = Self {
symbol: "z",
factor_log10: -21,
name: "zepto",
};
/// 10⁻¹⁸
pub const ATTO: Self = Self {
symbol: "a",
factor_log10: -18,
name: "atto",
};
/// 10⁻¹⁵
pub const FEMTO: Self = Self {
symbol: "f",
factor_log10: -15,
name: "femto",
};
/// 10⁻¹²
pub const PICO: Self = Self {
symbol: "p",
factor_log10: -12,
name: "pico",
};
/// 10⁻⁹
pub const NANO: Self = Self {
symbol: "n",
factor_log10: -9,
name: "nano",
};
/// 10⁻⁶
pub const MICRO: Self = Self {
symbol: "µ",
factor_log10: -6,
name: "micro",
};
/// 10⁻³
pub const MILLI: Self = Self {
symbol: "m",
factor_log10: -3,
name: "milli",
};
/// 10⁻²
pub const CENTI: Self = Self {
symbol: "c",
factor_log10: -2,
name: "centi",
};
/// 10⁻¹
pub const DECI: Self = Self {
symbol: "d",
factor_log10: -1,
name: "deci",
};
/// 10¹
pub const DECA: Self = Self {
symbol: "da",
factor_log10: 1,
name: "deca",
};
/// 10²
pub const HECTO: Self = Self {
symbol: "h",
factor_log10: 2,
name: "hecto",
};
/// 10³
pub const KILO: Self = Self {
symbol: "k",
factor_log10: 3,
name: "kilo",
};
/// 10⁶
pub const MEGA: Self = Self {
symbol: "M",
factor_log10: 6,
name: "mega",
};
/// 10⁹
pub const GIGA: Self = Self {
symbol: "G",
factor_log10: 9,
name: "giga",
};
/// 10¹²
pub const TERA: Self = Self {
symbol: "T",
factor_log10: 12,
name: "tera",
};
/// 10¹⁵
pub const PETA: Self = Self {
symbol: "P",
factor_log10: 15,
name: "peta",
};
/// 10¹⁸
pub const EXA: Self = Self {
symbol: "E",
factor_log10: 18,
name: "exa",
};
/// 10²¹
pub const ZETTA: Self = Self {
symbol: "Z",
factor_log10: 21,
name: "zetta",
};
/// 10²⁴
pub const YOTTA: Self = Self {
symbol: "Y",
factor_log10: 24,
name: "yotta",
};
/// 10²⁷
pub const RONNA: Self = Self {
symbol: "R",
factor_log10: 27,
name: "ronna",
};
/// 10³⁰
pub const QUETTA: Self = Self {
symbol: "Q",
factor_log10: 30,
name: "quetta",
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_find_prefix_from_symbol() {
for prefix in SiPrefix::ALL {
assert_eq!(SiPrefix::from_symbol(prefix.symbol).unwrap(), prefix);
}
assert_eq!(SiPrefix::from_symbol("?"), None);
}
#[test]
fn can_strip_prefix_name_from_str() {
assert_eq!(
SiPrefix::MILLI.strip_prefix_name("millimeter"),
Some("meter")
);
assert_eq!(
SiPrefix::MILLI.strip_prefix_name("Millimeter"),
Some("meter")
);
assert_eq!(SiPrefix::MILLI.strip_prefix_name("abc"), None);
}
#[test]
fn can_strip_any_prefix_name_from_str() {
assert_eq!(
SiPrefix::strip_any_prefix_name("megameter"),
Some((&SiPrefix::MEGA, "meter"))
);
assert_eq!(
SiPrefix::strip_any_prefix_name("Megameter"),
Some((&SiPrefix::MEGA, "meter"))
);
assert_eq!(SiPrefix::strip_any_prefix_name("abc"), None);
}
#[test]
fn can_strip_prefix_symbol_from_str() {
assert_eq!(SiPrefix::MILLI.strip_prefix_symbol("mm"), Some("m"));
assert_eq!(SiPrefix::MILLI.strip_prefix_name("?"), None);
}
#[test]
fn can_strip_any_prefix_symbol_from_str() {
assert_eq!(
SiPrefix::strip_any_prefix_symbol("Mm"),
Some((&SiPrefix::MEGA, "m"))
);
assert_eq!(SiPrefix::strip_any_prefix_name("?"), None);
}
}