mirror of
https://github.com/PaddiM8/kalker.git
synced 2024-12-12 17:40:52 +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)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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());
|
||||||
|
Loading…
Reference in New Issue
Block a user