From e915a3f689d204b0b229a192600a5b46570f902c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Sun, 14 May 2023 04:23:39 +0300 Subject: [PATCH] Implement output formatting --- src/format.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/format.rs diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..1e0677f --- /dev/null +++ b/src/format.rs @@ -0,0 +1,112 @@ +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")); + } +} diff --git a/src/lib.rs b/src/lib.rs index d9738d9..dd42f08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,10 @@ mod conversions; +mod format; mod parse; mod units; use conversions::convert; +use format::format; use parse::{parse, ParseError}; use units::{MetricQuantity, NonMetric}; @@ -54,7 +56,7 @@ pub fn run(input: &str) -> Result { unit: metric_unit.expect("we must have at least one quantity by this point"), }; - Ok(format!("{quantity:?}")) + Ok(format(quantity)) } fn unit_to_name(unit: NonMetric) -> &'static str {