Skip to content

Commit

Permalink
Use '=' for fractions and '≈' for rounded results, #140
Browse files Browse the repository at this point in the history
  • Loading branch information
PaddiM8 committed Apr 2, 2024
1 parent 7a44402 commit b6d2290
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 35 deletions.
14 changes: 11 additions & 3 deletions kalk/src/calculation_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
}

Expand Down
52 changes: 41 additions & 11 deletions kalk/src/kalk_value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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(", ")
)
Expand All @@ -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));
}
Expand Down Expand Up @@ -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));
Expand All @@ -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);

Expand All @@ -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" {
Expand All @@ -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 { "+" };
Expand All @@ -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.
Expand Down
70 changes: 49 additions & 21 deletions kalk/src/kalk_value/rounding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -74,15 +80,21 @@ 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,
// eg. x² is an integer,
// 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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
})
}
}

Expand Down Expand Up @@ -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());
Expand Down

0 comments on commit b6d2290

Please sign in to comment.