Format numbers so that they are easier to read

This commit is contained in:
Juhani Krekelä 2023-05-28 20:02:06 +03:00
parent d77a84fef1
commit f8db69e3b2
1 changed files with 81 additions and 2 deletions

View File

@ -3,10 +3,61 @@ use crate::units::{Metric, MetricQuantity};
pub fn format(quantity: MetricQuantity) -> String {
let SiUnit(multiplier, unit) = si_unit(quantity);
let amount = quantity.amount / multiplier;
let amount = format_number(amount);
format!("{amount} {unit}")
}
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}")
}
}
#[derive(Debug, PartialEq)]
struct SiUnit(f64, &'static str);
@ -53,7 +104,7 @@ mod test {
amount: 1.5,
unit: Metric::Metre,
}));
assert_eq!("1000 km", &format(MetricQuantity {
assert_eq!("1 000 km", &format(MetricQuantity {
amount: 1_000_000.0,
unit: Metric::Metre,
}));
@ -68,6 +119,34 @@ mod test {
}));
}
#[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");
}
#[test]
fn units() {
assert_eq!(SiUnit(0.001, "mm"), si_unit(MetricQuantity {