mirror of
https://github.com/PaddiM8/kalker.git
synced 2024-12-12 09:30:40 +01:00
Use '=' for fractions and '≈' for rounded results, #140
This commit is contained in:
parent
7a444022a2
commit
b6d22903fc
@ -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<String> {
|
||||
self.value.estimate()
|
||||
self.value.estimate().map(|x| x.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::<Vec<String>>()
|
||||
.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<String> {
|
||||
pub fn estimate(&self) -> Option<EstimationResult> {
|
||||
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.
|
||||
|
@ -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<String> {
|
||||
) -> Option<EstimationResult> {
|
||||
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<String> {
|
||||
fn equivalent_fraction(value: f64) -> Option<EstimationResult> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
} 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());
|
||||
|
Loading…
Reference in New Issue
Block a user