From f8db69e3b2b136b1138dcf1bc7eb6ecfb647c0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Sun, 28 May 2023 20:02:06 +0300 Subject: [PATCH] Format numbers so that they are easier to read --- src/format.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/src/format.rs b/src/format.rs index e529d6e..e571f77 100644 --- a/src/format.rs +++ b/src/format.rs @@ -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 {