Use SI-prefixes that make sense for the given unit

This commit is contained in:
Juhani Krekelä 2023-05-28 18:57:49 +03:00
parent 3dee08d6d4
commit d77a84fef1
2 changed files with 133 additions and 88 deletions

View File

@ -1,66 +1,37 @@
use crate::units::{Metric, MetricQuantity}; use crate::units::{Metric, MetricQuantity};
pub fn format(quantity: MetricQuantity) -> String { pub fn format(quantity: MetricQuantity) -> String {
let (amount, prefix) = si_prefix(quantity.amount); let SiUnit(multiplier, unit) = si_unit(quantity);
let unit = abbreviation(quantity.unit); let amount = quantity.amount / multiplier;
format!("{amount} {prefix}{unit}") format!("{amount} {unit}")
} }
#[derive(Clone, Copy)] #[derive(Debug, PartialEq)]
struct SiPrefix { struct SiUnit(f64, &'static str);
prefix: &'static str,
size: f64,
}
const SI_PREFIXES: [SiPrefix; 16] = [ fn si_unit(quantity: MetricQuantity) -> SiUnit {
SiPrefix { prefix: "z", size: 0.000_000_000_000_000_000_001 }, let absolute = quantity.amount.abs();
SiPrefix { prefix: "a", size: 0.000_000_000_000_000_001 },
SiPrefix { prefix: "f", size: 0.000_000_000_000_001 },
SiPrefix { prefix: "p", size: 0.000_000_000_001 },
SiPrefix { prefix: "n", size: 0.000_000_001 },
SiPrefix { prefix: "µ", size: 0.000_001 },
SiPrefix { prefix: "m", size: 0.001 },
SiPrefix { prefix: "c", size: 0.01 }, // Included for cm
SiPrefix { prefix: "", size: 1.0 },
SiPrefix { prefix: "k", size: 1_000.0 },
SiPrefix { prefix: "M", size: 1_000_000.0 },
SiPrefix { prefix: "G", size: 1_000_000_000.0 },
SiPrefix { prefix: "T", size: 1_000_000_000_000.0 },
SiPrefix { prefix: "P", size: 1_000_000_000_000_000.0 },
SiPrefix { prefix: "E", size: 1_000_000_000_000_000_000.0 },
SiPrefix { prefix: "Z", size: 1_000_000_000_000_000_000_000.0 },
// Yotta and above cannot be represented exactly as a f64
];
fn si_prefix(amount: f64) -> (f64, &'static str) { match quantity.unit {
let absolute = amount.abs(); Metric::Metre => {
if absolute < SI_PREFIXES[0].size { if absolute >= 1000.0 {
let prefix = SI_PREFIXES[0]; return SiUnit(1000.0, "km");
return (amount / prefix.size, prefix.prefix); } else if absolute >= 1.0 {
} return SiUnit(1.0, "m");
if absolute >= SI_PREFIXES[SI_PREFIXES.len() - 1].size { } else if absolute >= 0.01 {
let prefix = SI_PREFIXES[SI_PREFIXES.len() - 1]; return SiUnit(0.01, "cm");
return (amount / prefix.size, prefix.prefix); } else {
} return SiUnit(0.001, "mm");
}
// Find the correct prefix SI_PREFIX[index] such that: }
// SI_PREFIXES[index].size ≤ absolute < SI_PREFIXES[index + 1].size Metric::Gram => {
for index in 0..SI_PREFIXES.len() { if absolute >= 1000.0 {
if SI_PREFIXES[index].size <= absolute && return SiUnit(1000.0, "kg");
absolute < SI_PREFIXES[index + 1].size { } else {
let prefix = SI_PREFIXES[index]; return SiUnit(1.0, "g");
return (amount / prefix.size, prefix.prefix); }
} }
}
unreachable!();
}
fn abbreviation(unit: Metric) -> &'static str {
match unit {
Metric::Metre => "m",
Metric::Gram => "g",
} }
} }
@ -70,47 +41,121 @@ mod test {
#[test] #[test]
fn quantities() { fn quantities() {
assert_eq!("1 m", &format(MetricQuantity { assert_eq!("0.1 mm", &format(MetricQuantity {
amount: 1.0, amount: 0.000_1,
unit: Metric::Metre, unit: Metric::Metre,
})); }));
assert_eq!("5 kg", &format(MetricQuantity { assert_eq!("-1 m", &format(MetricQuantity {
amount: 5_000.0, amount: -1.0,
unit: Metric::Metre,
}));
assert_eq!("1.5 m", &format(MetricQuantity {
amount: 1.5,
unit: Metric::Metre,
}));
assert_eq!("1000 km", &format(MetricQuantity {
amount: 1_000_000.0,
unit: Metric::Metre,
}));
assert_eq!("-5 g", &format(MetricQuantity {
amount: -5.0,
unit: Metric::Gram, unit: Metric::Gram,
})); }));
assert_eq!("25.5 cm", &format(MetricQuantity { assert_eq!("3.2 kg", &format(MetricQuantity {
amount: 0.255, amount: 3_200.0,
unit: Metric::Metre, unit: Metric::Gram,
})); }));
} }
#[test] #[test]
fn prefixes() { fn units() {
assert_eq!(si_prefix(0.000_000_000_000_000_000_0005), (0.5, "z")); assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity {
assert_eq!(si_prefix(0.000_000_000_000_000_000_001), (1.0, "z")); amount: 0.0001,
assert_eq!(si_prefix(0.000_000_000_000_000_000_01), (10.0, "z")); unit: Metric::Metre,
assert_eq!(si_prefix(0.000_000_000_000_000_00_01), (100.0, "z")); }));
assert_eq!(si_prefix(0.000_000_000_000_000_001), (1.0, "a")); assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity {
assert_eq!(si_prefix(0.000_000_000_000_001), (1.0, "f")); amount: 0.001,
assert_eq!(si_prefix(0.000_000_000_001), (1.0, "p")); unit: Metric::Metre,
assert_eq!(si_prefix(0.000_000_001), (1.0, "n")); }));
assert_eq!(si_prefix(0.000_001), (1.0, "µ")); assert_eq!(SiUnit(0.01, "cm"), si_unit(MetricQuantity {
assert_eq!(si_prefix(0.001), (1.0, "m")); amount: 0.01,
assert_eq!(si_prefix(0.01), (1.0, "c")); unit: Metric::Metre,
assert_eq!(si_prefix(1.0), (1.0, "")); }));
assert_eq!(si_prefix(1_000.0), (1.0, "k")); assert_eq!(SiUnit(0.01, "cm"), si_unit(MetricQuantity {
assert_eq!(si_prefix(1_000_000.0), (1.0, "M")); amount: 0.1,
assert_eq!(si_prefix(1_000_000_000.0), (1.0, "G")); unit: Metric::Metre,
assert_eq!(si_prefix(1_000_000_000_000.0), (1.0, "T")); }));
assert_eq!(si_prefix(1_000_000_000_000_000.0), (1.0, "P")); assert_eq!(SiUnit(1.0, "m"), si_unit(MetricQuantity {
assert_eq!(si_prefix(1_000_000_000_000_000_000.0), (1.0, "E")); amount: 1.0,
assert_eq!(si_prefix(10_000_000_000_000_000_000.0), (10.0, "E")); unit: Metric::Metre,
assert_eq!(si_prefix(100_000_000_000_000_000_000.0), (100.0, "E")); }));
assert_eq!(si_prefix(1_000_000_000_000_000_000_000.0), (1.0, "Z")); assert_eq!(SiUnit(1.0, "m"), si_unit(MetricQuantity {
assert_eq!(si_prefix(2_000_000_000_000_000_000_000.0), (2.0, "Z")); amount: 10.0,
unit: Metric::Metre,
}));
assert_eq!(SiUnit(1.0, "m"), si_unit(MetricQuantity {
amount: 100.0,
unit: Metric::Metre,
}));
assert_eq!(SiUnit(1000.0, "km"), si_unit(MetricQuantity {
amount: 1000.0,
unit: Metric::Metre,
}));
assert_eq!(SiUnit(1000.0, "km"), si_unit(MetricQuantity {
amount: 10_000.0,
unit: Metric::Metre,
}));
assert_eq!(si_prefix(-0.000_000_000_000_000_000_0005), (-0.5, "z")); assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity {
assert_eq!(si_prefix(-0.5), (-50.0, "c")); amount: -0.001,
assert_eq!(si_prefix(-2_000_000_000_000_000_000_000.0), (-2.0, "Z")); unit: Metric::Metre,
}));
assert_eq!(SiUnit(0.01, "cm"), si_unit(MetricQuantity {
amount: -0.01,
unit: Metric::Metre,
}));
assert_eq!(SiUnit(1.0, "m"), si_unit(MetricQuantity {
amount: -1.0,
unit: Metric::Metre,
}));
assert_eq!(SiUnit(1000.0, "km"), si_unit(MetricQuantity {
amount: -1000.0,
unit: Metric::Metre,
}));
assert_eq!(SiUnit(1.0, "g"), si_unit(MetricQuantity {
amount: 0.1,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1.0, "g"), si_unit(MetricQuantity {
amount: 1.0,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1.0, "g"), si_unit(MetricQuantity {
amount: 10.0,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1.0, "g"), si_unit(MetricQuantity {
amount: 100.0,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1000.0, "kg"), si_unit(MetricQuantity {
amount: 1000.0,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1000.0, "kg"), si_unit(MetricQuantity {
amount: 10_1000.0,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1.0, "g"), si_unit(MetricQuantity {
amount: -1.0,
unit: Metric::Gram,
}));
assert_eq!(SiUnit(1000.0, "kg"), si_unit(MetricQuantity {
amount: -1000.0,
unit: Metric::Gram,
}));
} }
} }

View File

@ -17,7 +17,7 @@ pub enum NonMetric {
Stone, Stone,
} }
#[derive(Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct MetricQuantity { pub struct MetricQuantity {
pub amount: f64, pub amount: f64,
pub unit: Metric, pub unit: Metric,