Browse Source

Improved formatting of numbers

David Peter 2 years ago
parent
commit
04af7d155f
3 changed files with 55 additions and 10 deletions
  1. 3 3
      numbat-cli/tests/integration.rs
  2. 51 0
      numbat/src/number.rs
  3. 1 7
      numbat/src/quantity.rs

+ 3 - 3
numbat-cli/tests/integration.rs

@@ -11,7 +11,7 @@ fn pass_expression_on_command_line() {
         .arg("2 meter + 3 meter")
         .assert()
         .success()
-        .stdout(predicates::str::contains("5.0 meter"));
+        .stdout(predicates::str::contains("5 meter"));
 
     numbat()
         .arg("--expression")
@@ -60,8 +60,8 @@ fn without_prelude() {
     numbat()
         .arg("--no-prelude")
         .arg("--expression")
-        .arg("2 + 3")
+        .arg("2.1 + 3.1")
         .assert()
         .success()
-        .stdout(predicates::str::contains("5.0"));
+        .stdout(predicates::str::contains("5.2"));
 }

+ 51 - 0
numbat/src/number.rs

@@ -18,6 +18,38 @@ impl Number {
     pub fn pow(self, other: &Number) -> Self {
         Number::from_f64(self.to_f64().pow(other.to_f64()))
     }
+
+    fn is_integer(self) -> bool {
+        self.0.trunc() == self.0
+    }
+
+    pub fn pretty_print(self) -> String {
+        let fractional_digits = 6;
+
+        let number = self.0;
+
+        // 64-bit floats can accurately represent integers up to 2^52 [1].
+        //
+        // [1] https://stackoverflow.com/a/43656339
+        //
+        // TODO: this upper bound can be changed if we use a proper big-integer or
+        // big-decimal type.
+        if self.is_integer() && self.0.abs() < 1e15 {
+            format!("{number}")
+        } else {
+            if self.0.abs() > 1e12 {
+                format!("{number:.fractional_digits$e}")
+            } else {
+                let formatted_number = format!("{number:.fractional_digits$}");
+                let formatted_number = formatted_number.trim_end_matches('0');
+                if formatted_number.ends_with('.') {
+                    format!("{}0", formatted_number)
+                } else {
+                    formatted_number.to_string()
+                }
+            }
+        }
+    }
 }
 
 impl std::ops::Add for Number {
@@ -65,3 +97,22 @@ impl std::iter::Product for Number {
         iter.fold(Number::from_f64(1.0), |acc, n| acc * n)
     }
 }
+
+#[test]
+fn test_pretty_print() {
+    assert_eq!(Number::from_f64(1.).pretty_print(), "1");
+    assert_eq!(Number::from_f64(1.234).pretty_print(), "1.234");
+    assert_eq!(Number::from_f64(1.234e50).pretty_print(), "1.234000e50");
+
+    assert_eq!(Number::from_f64(1234567890.).pretty_print(), "1234567890");
+    assert_eq!(
+        Number::from_f64(1234567890000000.).pretty_print(),
+        "1.234568e15"
+    );
+
+    assert_eq!(Number::from_f64(1.23456789).pretty_print(), "1.234568");
+    assert_eq!(
+        Number::from_f64(1234567890000.1).pretty_print(),
+        "1.234568e12"
+    );
+}

+ 1 - 7
numbat/src/quantity.rs

@@ -260,13 +260,7 @@ impl PrettyPrint for Quantity {
     fn pretty_print(&self) -> crate::markup::Markup {
         use crate::markup;
 
-        let formatted_number = format!("{:.6}", self.unsafe_value().to_f64());
-        let formatted_number = formatted_number.trim_end_matches('0');
-        let formatted_number = if formatted_number.ends_with('.') {
-            format!("{}0", formatted_number)
-        } else {
-            formatted_number.to_string()
-        };
+        let formatted_number = self.unsafe_value().pretty_print();
 
         let output_markup = markup::text("    ")
             + markup::operator("=")