Use '=' for fractions and '≈' for rounded results, #140

This commit is contained in:
PaddiM8 2024-04-03 01:00:07 +02:00
parent 7a444022a2
commit b6d22903fc
3 changed files with 101 additions and 35 deletions

View File

@ -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) format!("{}", value)
} else { } else {
value format!("= {}", value)
} }
} }
@ -91,7 +99,7 @@ impl CalculationResult {
#[wasm_bindgen(js_name = estimate)] #[wasm_bindgen(js_name = estimate)]
pub fn estimate_js(&self) -> Option<String> { pub fn estimate_js(&self) -> Option<String> {
self.value.estimate() self.value.estimate().map(|x| x.value)
} }
} }

View File

@ -14,6 +14,8 @@ use crate::errors::KalkError;
use crate::radix; use crate::radix;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use self::rounding::EstimationResult;
const ACCEPTABLE_COMPARISON_MARGIN: f64 = 0.00000001; const ACCEPTABLE_COMPARISON_MARGIN: f64 = 0.00000001;
#[macro_export] #[macro_export]
@ -207,12 +209,21 @@ impl std::fmt::Display for KalkValue {
} }
} }
KalkValue::Vector(values) => { 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!( write!(
f, f,
"({})", "({})",
values values
.iter() .iter()
.map(|x| x.estimate().unwrap_or_else(|| x.to_string())) .map(get_estimation)
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
) )
@ -222,7 +233,13 @@ impl std::fmt::Display for KalkValue {
let mut longest = 0; let mut longest = 0;
for row in rows { for row in rows {
for value in row { 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()); longest = longest.max(value_str.len());
value_strings.push(format!("{},", value_str)); value_strings.push(format!("{},", value_str));
} }
@ -395,8 +412,9 @@ impl KalkValue {
let new_value = KalkValue::Number(new_real, new_imaginary, unit.clone()); let new_value = KalkValue::Number(new_real, new_imaginary, unit.clone());
if let Some(estimate) = new_value.estimate() { if let Some(estimate) = new_value.estimate() {
if estimate != output && radix == 10 { if estimate.value != output && radix == 10 {
output.push_str(&format!("{}", estimate)); let equal_sign = if estimate.is_exact { "=" } else { "" };
output.push_str(&format!(" {equal_sign} {}", estimate.value));
} }
} else if has_scientific_notation && !is_engineering_mode { } else if has_scientific_notation && !is_engineering_mode {
output.insert_str(0, &format!("{}", self)); 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. /// Get an estimate of what the number is, eg. 3.141592 => π. Does not work properly with scientific notation.
pub fn estimate(&self) -> Option<String> { pub fn estimate(&self) -> Option<EstimationResult> {
let rounded_real = rounding::estimate(self, ComplexNumberType::Real); let rounded_real = rounding::estimate(self, ComplexNumberType::Real);
let rounded_imaginary = rounding::estimate(self, ComplexNumberType::Imaginary); let rounded_imaginary = rounding::estimate(self, ComplexNumberType::Imaginary);
@ -428,20 +446,26 @@ impl KalkValue {
} }
let mut output = String::new(); let mut output = String::new();
if let Some(value) = rounded_real { let mut real_is_exact = rounded_real.is_none();
output.push_str(&value); if let Some(result) = rounded_real {
real_is_exact = result.is_exact;
output.push_str(&result.value);
} else if self.has_real() { } else if self.has_real() {
output.push_str(&self.to_string_real(10)); output.push_str(&self.to_string_real(10));
} }
let imaginary_value = if let Some(value) = rounded_imaginary { let mut imaginary_is_exact = rounded_imaginary.is_none();
Some(value) let imaginary_value = if let Some(result) = rounded_imaginary {
imaginary_is_exact = result.is_exact;
Some(result.value)
} else if self.has_imaginary() { } else if self.has_imaginary() {
Some(self.to_string_imaginary(10, false)) Some(self.to_string_imaginary(10, false))
} else { } else {
None None
}; };
let is_exact = real_is_exact && imaginary_is_exact;
if let Some(value) = imaginary_value { if let Some(value) = imaginary_value {
// Clear output if it's just 0. // Clear output if it's just 0.
if output == "0" { if output == "0" {
@ -452,7 +476,10 @@ impl KalkValue {
// If both values ended up being estimated as zero, // If both values ended up being estimated as zero,
// return zero. // return zero.
if output.is_empty() { if output.is_empty() {
return Some(String::from("0")); return Some(EstimationResult {
value: String::from("0"),
is_exact,
});
} }
} else { } else {
let sign = if value.starts_with('-') { "-" } 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. /// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc.

View File

@ -42,10 +42,16 @@ lazy_static! {
}; };
} }
#[derive(Debug)]
pub struct EstimationResult {
pub value: String,
pub is_exact: bool,
}
pub(super) fn estimate( pub(super) fn estimate(
input: &KalkValue, input: &KalkValue,
complex_number_type: ComplexNumberType, complex_number_type: ComplexNumberType,
) -> Option<String> { ) -> Option<EstimationResult> {
let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input { let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input {
(real, imaginary, unit) (real, imaginary, unit)
} else { } else {
@ -74,7 +80,10 @@ pub(super) fn estimate(
// Match with common numbers, eg. π, 2π/3, √2 // Match with common numbers, eg. π, 2π/3, √2
if let Some(equivalent_constant) = equivalent_constant(value) { 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, // 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²). // then it can be expressed as sqrt(x²).
// Ignore it if the square root of the result is an integer. // Ignore it if the square root of the result is an integer.
if let Some(equivalent_root) = equivalent_root(value) { 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 // 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, ComplexNumberType::Imaginary => round(input, complex_number_type)?.values().1,
}; };
let rounded_str = rounded.to_string(); let rounded_str = rounded.to_string();
Some(trim_zeroes(if rounded_str == "-0" { let result = trim_zeroes(if rounded_str == "-0" {
"0" "0"
} else { } else {
&rounded_str &rounded_str
})) });
Some(EstimationResult {
value: result,
is_exact: false,
})
} }
fn equivalent_fraction(value: f64) -> Option<String> { fn equivalent_fraction(value: f64) -> Option<EstimationResult> {
fn gcd(mut a: i64, mut b: i64) -> i64 { fn gcd(mut a: i64, mut b: i64) -> i64 {
while a != 0 { while a != 0 {
let old_a = a; let old_a = a;
@ -137,7 +154,7 @@ fn equivalent_fraction(value: f64) -> Option<String> {
let factor = 10i64.pow(non_repeating_dec_count as u32) as f64; 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 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 b_numer = b as f64;
let ab_denom = nines * factor; let ab_denom = nines * factor;
let integer_part_as_numer = non_repeating.trunc() * ab_denom; let integer_part_as_numer = non_repeating.trunc() * ab_denom;
@ -168,16 +185,28 @@ fn equivalent_fraction(value: f64) -> Option<String> {
} else { } else {
"-" "-"
}; };
let calculated_value =
Some(format!( original_sign * integer_part + original_sign * (numer.abs() / denom.abs());
let result_str = format!(
"{} {} {}/{}", "{} {} {}/{}",
integer_part * original_sign, original_sign * integer_part,
sign, sign,
numer.abs(), numer.abs(),
denom.abs() denom.abs()
)) );
Some(EstimationResult {
value: result_str,
is_exact: value == calculated_value,
})
} else { } 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 { for (input, output) in in_out {
let result = KalkValue::from(input).estimate(); let result = KalkValue::from(input).estimate().map(|x| x.value);
println!("{}", input);
assert_eq!(output, result); assert_eq!(output, result);
} }
} }
#[test] #[test]
fn test_equivalent_fraction() { fn test_equivalent_fraction() {
assert_eq!(equivalent_fraction(0.5f64).unwrap(), "1/2"); assert_eq!(equivalent_fraction(0.5f64).unwrap().value, "1/2");
assert_eq!(equivalent_fraction(-0.5f64).unwrap(), "-1/2"); assert_eq!(equivalent_fraction(-0.5f64).unwrap().value, "-1/2");
assert_eq!(equivalent_fraction(1f64 / 3f64).unwrap(), "1/3"); assert_eq!(equivalent_fraction(1f64 / 3f64).unwrap().value, "1/3");
assert_eq!(equivalent_fraction(4f64 / 3f64).unwrap(), "4/3"); assert_eq!(equivalent_fraction(4f64 / 3f64).unwrap().value, "4/3");
assert_eq!(equivalent_fraction(7f64 / 3f64).unwrap(), "2 + 1/3"); assert_eq!(equivalent_fraction(7f64 / 3f64).unwrap().value, "2 + 1/3");
assert_eq!(equivalent_fraction(-1f64 / 12f64).unwrap(), "-1/12"); assert_eq!(equivalent_fraction(-1f64 / 12f64).unwrap().value, "-1/12");
assert_eq!(equivalent_fraction(-16f64 / -7f64).unwrap(), "2 + 2/7"); assert_eq!(equivalent_fraction(-16f64 / -7f64).unwrap().value, "2 + 2/7");
assert!(equivalent_fraction(0.123f64).is_none()); assert!(equivalent_fraction(0.123f64).is_none());
assert!(equivalent_fraction(1f64).is_none()); assert!(equivalent_fraction(1f64).is_none());
assert!(equivalent_fraction(0.01f64).is_none()); assert!(equivalent_fraction(0.01f64).is_none());