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
| 1 | use oxedyne_fe2o3_core::prelude::*; |
| 2 | use oxedyne_fe2o3_num::float; |
| 3 | |
| 4 | #[derive(Clone, Debug, Eq, PartialEq)] |
| 5 | pub enum ScaleBasis { |
| 6 | Decimal, |
| 7 | Binary |
| 8 | } |
| 9 | |
| 10 | impl 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)] |
| 43 | pub 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 | |
| 71 | impl 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)] |
| 304 | pub struct Mag { |
| 305 | pub val: f64, |
| 306 | pub scale: Scale, |
| 307 | pub sf: u8, // significant figures |
| 308 | pub zero: bool, |
| 309 | } |
| 310 | |
| 311 | impl 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 | |
| 427 | impl 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 | |
| 455 | fn 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)] |
| 477 | mod 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 | } |