Implement output formatting
This commit is contained in:
parent
da620a04aa
commit
e915a3f689
2 changed files with 115 additions and 1 deletions
112
src/format.rs
Normal file
112
src/format.rs
Normal file
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
mod conversions;
|
mod conversions;
|
||||||
|
mod format;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod units;
|
mod units;
|
||||||
|
|
||||||
use conversions::convert;
|
use conversions::convert;
|
||||||
|
use format::format;
|
||||||
use parse::{parse, ParseError};
|
use parse::{parse, ParseError};
|
||||||
use units::{MetricQuantity, NonMetric};
|
use units::{MetricQuantity, NonMetric};
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ pub fn run(input: &str) -> Result<String, String> {
|
||||||
unit: metric_unit.expect("we must have at least one quantity by this point"),
|
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 {
|
fn unit_to_name(unit: NonMetric) -> &'static str {
|
||||||
|
|
Loading…
Reference in a new issue