oxedyne/fe2o3/fe2o3_units/tests/humanise.rs
5.7 KiB, 1 run
created by r1870400018:17588, 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
| 1 | //! External-oracle tests for engineering notation. |
| 2 | //! |
| 3 | //! The expected prefixes and values come from the SI brochure's prefix table |
| 4 | //! and from the IEC binary prefixes, not from the implementation: milli is |
| 5 | //! 10^-3, pico is 10^-12, kilo is 10^3, giga is 10^9, and a kibi is 1024. So |
| 6 | //! 0.005 is 5 milli, 2.8e-10 is 280 pico, and 1024 is 1 kibi. The table stops |
| 7 | //! at atto below and exa above, and a magnitude outside that span has no |
| 8 | //! prefix at all. |
| 9 | |
| 10 | use oxedyne_fe2o3_units::{ |
| 11 | scale::{ |
| 12 | Mag, |
| 13 | Scale, |
| 14 | ScaleBasis, |
| 15 | }, |
| 16 | si::SI, |
| 17 | system::Units, |
| 18 | }; |
| 19 | |
| 20 | use oxedyne_fe2o3_core::prelude::*; |
| 21 | |
| 22 | /// Asserts two floats agree to a relative tolerance. |
| 23 | fn close(a: f64, b: f64) -> bool { |
| 24 | if b == 0.0 { |
| 25 | a.abs() < 1.0e-12 |
| 26 | } else { |
| 27 | ((a - b) / b).abs() < 1.0e-9 |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | #[test] |
| 32 | fn test_humanise_decimal_prefixes_01() -> Outcome<()> { |
| 33 | // 0.005 is 5 x 10^-3, and 10^-3 is milli. |
| 34 | let h = res!(Mag::one_decimal(0.005, 2)).humanise(); |
| 35 | assert!(close(h.val, 5.0), "got {}", h.val); |
| 36 | assert_eq!(h.prefix(), "m"); |
| 37 | // 2.8e-10 is 280 x 10^-12, and 10^-12 is pico. |
| 38 | let h = res!(Mag::one_decimal(2.8e-10, 2)).humanise(); |
| 39 | assert!(close(h.val, 280.0), "got {}", h.val); |
| 40 | assert_eq!(h.prefix(), "p"); |
| 41 | // 123456 is 123.456 x 10^3, and 10^3 is kilo; three figures keep 123. |
| 42 | let h = res!(Mag::one_decimal(123456.0, 3)).humanise(); |
| 43 | assert!(close(h.val, 123.0), "got {}", h.val); |
| 44 | assert_eq!(h.prefix(), "k"); |
| 45 | // 1234 mega is 1.234 giga. |
| 46 | let h = res!(Mag::mega(1234.0, 4)).humanise(); |
| 47 | assert!(close(h.val, 1.234), "got {}", h.val); |
| 48 | assert_eq!(h.prefix(), "G"); |
| 49 | Ok(()) |
| 50 | } |
| 51 | |
| 52 | /// A prefix names a magnitude, so a negative value takes the same one its |
| 53 | /// positive counterpart does and keeps its sign in the value. |
| 54 | #[test] |
| 55 | fn test_humanise_negative_01() -> Outcome<()> { |
| 56 | let h = res!(Mag::one_decimal(-0.005, 2)).humanise(); |
| 57 | assert!(close(h.val, -5.0), "got {}", h.val); |
| 58 | assert_eq!(h.prefix(), "m"); |
| 59 | let h = res!(Mag::one_decimal(-123456.0, 3)).humanise(); |
| 60 | assert!(close(h.val, -123.0), "got {}", h.val); |
| 61 | assert_eq!(h.prefix(), "k"); |
| 62 | let h = res!(Mag::mega(-1234.0, 4)).humanise(); |
| 63 | assert!(close(h.val, -1.234), "got {}", h.val); |
| 64 | assert_eq!(h.prefix(), "G"); |
| 65 | Ok(()) |
| 66 | } |
| 67 | |
| 68 | /// A zero has no scale to find, and must come back as a zero with no prefix |
| 69 | /// rather than as a not-a-number. |
| 70 | #[test] |
| 71 | fn test_humanise_zero_01() -> Outcome<()> { |
| 72 | let h = res!(Mag::one_decimal(0.0, 3)).humanise(); |
| 73 | assert_eq!(h.val, 0.0); |
| 74 | assert_eq!(h.prefix(), ""); |
| 75 | let h = res!(Mag::milli(0.0, 3)).humanise(); |
| 76 | assert_eq!(h.val, 0.0); |
| 77 | assert_eq!(h.prefix(), ""); |
| 78 | Ok(()) |
| 79 | } |
| 80 | |
| 81 | /// The prefix table runs from atto to exa. A magnitude beyond either end is |
| 82 | /// returned unscaled and unprefixed, and above all does not bring the process |
| 83 | /// down. |
| 84 | #[test] |
| 85 | fn test_humanise_beyond_the_prefix_table_01() -> Outcome<()> { |
| 86 | for v in [1.0e21f64, -1.0e21, 1.0e-21, -1.0e-21, 1.0e30, 1.0e-30] { |
| 87 | let h = res!(Mag::one_decimal(v, 3)).humanise(); |
| 88 | assert_eq!(h.prefix(), "", "{:e} should have no prefix", v); |
| 89 | assert!(close(h.val, v), "{:e} came back as {:e}", v, h.val); |
| 90 | } |
| 91 | Ok(()) |
| 92 | } |
| 93 | |
| 94 | /// Every prefix in the decimal table is reachable, and each is a thousandfold |
| 95 | /// step from the last. |
| 96 | #[test] |
| 97 | fn test_humanise_reaches_every_prefix_01() -> Outcome<()> { |
| 98 | let want = [ |
| 99 | (-18i32, "a"), (-15, "f"), (-12, "p"), (-9, "n"), (-6, "\u{00b5}"), |
| 100 | (-3, "m"), (0, ""), (3, "k"), (6, "M"), (9, "G"), (12, "T"), |
| 101 | (15, "P"), (18, "E"), |
| 102 | ]; |
| 103 | for (exp, prefix) in want { |
| 104 | let v = match format!("2.5e{}", exp).parse::<f64>() { |
| 105 | Ok(v) => v, |
| 106 | Err(_) => continue, |
| 107 | }; |
| 108 | let h = res!(Mag::one_decimal(v, 2)).humanise(); |
| 109 | assert_eq!(h.prefix(), prefix, "2.5e{} should read in {}", exp, prefix); |
| 110 | assert!(close(h.val, 2.5), "2.5e{} came back as {}", exp, h.val); |
| 111 | } |
| 112 | Ok(()) |
| 113 | } |
| 114 | |
| 115 | /// A kibi is 1024, so 1024 bytes read in binary is 1 KiB. |
| 116 | #[test] |
| 117 | fn test_humanise_binary_prefixes_01() -> Outcome<()> { |
| 118 | let h = res!(Units::<SI>::bytes(1024.0, 4)).humanise(); |
| 119 | assert!(close(h.val(), 1.0), "got {}", h.val()); |
| 120 | assert_eq!(h.prefix(), "Ki"); |
| 121 | assert_eq!(h.symbol(), "B"); |
| 122 | // A mebi is 1024^2. |
| 123 | let h = res!(Units::<SI>::bytes(1024.0 * 1024.0, 4)).humanise(); |
| 124 | assert!(close(h.val(), 1.0), "got {}", h.val()); |
| 125 | assert_eq!(h.prefix(), "Mi"); |
| 126 | Ok(()) |
| 127 | } |
| 128 | |
| 129 | /// A prefix lookup that has no entry reports it, rather than bringing the |
| 130 | /// process down. |
| 131 | #[test] |
| 132 | fn test_dec_exp_lookup_reports_a_miss_01() -> Outcome<()> { |
| 133 | let dec = Scale::One(ScaleBasis::Decimal); |
| 134 | assert!(dec.dec_exp_lookup(3).is_ok()); |
| 135 | assert!(dec.dec_exp_lookup(21).is_err()); |
| 136 | assert!(dec.dec_exp_lookup(-21).is_err()); |
| 137 | assert!(dec.dec_exp_lookup(4).is_err()); |
| 138 | let bin = Scale::One(ScaleBasis::Binary); |
| 139 | assert!(bin.dec_exp_lookup(3).is_ok()); |
| 140 | assert!(bin.dec_exp_lookup(-3).is_err()); |
| 141 | Ok(()) |
| 142 | } |
| 143 | |
| 144 | /// Normalising puts the leading digit in the units place and reports the |
| 145 | /// decade it came from, for either sign. |
| 146 | #[test] |
| 147 | fn test_normalise_01() -> Outcome<()> { |
| 148 | let (sig, exp) = res!(Mag::one_decimal(1234.0, 4)).normalise(); |
| 149 | assert!(close(sig, 1.234), "got {}", sig); |
| 150 | assert_eq!(exp, 3); |
| 151 | let (sig, exp) = res!(Mag::one_decimal(-1234.0, 4)).normalise(); |
| 152 | assert!(close(sig, -1.234), "got {}", sig); |
| 153 | assert_eq!(exp, 3); |
| 154 | // 0.0475 is 4.75 x 10^-2, and two figures round the tie away from zero. |
| 155 | let (sig, exp) = res!(Mag::one_decimal(-0.0475, 2)).normalise(); |
| 156 | assert!(close(sig, -4.8), "got {}", sig); |
| 157 | assert_eq!(exp, -2); |
| 158 | // A zero has no decade. |
| 159 | let (sig, exp) = res!(Mag::one_decimal(0.0, 3)).normalise(); |
| 160 | assert_eq!(sig, 0.0); |
| 161 | assert_eq!(exp, 0); |
| 162 | Ok(()) |
| 163 | } |