use crate::units::{Metric, MetricQuantity}; pub fn format(quantity: MetricQuantity) -> String { let (amount, prefix) = si_prefix(quantity.amount); let unit = abbreviation(quantity.unit); format!("{amount} {prefix}{unit}") } #[derive(Clone, Copy)] struct SiPrefix { prefix: &'static str, size: f64, } 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_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); } } unreachable!(); } fn abbreviation(unit: Metric) -> &'static str { match unit { Metric::Metre => "m", Metric::Gram => "g", } } #[cfg(test)] mod test { use super::*; #[test] fn quantities() { assert_eq!("1 m", &format(MetricQuantity { amount: 1.0, unit: Metric::Metre, })); assert_eq!("5 kg", &format(MetricQuantity { amount: 5_000.0, unit: Metric::Gram, })); assert_eq!("25.5 cm", &format(MetricQuantity { amount: 0.255, unit: Metric::Metre, })); } #[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")); } }