2023-05-14 01:23:39 +00:00
|
|
|
use crate::units::{Metric, MetricQuantity};
|
|
|
|
|
|
|
|
pub fn format(quantity: MetricQuantity) -> String {
|
2023-05-28 17:32:31 +00:00
|
|
|
let PrefixedUnit(multiplier, unit) = prefixed_unit(quantity);
|
2023-05-28 15:57:49 +00:00
|
|
|
let amount = quantity.amount / multiplier;
|
2023-05-28 17:02:06 +00:00
|
|
|
let amount = format_number(amount);
|
2023-05-28 15:57:49 +00:00
|
|
|
format!("{amount} {unit}")
|
2023-05-14 01:23:39 +00:00
|
|
|
}
|
|
|
|
|
2023-05-28 17:02:06 +00:00
|
|
|
fn format_number(number: f64) -> String {
|
|
|
|
let sign = if number < 0.0 { "-" } else { "" };
|
|
|
|
let number = number.abs();
|
|
|
|
|
|
|
|
// Lower the number of decimal digits as the number grows, to try
|
|
|
|
// to maintain four significant figures
|
|
|
|
let precision = if number < 1.0 {
|
|
|
|
4
|
|
|
|
} else if number < 10.0 {
|
|
|
|
3
|
|
|
|
} else if number < 100.0 {
|
|
|
|
2
|
|
|
|
} else if number < 1000.0 {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
// Split number into integer and decimal parts for further processing
|
|
|
|
let formatted = format!("{number:.precision$}");
|
|
|
|
let mut formatted = formatted.split('.');
|
|
|
|
let integer = formatted.next().expect("f64 formatted with .precision$ must have a part before '.'");
|
|
|
|
let decimal = if precision > 0 {
|
|
|
|
formatted.next().expect("f64 formatted with .precision$ must have a part after '.' if precision > 0")
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
|
|
|
// Group the integer part into groups of three, e.g. 1000 → 10 000
|
|
|
|
let mut grouped = String::new();
|
|
|
|
let mut group_length = 0;
|
|
|
|
for c in integer.chars().rev() {
|
|
|
|
if group_length == 3 {
|
|
|
|
grouped.push(' ');
|
|
|
|
group_length = 0;
|
|
|
|
}
|
|
|
|
grouped.push(c);
|
|
|
|
group_length += 1;
|
|
|
|
}
|
|
|
|
let grouped: String = grouped.chars().rev().collect();
|
|
|
|
|
|
|
|
// Remove trailing zeroes
|
|
|
|
let decimal = decimal.trim_end_matches('0');
|
|
|
|
|
|
|
|
if decimal.len() == 0 {
|
|
|
|
format!("{sign}{grouped}")
|
|
|
|
} else {
|
|
|
|
format!("{sign}{grouped}.{decimal}")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-28 15:57:49 +00:00
|
|
|
#[derive(Debug, PartialEq)]
|
2023-05-28 17:32:31 +00:00
|
|
|
struct PrefixedUnit(f64, &'static str);
|
2023-05-14 01:23:39 +00:00
|
|
|
|
2023-05-28 17:32:31 +00:00
|
|
|
fn prefixed_unit(quantity: MetricQuantity) -> PrefixedUnit {
|
2023-05-28 15:57:49 +00:00
|
|
|
let absolute = quantity.amount.abs();
|
2023-05-14 01:23:39 +00:00
|
|
|
|
2023-05-28 15:57:49 +00:00
|
|
|
match quantity.unit {
|
|
|
|
Metric::Metre => {
|
|
|
|
if absolute >= 1000.0 {
|
2023-05-28 17:32:31 +00:00
|
|
|
return PrefixedUnit(1000.0, "km");
|
2023-05-28 15:57:49 +00:00
|
|
|
} else if absolute >= 1.0 {
|
2023-05-28 17:32:31 +00:00
|
|
|
return PrefixedUnit(1.0, "m");
|
2023-05-28 15:57:49 +00:00
|
|
|
} else if absolute >= 0.01 {
|
2023-05-28 17:32:31 +00:00
|
|
|
return PrefixedUnit(0.01, "cm");
|
2023-05-28 15:57:49 +00:00
|
|
|
} else {
|
2023-05-28 17:32:31 +00:00
|
|
|
return PrefixedUnit(0.001, "mm");
|
2023-05-28 15:57:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Metric::Gram => {
|
|
|
|
if absolute >= 1000.0 {
|
2023-05-28 17:32:31 +00:00
|
|
|
return PrefixedUnit(1000.0, "kg");
|
2023-05-28 15:57:49 +00:00
|
|
|
} else {
|
2023-05-28 17:32:31 +00:00
|
|
|
return PrefixedUnit(1.0, "g");
|
2023-05-28 15:57:49 +00:00
|
|
|
}
|
2023-05-14 01:23:39 +00:00
|
|
|
}
|
2023-05-29 18:37:56 +00:00
|
|
|
Metric::Celsius => PrefixedUnit(1.0, "°C"),
|
2023-05-28 23:55:43 +00:00
|
|
|
Metric::SquareMetre => {
|
|
|
|
if absolute >= 1000.0 * 1000.0 {
|
|
|
|
return PrefixedUnit(1000.0 * 1000.0, "km²");
|
|
|
|
} else if absolute >= 1.0 {
|
|
|
|
return PrefixedUnit(1.0, "m²");
|
|
|
|
} else if absolute >= 0.01 * 0.01 {
|
|
|
|
return PrefixedUnit(0.01 * 0.01, "cm²");
|
|
|
|
} else {
|
|
|
|
return PrefixedUnit(0.001 * 0.001, "mm²");
|
|
|
|
}
|
|
|
|
}
|
2023-05-14 01:23:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn quantities() {
|
2023-05-28 15:57:49 +00:00
|
|
|
assert_eq!("0.1 mm", &format(MetricQuantity {
|
|
|
|
amount: 0.000_1,
|
2023-05-14 01:23:39 +00:00
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 15:57:49 +00:00
|
|
|
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,
|
2023-05-14 01:23:39 +00:00
|
|
|
}));
|
2023-05-28 17:02:06 +00:00
|
|
|
assert_eq!("1 000 km", &format(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 1_000_000.0,
|
2023-05-14 01:23:39 +00:00
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 15:57:49 +00:00
|
|
|
|
|
|
|
assert_eq!("-5 g", &format(MetricQuantity {
|
|
|
|
amount: -5.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
|
|
|
assert_eq!("3.2 kg", &format(MetricQuantity {
|
|
|
|
amount: 3_200.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
|
|
|
|
assert_eq!("1 000 °C", &format(MetricQuantity {
|
|
|
|
amount: 1_000.0,
|
2023-05-29 18:37:56 +00:00
|
|
|
unit: Metric::Celsius,
|
2023-05-28 17:32:31 +00:00
|
|
|
}));
|
2023-05-14 01:23:39 +00:00
|
|
|
}
|
|
|
|
|
2023-05-28 17:02:06 +00:00
|
|
|
#[test]
|
|
|
|
fn numbers() {
|
|
|
|
assert_eq!(&format_number(0.0001), "0.0001");
|
|
|
|
assert_eq!(&format_number(0.001), "0.001");
|
|
|
|
assert_eq!(&format_number(0.01), "0.01");
|
|
|
|
assert_eq!(&format_number(0.1), "0.1");
|
|
|
|
assert_eq!(&format_number(1.0), "1");
|
|
|
|
assert_eq!(&format_number(10.0), "10");
|
|
|
|
assert_eq!(&format_number(100.0), "100");
|
|
|
|
assert_eq!(&format_number(1_000.0), "1 000");
|
|
|
|
assert_eq!(&format_number(10_000.0), "10 000");
|
|
|
|
assert_eq!(&format_number(100_000.0), "100 000");
|
|
|
|
assert_eq!(&format_number(1_000_000.0), "1 000 000");
|
|
|
|
|
|
|
|
assert_eq!(&format_number(0.00001), "0");
|
|
|
|
assert_eq!(&format_number(1.0001), "1");
|
|
|
|
assert_eq!(&format_number(1.001), "1.001");
|
|
|
|
assert_eq!(&format_number(10.001), "10");
|
|
|
|
assert_eq!(&format_number(10.01), "10.01");
|
|
|
|
assert_eq!(&format_number(100.01), "100");
|
|
|
|
assert_eq!(&format_number(100.1), "100.1");
|
|
|
|
assert_eq!(&format_number(1_000.1), "1 000");
|
|
|
|
|
|
|
|
assert_eq!(&format_number(-1.0), "-1");
|
|
|
|
assert_eq!(&format_number(-100.0), "-100");
|
|
|
|
assert_eq!(&format_number(-1000.0), "-1 000");
|
|
|
|
}
|
|
|
|
|
2023-05-14 01:23:39 +00:00
|
|
|
#[test]
|
2023-05-28 23:54:27 +00:00
|
|
|
fn metres() {
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(0.001, "mm"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 0.0001,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(0.001, "mm"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 0.001,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(0.01, "cm"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 0.01,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(0.01, "cm"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 0.1,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "m"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 1.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "m"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 10.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "m"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 100.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1000.0, "km"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 1000.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1000.0, "km"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 10_000.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
|
|
|
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(0.001, "mm"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: -0.001,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(0.01, "cm"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: -0.01,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "m"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: -1.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1000.0, "km"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: -1000.0,
|
|
|
|
unit: Metric::Metre,
|
|
|
|
}));
|
2023-05-28 23:54:27 +00:00
|
|
|
}
|
2023-05-28 15:57:49 +00:00
|
|
|
|
2023-05-28 23:54:27 +00:00
|
|
|
#[test]
|
|
|
|
fn grams() {
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "g"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 0.1,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "g"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 1.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "g"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 10.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "g"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 100.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1000.0, "kg"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 1000.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1000.0, "kg"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: 10_1000.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-14 01:41:39 +00:00
|
|
|
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "g"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: -1.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1000.0, "kg"), prefixed_unit(MetricQuantity {
|
2023-05-28 15:57:49 +00:00
|
|
|
amount: -1000.0,
|
|
|
|
unit: Metric::Gram,
|
|
|
|
}));
|
2023-05-28 23:54:27 +00:00
|
|
|
}
|
2023-05-28 17:32:31 +00:00
|
|
|
|
2023-05-28 23:54:27 +00:00
|
|
|
#[test]
|
2023-05-29 18:37:56 +00:00
|
|
|
fn celsius() {
|
2023-05-28 17:32:31 +00:00
|
|
|
assert_eq!(PrefixedUnit(1.0, "°C"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.0001,
|
2023-05-29 18:37:56 +00:00
|
|
|
unit: Metric::Celsius,
|
2023-05-28 17:32:31 +00:00
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "°C"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: -1.0,
|
2023-05-29 18:37:56 +00:00
|
|
|
unit: Metric::Celsius,
|
2023-05-28 17:32:31 +00:00
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "°C"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 1_000.0,
|
2023-05-29 18:37:56 +00:00
|
|
|
unit: Metric::Celsius,
|
2023-05-28 17:32:31 +00:00
|
|
|
}));
|
2023-05-14 01:23:39 +00:00
|
|
|
}
|
2023-05-28 23:55:43 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn square_metres() {
|
|
|
|
assert_eq!(PrefixedUnit(0.000_001, "mm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.000_000_1,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_001, "mm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.000_001,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_001, "mm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.000_01,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_1, "cm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.000_1,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_1, "cm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.001,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_1, "cm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.01,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_1, "cm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.01,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(0.000_1, "cm²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 0.1,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "m²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 1.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "m²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 10.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "m²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 100.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "m²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 1_000.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "m²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 10_000.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1.0, "m²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 100_000.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1_000_000.0, "km²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 1_000_000.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
assert_eq!(PrefixedUnit(1_000_000.0, "km²"), prefixed_unit(MetricQuantity {
|
|
|
|
amount: 10_000_000.0,
|
|
|
|
unit: Metric::SquareMetre,
|
|
|
|
}));
|
|
|
|
}
|
2023-05-14 01:23:39 +00:00
|
|
|
}
|