Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_units/src/scale.rs

22.5 KiB, 70 runs

created by r1870400018:1235, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use oxedyne_fe2o3_core::prelude::*;
2use oxedyne_fe2o3_num::float;
3
4#[derive(Clone, Debug, Eq, PartialEq)]
5pub enum ScaleBasis {
6 Decimal,
7 Binary
8}
9
10impl ScaleBasis {
11
12 // Decimal floating point form uses base 10 (e.g. 1.0 x 10^3), so no factor required
13 pub const DEC_LOG_FACTOR: f64 = 1.0;
14 pub const DEC_BASE: f64 = 10.0;
15 // For binary decimal form using exponents which are a factor of three for engineering
16 // notation, on the other hand, we can think of the base as being X, e.g. 1.0 x X^3 = 1024.
17 // This gives X = 10^(log10(1024)/3) = 10.07936.. Next we want to be able to numbers into this
18 // new basis. If a = X^b, b = logX(a) [1] and log10(a) = log10(X^b) = b*log10(X) [2].
19 // Substituting the b in [1] into [2] we get logX(a) = log10(a)/log10(X). The factor below is
20 // log10(X). Using the value of X above, this can be simplified to log10(1024)/3, allowing us
21 // to use the existing log10 functionality. Exponents calculated this way will differ by +/-3
22 // if the numbers they form are in the ratio 1024. For example
23 // 1024 = 1.0 x X^3, 1024^2 = 1.0 x X^6, etc.
24 pub const BIN_LOG_FACTOR: f64 = 1.0034333188799373;
25 pub const BIN_BASE: f64 = 10.0793683991589853;
26
27 pub fn log_factor(&self) -> f64 {
28 match self {
29 Self::Decimal => Self::DEC_LOG_FACTOR,
30 Self::Binary => Self::BIN_LOG_FACTOR,
31 }
32 }
33
34 pub fn base(&self) -> f64 {
35 match self {
36 Self::Decimal => Self::DEC_BASE,
37 Self::Binary => Self::BIN_BASE,
38 }
39 }
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
43pub enum Scale {
44 // decimal
45 Atto,
46 Femto,
47 Pico,
48 Nano,
49 Micro,
50 Milli,
51 Centi,
52 Deci,
53 One(ScaleBasis),
54 Deca,
55 Hecto,
56 Kilo,
57 Mega,
58 Giga,
59 Tera,
60 Peta,
61 Exa,
62 // binary
63 Kibi,
64 Mebi,
65 Gibi,
66 Tebi,
67 Pebi,
68 Exbi,
69}
70
71impl Scale {
72
73 // decimal
74 const ATTO_SCALE: u64 = 1_000_000_000_000_000_000;
75 const FEMTO_SCALE: u64 = 1_000_000_000_000_000;
76 const PICO_SCALE: u64 = 1_000_000_000_000;
77 const NANO_SCALE: u64 = 1_000_000_000;
78 const MICRO_SCALE: u64 = 1_000_000;
79 const MILLI_SCALE: u64 = 1_000;
80 const CENTI_SCALE: u64 = 100;
81 const DECI_SCALE: u64 = 10;
82 const ONE_SCALE: u64 = 1;
83 const DECA_SCALE: u64 = 10;
84 const HECTO_SCALE: u64 = 100;
85 const KILO_SCALE: u64 = 1_000;
86 const MEGA_SCALE: u64 = 1_000_000;
87 const GIGA_SCALE: u64 = 1_000_000_000;
88 const TERA_SCALE: u64 = 1_000_000_000_000;
89 const PETA_SCALE: u64 = 1_000_000_000_000_000;
90 const EXA_SCALE: u64 = 1_000_000_000_000_000_000;
91 // binary 18_446_744_073_709_551_615 u64 max
92 const KIBI_SCALE: u64 = 1_024;
93 const MEBI_SCALE: u64 = 1_048_576;
94 const GIBI_SCALE: u64 = 1_073_741_824;
95 const TEBI_SCALE: u64 = 1_099_511_627_776;
96 const PEBI_SCALE: u64 = 1_125_899_906_842_624;
97 const EXBI_SCALE: u64 = 1_152_921_504_606_846_976;
98
99 // decimal
100 const ATTO_DEC_EXP: f64 = -18.0;
101 const FEMTO_DEC_EXP: f64 = -15.0;
102 const PICO_DEC_EXP: f64 = -12.0;
103 const NANO_DEC_EXP: f64 = -9.0;
104 const MICRO_DEC_EXP: f64 = -6.0;
105 const MILLI_DEC_EXP: f64 = -3.0;
106 const CENTI_DEC_EXP: f64 = -2.0;
107 const DECI_DEC_EXP: f64 = -1.0;
108 const ONE_DEC_EXP: f64 = 0.0;
109 const DECA_DEC_EXP: f64 = 1.0;
110 const HECTO_DEC_EXP: f64 = 2.0;
111 const KILO_DEC_EXP: f64 = 3.0;
112 const MEGA_DEC_EXP: f64 = 6.0;
113 const GIGA_DEC_EXP: f64 = 9.0;
114 const TERA_DEC_EXP: f64 = 12.0;
115 const PETA_DEC_EXP: f64 = 15.0;
116 const EXA_DEC_EXP: f64 = 18.0;
117 // binary
118 const KIBI_DEC_EXP: f64 = 3.01029995664;
119 const MEBI_DEC_EXP: f64 = 6.02059991328;
120 const GIBI_DEC_EXP: f64 = 9.03089986992;
121 const TEBI_DEC_EXP: f64 = 12.0411998266;
122 const PEBI_DEC_EXP: f64 = 15.0514997832;
123 const EXBI_DEC_EXP: f64 = 18.0617997398;
124
125 // decimal
126 const ATTO_PREFIX: &'static str = "a";
127 const FEMTO_PREFIX: &'static str = "f";
128 const PICO_PREFIX: &'static str = "p";
129 const NANO_PREFIX: &'static str = "n";
130 const MICRO_PREFIX: &'static str = "\u{00b5}";
131 const MILLI_PREFIX: &'static str = "m";
132 const CENTI_PREFIX: &'static str = "c";
133 const DECI_PREFIX: &'static str = "d";
134 const ONE_PREFIX: &'static str = "";
135 const DECA_PREFIX: &'static str = "da";
136 const HECTO_PREFIX: &'static str = "h";
137 const KILO_PREFIX: &'static str = "k";
138 const MEGA_PREFIX: &'static str = "M";
139 const GIGA_PREFIX: &'static str = "G";
140 const TERA_PREFIX: &'static str = "T";
141 const PETA_PREFIX: &'static str = "P";
142 const EXA_PREFIX: &'static str = "E";
143 // binary
144 const KIBI_PREFIX: &'static str = "Ki";
145 const MEBI_PREFIX: &'static str = "Mi";
146 const GIBI_PREFIX: &'static str = "Gi";
147 const TEBI_PREFIX: &'static str = "Ti";
148 const PEBI_PREFIX: &'static str = "Pi";
149 const EXBI_PREFIX: &'static str = "Ei";
150
151 pub fn basis(&self) -> ScaleBasis {
152 match self {
153 Self::One(b) => b.clone(),
154 Self::Kibi |
155 Self::Mebi |
156 Self::Gibi |
157 Self::Tebi |
158 Self::Pebi |
159 Self::Exbi => ScaleBasis::Binary,
160 _ => ScaleBasis::Decimal,
161 }
162 }
163
164 pub fn as_u64(&self) -> u64 {
165 match self {
166 // decimal
167 Self::Atto => Self::ATTO_SCALE,
168 Self::Femto => Self::FEMTO_SCALE,
169 Self::Pico => Self::PICO_SCALE,
170 Self::Nano => Self::NANO_SCALE,
171 Self::Micro => Self::MICRO_SCALE,
172 Self::Milli => Self::MILLI_SCALE,
173 Self::Centi => Self::CENTI_SCALE,
174 Self::Deci => Self::DECI_SCALE,
175 Self::One(_) => Self::ONE_SCALE,
176 Self::Deca => Self::DECA_SCALE,
177 Self::Hecto => Self::HECTO_SCALE,
178 Self::Kilo => Self::KILO_SCALE,
179 Self::Mega => Self::MEGA_SCALE,
180 Self::Giga => Self::GIGA_SCALE,
181 Self::Tera => Self::TERA_SCALE,
182 Self::Peta => Self::PETA_SCALE,
183 Self::Exa => Self::EXA_SCALE,
184 // binary
185 Self::Kibi => Self::KIBI_SCALE,
186 Self::Mebi => Self::MEBI_SCALE,
187 Self::Gibi => Self::GIBI_SCALE,
188 Self::Tebi => Self::TEBI_SCALE,
189 Self::Pebi => Self::PEBI_SCALE,
190 Self::Exbi => Self::EXBI_SCALE,
191 //
192 //_ => unimplemented!(),
193 }
194 }
195
196 pub fn dec_exp(&self) -> f64 {
197 match self {
198 // decimal
199 Self::Atto => Self::ATTO_DEC_EXP,
200 Self::Femto => Self::FEMTO_DEC_EXP,
201 Self::Pico => Self::PICO_DEC_EXP,
202 Self::Nano => Self::NANO_DEC_EXP,
203 Self::Micro => Self::MICRO_DEC_EXP,
204 Self::Milli => Self::MILLI_DEC_EXP,
205 Self::Centi => Self::CENTI_DEC_EXP,
206 Self::Deci => Self::DECI_DEC_EXP,
207 Self::One(_) => Self::ONE_DEC_EXP,
208 Self::Deca => Self::DECA_DEC_EXP,
209 Self::Hecto => Self::HECTO_DEC_EXP,
210 Self::Kilo => Self::KILO_DEC_EXP,
211 Self::Mega => Self::MEGA_DEC_EXP,
212 Self::Giga => Self::GIGA_DEC_EXP,
213 Self::Tera => Self::TERA_DEC_EXP,
214 Self::Peta => Self::PETA_DEC_EXP,
215 Self::Exa => Self::EXA_DEC_EXP,
216 // binary
217 Self::Kibi => Self::KIBI_DEC_EXP,
218 Self::Mebi => Self::MEBI_DEC_EXP,
219 Self::Gibi => Self::GIBI_DEC_EXP,
220 Self::Tebi => Self::TEBI_DEC_EXP,
221 Self::Pebi => Self::PEBI_DEC_EXP,
222 Self::Exbi => Self::EXBI_DEC_EXP,
223 //
224 //_ => unimplemented!(),
225 }
226 }
227
228 pub fn dec_exp_lookup(&self, exp: i32) -> Outcome<Self> {
229 Ok(match self.basis() {
230 ScaleBasis::Decimal => {
231 match exp {
232 -18 => Self::Atto,
233 -15 => Self::Femto,
234 -12 => Self::Pico,
235 -9 => Self::Nano,
236 -6 => Self::Micro,
237 -3 => Self::Milli,
238 -2 => Self::Centi,
239 -1 => Self::Deci,
240 0 => Self::One(ScaleBasis::Decimal),
241 1 => Self::Deca,
242 2 => Self::Hecto,
243 3 => Self::Kilo,
244 6 => Self::Mega,
245 9 => Self::Giga,
246 12 => Self::Tera,
247 15 => Self::Peta,
248 18 => Self::Exa,
249 _ => return Err(err!(
250 "No decimal prefix has the exponent {}.", exp;
251 Input, Invalid, Missing)),
252 }
253 },
254 ScaleBasis::Binary => {
255 match exp {
256 0 => Self::One(ScaleBasis::Binary),
257 3 => Self::Kibi,
258 6 => Self::Mebi,
259 9 => Self::Gibi,
260 12 => Self::Tebi,
261 15 => Self::Pebi,
262 18 => Self::Exbi,
263 _ => return Err(err!(
264 "No binary prefix has the exponent {}.", exp;
265 Input, Invalid, Missing)),
266 }
267 },
268 })
269 }
270
271 pub fn prefix(&self) -> &'static str {
272 match self {
273 // decimal
274 Self::Atto => Self::ATTO_PREFIX,
275 Self::Femto => Self::FEMTO_PREFIX,
276 Self::Pico => Self::PICO_PREFIX,
277 Self::Nano => Self::NANO_PREFIX,
278 Self::Micro => Self::MICRO_PREFIX,
279 Self::Milli => Self::MILLI_PREFIX,
280 Self::Centi => Self::CENTI_PREFIX,
281 Self::Deci => Self::DECI_PREFIX,
282 Self::One(_) => Self::ONE_PREFIX,
283 Self::Deca => Self::DECA_PREFIX,
284 Self::Hecto => Self::HECTO_PREFIX,
285 Self::Kilo => Self::KILO_PREFIX,
286 Self::Mega => Self::MEGA_PREFIX,
287 Self::Giga => Self::GIGA_PREFIX,
288 Self::Tera => Self::TERA_PREFIX,
289 Self::Peta => Self::PETA_PREFIX,
290 Self::Exa => Self::EXA_PREFIX,
291 // binary
292 Self::Kibi => Self::KIBI_PREFIX,
293 Self::Mebi => Self::MEBI_PREFIX,
294 Self::Gibi => Self::GIBI_PREFIX,
295 Self::Tebi => Self::TEBI_PREFIX,
296 Self::Pebi => Self::PEBI_PREFIX,
297 Self::Exbi => Self::EXBI_PREFIX,
298 //_ => unimplemented!(),
299 }
300 }
301}
302
303#[derive(Clone, Debug)]
304pub struct Mag {
305 pub val: f64,
306 pub scale: Scale,
307 pub sf: u8, // significant figures
308 pub zero: bool,
309}
310
311impl Mag {
312
313 pub fn new(val: f64, scale: Scale, sf: u8) -> Outcome<Self> {
314 if sf == 0 {
315 return Err(err!(
316 "Number of significant figures must be > 0.";
317 Input, Invalid));
318 }
319 Ok(Self {
320 val: val,
321 scale: scale,
322 sf: sf,
323 zero: val.abs() < f64::MIN_POSITIVE,
324 })
325 }
326
327 fn derived(&self, val: f64, scale: Scale) -> Self {
328 Self {
329 val,
330 scale,
331 sf: self.sf,
332 zero: val.abs() < f64::MIN_POSITIVE,
333 }
334 }
335
336 // decimal
337 pub fn atto(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Atto , sf) }
338 pub fn femto(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Femto, sf) }
339 pub fn pico(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Pico , sf) }
340 pub fn nano(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Nano , sf) }
341 pub fn micro(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Micro, sf) }
342 pub fn milli(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Milli, sf) }
343 pub fn centi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Centi, sf) }
344 pub fn deci(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Deci , sf) }
345 pub fn deca(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Deca , sf) }
346 pub fn hecto(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Hecto, sf) }
347 pub fn kilo(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Kilo , sf) }
348 pub fn mega(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Mega , sf) }
349 pub fn giga(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Giga , sf) }
350 pub fn tera(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Tera , sf) }
351 pub fn peta(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Peta , sf) }
352 pub fn exa(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Exa , sf) }
353
354 // binary
355 pub fn kibi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Kibi , sf) }
356 pub fn mebi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Mebi , sf) }
357 pub fn gibi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Gibi , sf) }
358 pub fn tebi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Tebi , sf) }
359 pub fn pebi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Pebi , sf) }
360 pub fn exbi(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::Exbi , sf) }
361
362 pub fn one_decimal(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::One(ScaleBasis::Decimal), sf) }
363 pub fn one_binary(val: f64, sf: u8) -> Outcome<Self> { Self::new(val, Scale::One(ScaleBasis::Binary), sf)
364 }
365
366 pub fn basis(&self) -> ScaleBasis {
367 self.scale.basis()
368 }
369
370 pub fn prefix(&self) -> &'static str {
371 self.scale.prefix()
372 }
373
374 /// Adjust value to make the scale `Scale::One`.
375 pub fn unitise(&self) -> Self {
376 self.derived(
377 if self.zero {
378 0.0f64
379 } else {
380 self.val * (10u64 as f64).powf(self.scale.dec_exp())
381 },
382 Scale::One(self.basis()),
383 )
384 }
385
386 /// Return a value in the range (-10, -1) or (1, 10) rounded to the required number of
387 /// significant figures, and the decimal exponent.
388 ///
389 /// A logarithm has nothing to say about a negative number, so the exponent
390 /// comes from the magnitude and the sign is carried by the value. A zero,
391 /// or a value that is not finite, has no exponent and is returned with one
392 /// of zero.
393 pub fn normalise(&self) -> (f64, i32) {
394 if self.zero || !self.val.is_finite() {
395 return (self.val, 0);
396 }
397 let exp = self.val.abs().log10().floor() as i32;
398 let sig = float::round_to_sf(float::mul_pow10(self.val, -exp), self.sf);
399 (sig, exp)
400 }
401
402 /// Implements engineering notation (i.e that is, use of standard decimal exponent), and rounds
403 /// the value according to the required number of significant figures.
404 ///
405 /// The prefix is chosen from the magnitude, so a negative value takes the
406 /// same prefix as its positive counterpart. A magnitude the prefix table
407 /// cannot reach, below atto or above exa, is returned unscaled rather than
408 /// mislabelled.
409 pub fn humanise(&self) -> Self {
410 let n = self.unitise();
411 if self.zero {
412 return n;
413 }
414 let expbase = n.val.abs().log10() / self.basis().log_factor();
415 let engexp = (3.0 * (expbase / 3.0).floor()) as i32;
416 match self.scale.dec_exp_lookup(engexp) {
417 Ok(newscale) => {
418 let newval = n.val / (self.basis().base().powi(engexp));
419 n.derived(float::round_to_sf(newval, self.sf), newscale)
420 },
421 Err(_) => n,
422 }
423 }
424
425}
426
427impl PartialEq for Mag {
428 fn eq(&self, other: &Self) -> bool {
429 // Fold each prefix into its value, which is the only way a mega and a
430 // kilo can be put side by side at all.
431 let a = self.unitise().val;
432 let b = other.unitise().val;
433
434 if a.is_nan() || b.is_nan() {
435 return false;
436 }
437 if a == 0.0 || b == 0.0 {
438 return a == 0.0 && b == 0.0;
439 }
440 if a.is_sign_negative() != b.is_sign_negative() {
441 return false;
442 }
443 if a.is_infinite() || b.is_infinite() {
444 return a == b;
445 }
446
447 // A count of zero can only arrive through the public fields, since the
448 // constructor refuses it; one figure is the least a comparison can be
449 // made at, and seventeen is the most an f64 carries.
450 let sf = self.sf.min(other.sf).clamp(1, 17);
451 significand(a, sf) == significand(b, sf)
452 }
453}
454
455fn significand(val: f64, sf: u8) -> (i32, i64) {
456 let mag = val.abs();
457 let mut exp = mag.log10().floor() as i32;
458 // A logarithm is not exact, so the exponent is checked against the value it
459 // is meant to describe and nudged if it names the wrong decade.
460 let lead = float::mul_pow10(mag, -exp);
461 if lead >= 10.0 {
462 exp += 1;
463 } else if lead < 1.0 {
464 exp -= 1;
465 }
466 let mut sig = float::mul_pow10(mag, (sf as i32) - 1 - exp).round() as i64;
467 // Rounding can carry into the next decade: 9.99 to two figures is 10.
468 let ceiling = 10i64.pow(sf as u32);
469 if sig >= ceiling {
470 sig /= 10;
471 exp += 1;
472 }
473 (exp, sig)
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use crate::{
480 si::SI,
481 system::Units,
482 };
483
484 #[test]
485 fn test_simple_mag_one_01() -> Outcome<()> {
486 let a = res!(Mag::one_decimal(123456.0, 3));
487 let b = res!(Mag::kilo(123.0, 3));
488 assert_eq!(a.humanise(), b);
489 Ok(())
490 }
491
492 #[test]
493 fn test_simple_mag_dec_01() -> Outcome<()> {
494 let a = res!(Mag::mega(1234.0, 4));
495 let b = res!(Mag::giga(1.234, 4));
496 assert_eq!(a, b);
497 Ok(())
498 }
499
500 #[test]
501 fn test_simple_mag_dec_02() -> Outcome<()> {
502 let a = res!(Mag::mega(1234.0, 4));
503 let h = a.humanise();
504 let b = res!(Mag::giga(1.234, 4));
505 assert_eq!(b, h);
506 Ok(())
507 }
508
509 #[test]
510 fn test_simple_mag_dec_03() -> Outcome<()> {
511 let a = res!(Mag::micro(1234.0, 4));
512 let b = res!(Mag::milli(1.234, 4));
513 assert_eq!(a, b);
514 Ok(())
515 }
516
517 #[test]
518 fn test_simple_mag_bin_01() -> Outcome<()> {
519 let a = res!(Mag::kibi(1.0, 4));
520 let b = res!(Mag::one_binary(1024.0, 4));
521 assert_eq!(a.unitise(), b);
522 assert_eq!(a, b.humanise());
523 Ok(())
524 }
525
526 #[test]
527 fn test_simple_units_01() -> Outcome<()> {
528 let a = Units::new(res!(Mag::one_binary(1024.0, 4)), SI::bytes());
529 let b = a.humanise();
530 assert_eq!(a, a.unitise());
531 assert_eq!(a, b.unitise());
532 Ok(())
533 }
534
535 #[test]
536 fn test_simple_units_02() -> Outcome<()> {
537 let a = res!(Units::<SI>::bytes(1024.0, 4));
538 let b = a.humanise();
539 assert_eq!(a, a.unitise());
540 assert_eq!(a, b.unitise());
541 Ok(())
542 }
543
544 #[test]
545 fn test_a_prefix_apart_is_not_the_same_quantity_00() -> Outcome<()> {
546 assert_ne!(res!(Mag::mega(1.234, 4)), res!(Mag::giga(1.234, 4)));
547 assert_ne!(res!(Mag::milli(5.0, 3)), res!(Mag::micro(5.0, 3)));
548 assert_ne!(res!(Mag::kilo(1.0, 3)), res!(Mag::one_decimal(1.0, 3)));
549 Ok(())
550 }
551
552 #[test]
553 fn test_one_quantity_under_three_prefixes_is_one_quantity_00() -> Outcome<()> {
554 let m = res!(Mag::mega(1.0, 4));
555 assert_eq!(m, res!(Mag::kilo(1000.0, 4)));
556 assert_eq!(m, res!(Mag::one_decimal(1_000_000.0, 4)));
557 assert_eq!(res!(Mag::milli(1234.0, 4)), res!(Mag::one_decimal(1.234, 4)));
558 Ok(())
559 }
560
561 #[test]
562 fn test_zero_equals_zero_00() -> Outcome<()> {
563 assert_eq!(res!(Mag::one_decimal(0.0, 3)), res!(Mag::one_decimal(0.0, 3)));
564 assert_eq!(res!(Mag::mega(0.0, 3)), res!(Mag::nano(0.0, 5)));
565 assert_ne!(res!(Mag::one_decimal(0.0, 3)), res!(Mag::one_decimal(1.0, 3)));
566 Ok(())
567 }
568
569 #[test]
570 fn test_negatives_compare_by_magnitude_00() -> Outcome<()> {
571 assert_eq!(res!(Mag::milli(-5.0, 3)), res!(Mag::milli(-5.0, 3)));
572 assert_eq!(res!(Mag::milli(-5.0, 3)), res!(Mag::micro(-5000.0, 3)));
573 assert_ne!(res!(Mag::milli(-5.0, 3)), res!(Mag::milli(5.0, 3)));
574 assert_ne!(res!(Mag::mega(-1.234, 4)), res!(Mag::giga(-1.234, 4)));
575 Ok(())
576 }
577
578 #[test]
579 fn test_the_comparison_is_symmetric_00() -> Outcome<()> {
580 let coarse = res!(Mag::kilo(1.0, 1));
581 let fine = res!(Mag::kilo(1.04, 3));
582 assert_eq!(coarse, fine);
583 assert_eq!(fine, coarse);
584 let apart = res!(Mag::kilo(1.6, 3));
585 assert_ne!(coarse, apart);
586 assert_ne!(apart, coarse);
587 Ok(())
588 }
589
590 #[test]
591 fn test_a_rounding_carry_moves_the_exponent_00() -> Outcome<()> {
592 assert_eq!(res!(Mag::kilo(9.99, 2)), res!(Mag::kilo(10.0, 2)));
593 assert_eq!(res!(Mag::one_decimal(0.0999, 2)), res!(Mag::one_decimal(0.1, 2)));
594 Ok(())
595 }
596
597 #[test]
598 fn test_a_zero_figure_count_does_not_wrap_00() -> Outcome<()> {
599 let mut a = res!(Mag::kilo(1.234, 4));
600 a.sf = 0;
601 let b = res!(Mag::kilo(1.2, 2));
602 // One figure is the floor, and 1.234 k and 1.2 k are both 1 k at it.
603 assert_eq!(a, b);
604 assert_ne!(a, res!(Mag::mega(1.234, 4)));
605 Ok(())
606 }
607
608 #[test]
609 fn test_a_value_that_is_not_a_number_equals_nothing_00() -> Outcome<()> {
610 let nan = res!(Mag::one_decimal(f64::NAN, 3));
611 assert_ne!(nan, res!(Mag::one_decimal(f64::NAN, 3)));
612 assert_ne!(nan, res!(Mag::one_decimal(1.0, 3)));
613 let inf = res!(Mag::one_decimal(f64::INFINITY, 3));
614 assert_eq!(inf, res!(Mag::one_decimal(f64::INFINITY, 3)));
615 assert_ne!(inf, res!(Mag::one_decimal(f64::NEG_INFINITY, 3)));
616 Ok(())
617 }
618}