From b6d22903fc6ec13262f32014a7d7bd5a7e6ff89e Mon Sep 17 00:00:00 2001 From: PaddiM8 Date: Wed, 3 Apr 2024 01:00:07 +0200 Subject: [PATCH] =?UTF-8?q?Use=20'=3D'=20for=20fractions=20and=20'?= =?UTF-8?q?=E2=89=88'=20for=20rounded=20results,=20#140?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kalk/src/calculation_result.rs | 14 +++++-- kalk/src/kalk_value/mod.rs | 52 ++++++++++++++++++------ kalk/src/kalk_value/rounding.rs | 70 +++++++++++++++++++++++---------- 3 files changed, 101 insertions(+), 35 deletions(-) diff --git a/kalk/src/calculation_result.rs b/kalk/src/calculation_result.rs index 67b9cc4..ffdd7fa 100644 --- a/kalk/src/calculation_result.rs +++ b/kalk/src/calculation_result.rs @@ -48,10 +48,18 @@ impl CalculationResult { ) }; - if self.is_approximation { + let decimal_count = if let Some(dot_index) = value.chars().position(|c| c == '.') { + let end_index = value.chars().position(|c| c == ' ').unwrap_or(value.len()) - 1; + + if end_index > dot_index { end_index - dot_index } else { 0 } + } else { + 0 + }; + + if self.is_approximation || decimal_count == 10 { format!("≈ {}", value) } else { - value + format!("= {}", value) } } @@ -91,7 +99,7 @@ impl CalculationResult { #[wasm_bindgen(js_name = estimate)] pub fn estimate_js(&self) -> Option { - self.value.estimate() + self.value.estimate().map(|x| x.value) } } diff --git a/kalk/src/kalk_value/mod.rs b/kalk/src/kalk_value/mod.rs index ad58698..4d3ff48 100644 --- a/kalk/src/kalk_value/mod.rs +++ b/kalk/src/kalk_value/mod.rs @@ -14,6 +14,8 @@ use crate::errors::KalkError; use crate::radix; use wasm_bindgen::prelude::*; +use self::rounding::EstimationResult; + const ACCEPTABLE_COMPARISON_MARGIN: f64 = 0.00000001; #[macro_export] @@ -207,12 +209,21 @@ impl std::fmt::Display for KalkValue { } } KalkValue::Vector(values) => { + let get_estimation: fn(&KalkValue) -> String = |x| { + x.estimate() + .unwrap_or_else(|| EstimationResult { + value: x.to_string(), + is_exact: false, + }) + .value + }; + write!( f, "({})", values .iter() - .map(|x| x.estimate().unwrap_or_else(|| x.to_string())) + .map(get_estimation) .collect::>() .join(", ") ) @@ -222,7 +233,13 @@ impl std::fmt::Display for KalkValue { let mut longest = 0; for row in rows { for value in row { - let value_str = value.estimate().unwrap_or_else(|| value.to_string()); + let value_str = value + .estimate() + .unwrap_or_else(|| EstimationResult { + value: value.to_string(), + is_exact: false, + }) + .value; longest = longest.max(value_str.len()); value_strings.push(format!("{},", value_str)); } @@ -395,8 +412,9 @@ impl KalkValue { let new_value = KalkValue::Number(new_real, new_imaginary, unit.clone()); if let Some(estimate) = new_value.estimate() { - if estimate != output && radix == 10 { - output.push_str(&format!(" ≈ {}", estimate)); + if estimate.value != output && radix == 10 { + let equal_sign = if estimate.is_exact { "=" } else { "≈" }; + output.push_str(&format!(" {equal_sign} {}", estimate.value)); } } else if has_scientific_notation && !is_engineering_mode { output.insert_str(0, &format!("{} ≈ ", self)); @@ -419,7 +437,7 @@ impl KalkValue { } /// Get an estimate of what the number is, eg. 3.141592 => π. Does not work properly with scientific notation. - pub fn estimate(&self) -> Option { + pub fn estimate(&self) -> Option { let rounded_real = rounding::estimate(self, ComplexNumberType::Real); let rounded_imaginary = rounding::estimate(self, ComplexNumberType::Imaginary); @@ -428,20 +446,26 @@ impl KalkValue { } let mut output = String::new(); - if let Some(value) = rounded_real { - output.push_str(&value); + let mut real_is_exact = rounded_real.is_none(); + if let Some(result) = rounded_real { + real_is_exact = result.is_exact; + output.push_str(&result.value); } else if self.has_real() { output.push_str(&self.to_string_real(10)); } - let imaginary_value = if let Some(value) = rounded_imaginary { - Some(value) + let mut imaginary_is_exact = rounded_imaginary.is_none(); + let imaginary_value = if let Some(result) = rounded_imaginary { + imaginary_is_exact = result.is_exact; + + Some(result.value) } else if self.has_imaginary() { Some(self.to_string_imaginary(10, false)) } else { None }; + let is_exact = real_is_exact && imaginary_is_exact; if let Some(value) = imaginary_value { // Clear output if it's just 0. if output == "0" { @@ -452,7 +476,10 @@ impl KalkValue { // If both values ended up being estimated as zero, // return zero. if output.is_empty() { - return Some(String::from("0")); + return Some(EstimationResult { + value: String::from("0"), + is_exact, + }); } } else { let sign = if value.starts_with('-') { "-" } else { "+" }; @@ -471,7 +498,10 @@ impl KalkValue { } } - Some(output) + Some(EstimationResult { + value: output, + is_exact, + }) } /// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc. diff --git a/kalk/src/kalk_value/rounding.rs b/kalk/src/kalk_value/rounding.rs index 46cbdd2..6f828e2 100644 --- a/kalk/src/kalk_value/rounding.rs +++ b/kalk/src/kalk_value/rounding.rs @@ -42,10 +42,16 @@ lazy_static! { }; } +#[derive(Debug)] +pub struct EstimationResult { + pub value: String, + pub is_exact: bool, +} + pub(super) fn estimate( input: &KalkValue, complex_number_type: ComplexNumberType, -) -> Option { +) -> Option { let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input { (real, imaginary, unit) } else { @@ -74,7 +80,10 @@ pub(super) fn estimate( // Match with common numbers, eg. π, 2π/3, √2 if let Some(equivalent_constant) = equivalent_constant(value) { - return Some(equivalent_constant); + return Some(EstimationResult { + value: equivalent_constant, + is_exact: false, + }); } // If the value squared (and rounded) is an integer, @@ -82,7 +91,10 @@ pub(super) fn estimate( // then it can be expressed as sqrt(x²). // Ignore it if the square root of the result is an integer. if let Some(equivalent_root) = equivalent_root(value) { - return Some(equivalent_root); + return Some(EstimationResult { + value: equivalent_root, + is_exact: false, + }); } // If nothing above was relevant, simply round it off a bit, eg. from 0.99999 to 1 @@ -91,14 +103,19 @@ pub(super) fn estimate( ComplexNumberType::Imaginary => round(input, complex_number_type)?.values().1, }; let rounded_str = rounded.to_string(); - Some(trim_zeroes(if rounded_str == "-0" { + let result = trim_zeroes(if rounded_str == "-0" { "0" } else { &rounded_str - })) + }); + + Some(EstimationResult { + value: result, + is_exact: false, + }) } -fn equivalent_fraction(value: f64) -> Option { +fn equivalent_fraction(value: f64) -> Option { fn gcd(mut a: i64, mut b: i64) -> i64 { while a != 0 { let old_a = a; @@ -137,7 +154,7 @@ fn equivalent_fraction(value: f64) -> Option { let factor = 10i64.pow(non_repeating_dec_count as u32) as f64; let nines = (10i64.pow(repeatend_str.len() as u32) - 1) as f64; - let a_numer = a as f64 * factor * nines; + let a_numer = a * factor * nines; let b_numer = b as f64; let ab_denom = nines * factor; let integer_part_as_numer = non_repeating.trunc() * ab_denom; @@ -168,16 +185,28 @@ fn equivalent_fraction(value: f64) -> Option { } else { "-" }; - - Some(format!( + let calculated_value = + original_sign * integer_part + original_sign * (numer.abs() / denom.abs()); + let result_str = format!( "{} {} {}/{}", - integer_part * original_sign, + original_sign * integer_part, sign, numer.abs(), denom.abs() - )) + ); + + Some(EstimationResult { + value: result_str, + is_exact: value == calculated_value, + }) } else { - Some(format!("{}/{}", numer * original_sign, denom)) + let calculated_value = numer * original_sign / denom; + let result_str = format!("{}/{}", numer * original_sign, denom); + + Some(EstimationResult { + value: result_str, + is_exact: value == calculated_value, + }) } } @@ -388,21 +417,20 @@ mod tests { ]; for (input, output) in in_out { - let result = KalkValue::from(input).estimate(); - println!("{}", input); + let result = KalkValue::from(input).estimate().map(|x| x.value); assert_eq!(output, result); } } #[test] fn test_equivalent_fraction() { - assert_eq!(equivalent_fraction(0.5f64).unwrap(), "1/2"); - assert_eq!(equivalent_fraction(-0.5f64).unwrap(), "-1/2"); - assert_eq!(equivalent_fraction(1f64 / 3f64).unwrap(), "1/3"); - assert_eq!(equivalent_fraction(4f64 / 3f64).unwrap(), "4/3"); - assert_eq!(equivalent_fraction(7f64 / 3f64).unwrap(), "2 + 1/3"); - assert_eq!(equivalent_fraction(-1f64 / 12f64).unwrap(), "-1/12"); - assert_eq!(equivalent_fraction(-16f64 / -7f64).unwrap(), "2 + 2/7"); + assert_eq!(equivalent_fraction(0.5f64).unwrap().value, "1/2"); + assert_eq!(equivalent_fraction(-0.5f64).unwrap().value, "-1/2"); + assert_eq!(equivalent_fraction(1f64 / 3f64).unwrap().value, "1/3"); + assert_eq!(equivalent_fraction(4f64 / 3f64).unwrap().value, "4/3"); + assert_eq!(equivalent_fraction(7f64 / 3f64).unwrap().value, "2 + 1/3"); + assert_eq!(equivalent_fraction(-1f64 / 12f64).unwrap().value, "-1/12"); + assert_eq!(equivalent_fraction(-16f64 / -7f64).unwrap().value, "2 + 2/7"); assert!(equivalent_fraction(0.123f64).is_none()); assert!(equivalent_fraction(1f64).is_none()); assert!(equivalent_fraction(0.01f64).is_none());