diff --git a/src/format.rs b/src/format.rs index e4b5d4a..e529d6e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,66 +1,37 @@ use crate::units::{Metric, MetricQuantity}; pub fn format(quantity: MetricQuantity) -> String { - let (amount, prefix) = si_prefix(quantity.amount); - let unit = abbreviation(quantity.unit); + let SiUnit(multiplier, unit) = si_unit(quantity); + let amount = quantity.amount / multiplier; - format!("{amount} {prefix}{unit}") + format!("{amount} {unit}") } -#[derive(Clone, Copy)] -struct SiPrefix { - prefix: &'static str, - size: f64, -} +#[derive(Debug, PartialEq)] +struct SiUnit(f64, &'static str); -const SI_PREFIXES: [SiPrefix; 16] = [ - SiPrefix { prefix: "z", size: 0.000_000_000_000_000_000_001 }, - 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_unit(quantity: MetricQuantity) -> SiUnit { + let absolute = quantity.amount.abs(); -fn si_prefix(amount: f64) -> (f64, &'static str) { - let absolute = amount.abs(); - if absolute < SI_PREFIXES[0].size { - let prefix = SI_PREFIXES[0]; - return (amount / prefix.size, prefix.prefix); - } - if absolute >= SI_PREFIXES[SI_PREFIXES.len() - 1].size { - let prefix = SI_PREFIXES[SI_PREFIXES.len() - 1]; - return (amount / prefix.size, prefix.prefix); - } - - // Find the correct prefix SI_PREFIX[index] such that: - // SI_PREFIXES[index].size ≤ absolute < SI_PREFIXES[index + 1].size - for index in 0..SI_PREFIXES.len() { - if SI_PREFIXES[index].size <= absolute && - absolute < SI_PREFIXES[index + 1].size { - let prefix = SI_PREFIXES[index]; - return (amount / prefix.size, prefix.prefix); + match quantity.unit { + Metric::Metre => { + if absolute >= 1000.0 { + return SiUnit(1000.0, "km"); + } else if absolute >= 1.0 { + return SiUnit(1.0, "m"); + } else if absolute >= 0.01 { + return SiUnit(0.01, "cm"); + } else { + return SiUnit(0.001, "mm"); + } + } + Metric::Gram => { + if absolute >= 1000.0 { + return SiUnit(1000.0, "kg"); + } else { + return SiUnit(1.0, "g"); + } } - } - - unreachable!(); -} - -fn abbreviation(unit: Metric) -> &'static str { - match unit { - Metric::Metre => "m", - Metric::Gram => "g", } } @@ -70,47 +41,121 @@ mod test { #[test] fn quantities() { - assert_eq!("1 m", &format(MetricQuantity { - amount: 1.0, + assert_eq!("0.1 mm", &format(MetricQuantity { + amount: 0.000_1, unit: Metric::Metre, })); - assert_eq!("5 kg", &format(MetricQuantity { - amount: 5_000.0, + assert_eq!("-1 m", &format(MetricQuantity { + 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, })); - assert_eq!("25.5 cm", &format(MetricQuantity { - amount: 0.255, - unit: Metric::Metre, + assert_eq!("3.2 kg", &format(MetricQuantity { + amount: 3_200.0, + unit: Metric::Gram, })); } #[test] - fn prefixes() { - assert_eq!(si_prefix(0.000_000_000_000_000_000_0005), (0.5, "z")); - assert_eq!(si_prefix(0.000_000_000_000_000_000_001), (1.0, "z")); - assert_eq!(si_prefix(0.000_000_000_000_000_000_01), (10.0, "z")); - 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!(si_prefix(0.000_000_000_000_001), (1.0, "f")); - assert_eq!(si_prefix(0.000_000_000_001), (1.0, "p")); - assert_eq!(si_prefix(0.000_000_001), (1.0, "n")); - assert_eq!(si_prefix(0.000_001), (1.0, "µ")); - assert_eq!(si_prefix(0.001), (1.0, "m")); - assert_eq!(si_prefix(0.01), (1.0, "c")); - assert_eq!(si_prefix(1.0), (1.0, "")); - assert_eq!(si_prefix(1_000.0), (1.0, "k")); - assert_eq!(si_prefix(1_000_000.0), (1.0, "M")); - assert_eq!(si_prefix(1_000_000_000.0), (1.0, "G")); - 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!(si_prefix(1_000_000_000_000_000_000.0), (1.0, "E")); - assert_eq!(si_prefix(10_000_000_000_000_000_000.0), (10.0, "E")); - 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!(si_prefix(2_000_000_000_000_000_000_000.0), (2.0, "Z")); + fn units() { + assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity { + amount: 0.0001, + unit: Metric::Metre, + })); + assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity { + amount: 0.001, + unit: Metric::Metre, + })); + assert_eq!(SiUnit(0.01, "cm"), si_unit(MetricQuantity { + amount: 0.01, + unit: Metric::Metre, + })); + assert_eq!(SiUnit(0.01, "cm"), si_unit(MetricQuantity { + amount: 0.1, + unit: Metric::Metre, + })); + assert_eq!(SiUnit(1.0, "m"), si_unit(MetricQuantity { + amount: 1.0, + unit: Metric::Metre, + })); + assert_eq!(SiUnit(1.0, "m"), si_unit(MetricQuantity { + 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!(si_prefix(-0.5), (-50.0, "c")); - assert_eq!(si_prefix(-2_000_000_000_000_000_000_000.0), (-2.0, "Z")); + assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity { + amount: -0.001, + 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, + })); } } diff --git a/src/units.rs b/src/units.rs index 6e0b0c6..38a2921 100644 --- a/src/units.rs +++ b/src/units.rs @@ -17,7 +17,7 @@ pub enum NonMetric { Stone, } -#[derive(Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct MetricQuantity { pub amount: f64, pub unit: Metric,