mirror of
https://github.com/PaddiM8/kalker.git
synced 2024-11-07 16:34:21 +01:00
Estimation for any fractions with a denominator < 100
This commit is contained in:
parent
4903707ce0
commit
9746556226
@ -1326,78 +1326,4 @@ mod tests {
|
|||||||
assert_eq!(output, result);
|
assert_eq!(output, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_estimate() {
|
|
||||||
let in_out = vec![
|
|
||||||
(0.99999999, Some(String::from("1"))),
|
|
||||||
(-0.9999999, Some(String::from("-1"))),
|
|
||||||
(0.0000000001, Some(String::from("0"))),
|
|
||||||
(-0.000000001, Some(String::from("0"))),
|
|
||||||
(1.99999999, Some(String::from("2"))),
|
|
||||||
(-1.9999999, Some(String::from("-2"))),
|
|
||||||
(1.000000001, Some(String::from("1"))),
|
|
||||||
(-1.000001, Some(String::from("-1"))),
|
|
||||||
(0.5, Some(String::from("1/2"))),
|
|
||||||
(-0.5, Some(String::from("-1/2"))),
|
|
||||||
(0.3333333333, Some(String::from("1/3"))),
|
|
||||||
(1.3333333333, Some(String::from("1 + 1/3"))),
|
|
||||||
(-0.666666666, Some(String::from("-2/3"))),
|
|
||||||
(-1.666666666, Some(String::from("-1 - 2/3"))),
|
|
||||||
(-1.666666666, Some(String::from("-1 - 2/3"))),
|
|
||||||
(100.33333333, Some(String::from("100 + 1/3"))),
|
|
||||||
(-100.6666666, Some(String::from("-100 - 2/3"))),
|
|
||||||
(0.9932611, None),
|
|
||||||
(-0.9932611, None),
|
|
||||||
(-0.00001, None),
|
|
||||||
(1.9932611, None),
|
|
||||||
(-1.9932611, None),
|
|
||||||
(24f64, None),
|
|
||||||
(-24f64, None),
|
|
||||||
(1.23456f64, None),
|
|
||||||
(-1.23456f64, None),
|
|
||||||
(1.98, None),
|
|
||||||
(-1.98, None),
|
|
||||||
(9999999999f64, None),
|
|
||||||
(-9999999999f64, None),
|
|
||||||
(1000000001f64, None),
|
|
||||||
(-1000000001f64, None),
|
|
||||||
(0.53f64, None),
|
|
||||||
(-0.53f64, None),
|
|
||||||
(-1.51f64, None),
|
|
||||||
(0.335f64, None),
|
|
||||||
(-0.335f64, None),
|
|
||||||
(0.665f64, None),
|
|
||||||
(-0.665f64, None),
|
|
||||||
(100f64, None),
|
|
||||||
(-100f64, None),
|
|
||||||
(1f64, None),
|
|
||||||
(0.12f64, None),
|
|
||||||
(0.1f64, None),
|
|
||||||
(1.2f64, None),
|
|
||||||
(1.23f64, None),
|
|
||||||
(1.234f64, None),
|
|
||||||
(1.2345f64, None),
|
|
||||||
(1.23456f64, None),
|
|
||||||
(1.234567f64, None),
|
|
||||||
(1.2345678f64, None),
|
|
||||||
(1.23456789f64, None),
|
|
||||||
(-0.12f64, None),
|
|
||||||
(-0.1f64, None),
|
|
||||||
(-1.2f64, None),
|
|
||||||
(-1.23f64, None),
|
|
||||||
(-1.234f64, None),
|
|
||||||
(-1.2345f64, None),
|
|
||||||
(-1.23456f64, None),
|
|
||||||
(-1.234567f64, None),
|
|
||||||
(-1.2345678f64, None),
|
|
||||||
(-1.23456789f64, None),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (input, output) in in_out {
|
|
||||||
let result = KalkValue::from(input).estimate();
|
|
||||||
println!("{}", input);
|
|
||||||
assert_eq!(output, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{float, primitive};
|
use crate::{float, primitive};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@ -42,7 +42,6 @@ lazy_static! {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub(super) fn estimate(
|
pub(super) fn estimate(
|
||||||
input: &KalkValue,
|
input: &KalkValue,
|
||||||
complex_number_type: ComplexNumberType,
|
complex_number_type: ComplexNumberType,
|
||||||
@ -53,76 +52,37 @@ pub(super) fn estimate(
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (value, value_string) = match complex_number_type {
|
let value = match complex_number_type {
|
||||||
ComplexNumberType::Real => (real, input.to_string_real(10)),
|
ComplexNumberType::Real => real,
|
||||||
ComplexNumberType::Imaginary => (imaginary, input.to_string_imaginary(10, true)),
|
ComplexNumberType::Imaginary => imaginary,
|
||||||
};
|
};
|
||||||
|
|
||||||
let fract = value.clone().fract().abs();
|
if value > &f64::MAX {
|
||||||
let integer = value.clone().trunc();
|
|
||||||
|
|
||||||
#[cfg(feature = "rug")]
|
|
||||||
let fract_as_string = fract.to_f64().to_string();
|
|
||||||
#[cfg(not(feature = "rug"))]
|
|
||||||
let fract_as_string = fract.to_string();
|
|
||||||
|
|
||||||
// If it's an integer, there's nothing that would be done to it.
|
|
||||||
if fract == 0f64 {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eg. 0.5 to 1/2
|
let value = primitive!(value);
|
||||||
let as_abs_string = value_string.trim_start_matches('-').to_string();
|
|
||||||
let sign = if value < &0f64 { "-" } else { "" };
|
// If it's an integer, there's nothing that would be done to it.
|
||||||
if as_abs_string.starts_with("0.5")
|
if value.fract() == 0f64 {
|
||||||
&& (as_abs_string.len() == 3 || (as_abs_string.len() > 6 && &as_abs_string[3..5] == "00"))
|
return None;
|
||||||
{
|
|
||||||
return Some(format!("{}1/2", sign));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eg. 1.33333333 to 1 + 1/3
|
if let Some(equivalent_fraction) = equivalent_fraction(value) {
|
||||||
if fract_as_string.len() >= 7 {
|
return Some(equivalent_fraction);
|
||||||
let first_five_decimals = &fract_as_string[2..7];
|
|
||||||
if first_five_decimals == "33333" || first_five_decimals == "66666" {
|
|
||||||
let fraction = match first_five_decimals {
|
|
||||||
"33333" => "1/3",
|
|
||||||
"66666" => "2/3",
|
|
||||||
_ => "?",
|
|
||||||
};
|
|
||||||
|
|
||||||
if integer == 0f64 {
|
|
||||||
return Some(format!("{}{}", sign, fraction));
|
|
||||||
} else {
|
|
||||||
let explicit_sign = if sign.is_empty() { "+" } else { "-" };
|
|
||||||
return Some(format!(
|
|
||||||
"{} {} {}",
|
|
||||||
trim_zeroes(&integer.to_string()),
|
|
||||||
explicit_sign,
|
|
||||||
fraction
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match with common numbers, eg. π, 2π/3, √2
|
// Match with common numbers, eg. π, 2π/3, √2
|
||||||
if as_abs_string.len() >= 8 {
|
if let Some(equivalent_constant) = equivalent_constant(&value.to_string()) {
|
||||||
if let Some(constant) = CONSTANTS.get(&as_abs_string[0..8]) {
|
return Some(equivalent_constant);
|
||||||
return Some(format!("{}{}", sign, constant));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the value squared (and rounded) is an integer,
|
// If the value squared (and rounded) is an integer,
|
||||||
// eg. x² is an integer,
|
// eg. x² is an integer,
|
||||||
// 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 fract != 0f64 {
|
if let Some(equivalent_root) = equivalent_root(value) {
|
||||||
let squared = KalkValue::Number(value.clone() * value, float!(0), String::new())
|
return Some(equivalent_root);
|
||||||
.round_if_needed()
|
|
||||||
.values()
|
|
||||||
.0;
|
|
||||||
if squared.clone().sqrt().fract() != 0f64 && squared.clone().fract() == 0f64 {
|
|
||||||
return Some(format!("√{}", primitive!(squared) as i32));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -138,6 +98,145 @@ pub(super) fn estimate(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn equivalent_fraction(value: f64) -> Option<String> {
|
||||||
|
fn gcd(mut a: i64, mut b: i64) -> i64 {
|
||||||
|
while a != 0 {
|
||||||
|
let old_a = a;
|
||||||
|
a = b % a;
|
||||||
|
b = old_a;
|
||||||
|
}
|
||||||
|
|
||||||
|
b.abs()
|
||||||
|
}
|
||||||
|
|
||||||
|
let original_sign = value.signum();
|
||||||
|
let value = value.abs();
|
||||||
|
let abs_value_str = value.to_string();
|
||||||
|
|
||||||
|
// https://goodcalculators.com/repeating-decimal-to-fraction-conversion-calculator/
|
||||||
|
let (mut numer, mut denom) = if let Some(repeatend_str) = find_repeatend(&abs_value_str) {
|
||||||
|
let repeatend_pos = abs_value_str.find(&repeatend_str)?;
|
||||||
|
let non_repeating_str = &abs_value_str[..repeatend_pos];
|
||||||
|
|
||||||
|
// non-repeating
|
||||||
|
let non_repeating = non_repeating_str.parse::<f64>().unwrap_or(0f64);
|
||||||
|
let non_repeating_dec_count =
|
||||||
|
non_repeating_str.len() - non_repeating_str.find('.').unwrap_or(0) - 1;
|
||||||
|
let a = non_repeating.fract();
|
||||||
|
|
||||||
|
// repeatend
|
||||||
|
let b = match repeatend_str.parse::<i64>() {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 b_numer = b as f64;
|
||||||
|
let ab_denom = nines * factor;
|
||||||
|
let integer_part_as_numer = non_repeating.trunc() * ab_denom;
|
||||||
|
|
||||||
|
(a_numer + b_numer + integer_part_as_numer, ab_denom)
|
||||||
|
} else {
|
||||||
|
const PREC: f64 = 10e10f64;
|
||||||
|
|
||||||
|
(value * PREC, PREC)
|
||||||
|
};
|
||||||
|
|
||||||
|
let gcd = gcd(numer as i64, denom as i64) as f64;
|
||||||
|
numer /= gcd;
|
||||||
|
denom /= gcd;
|
||||||
|
|
||||||
|
if denom <= 1f64 || denom >= 100f64 || denom == 10f64 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let integer_part = (numer / denom).trunc();
|
||||||
|
if integer_part > 1f64 {
|
||||||
|
numer -= integer_part * denom;
|
||||||
|
let sign = if original_sign.is_sign_positive() {
|
||||||
|
"+"
|
||||||
|
} else {
|
||||||
|
"-"
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(format!(
|
||||||
|
"{} {} {}/{}",
|
||||||
|
integer_part * original_sign,
|
||||||
|
sign,
|
||||||
|
numer.abs(),
|
||||||
|
denom.abs()
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some(format!("{}/{}", numer * original_sign, denom))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_repeatend(input: &str) -> Option<String> {
|
||||||
|
if input.len() < 10 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for len in 1..=9 {
|
||||||
|
for offset in 1..=len {
|
||||||
|
let chars: Vec<char> = input[..input.len() - offset].chars().rev().collect();
|
||||||
|
let mut chunks = chars.chunks(len);
|
||||||
|
let mut repeats = 1;
|
||||||
|
let mut prev: &[char] = chunks.next()?;
|
||||||
|
for chunk in chunks {
|
||||||
|
if chunk == prev {
|
||||||
|
repeats += 1;
|
||||||
|
} else {
|
||||||
|
repeats = 0;
|
||||||
|
prev = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
let required_repeats = match len {
|
||||||
|
1..=3 => 4,
|
||||||
|
_ => 2,
|
||||||
|
};
|
||||||
|
if repeats >= required_repeats && !prev.iter().all(|x| x == &'0') {
|
||||||
|
return Some(prev.iter().rev().cloned().collect::<String>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equivalent_constant(value_str: &str) -> Option<String> {
|
||||||
|
if value_str.trim_start_matches('-').len() < 8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (abs_value_str, sign) = if let Some(abs_value_str) = value_str.strip_prefix('-') {
|
||||||
|
(abs_value_str, "-")
|
||||||
|
} else {
|
||||||
|
(value_str, "")
|
||||||
|
};
|
||||||
|
|
||||||
|
CONSTANTS
|
||||||
|
.get(&abs_value_str[..8])
|
||||||
|
.map(|constant| format!("{}{}", sign, constant))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equivalent_root(value: f64) -> Option<String> {
|
||||||
|
if value.fract() != 0f64 {
|
||||||
|
let squared = KalkValue::Number(float!(value * value), float!(0), String::new())
|
||||||
|
.round_if_needed()
|
||||||
|
.values()
|
||||||
|
.0;
|
||||||
|
if squared.clone().sqrt().fract() != 0f64 && squared.clone().fract() == 0f64 {
|
||||||
|
return Some(format!("√{}", primitive!(squared) as i32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn round(
|
pub(super) fn round(
|
||||||
input: &KalkValue,
|
input: &KalkValue,
|
||||||
complex_number_type: ComplexNumberType,
|
complex_number_type: ComplexNumberType,
|
||||||
@ -205,3 +304,92 @@ pub(super) fn trim_zeroes(input: &str) -> String {
|
|||||||
input.into()
|
input.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_estimate() {
|
||||||
|
let in_out = vec![
|
||||||
|
(0.99999999, Some(String::from("1"))),
|
||||||
|
(-0.9999999, Some(String::from("-1"))),
|
||||||
|
(0.0000000001, Some(String::from("0"))),
|
||||||
|
(-0.000000001, Some(String::from("0"))),
|
||||||
|
(1.99999999, Some(String::from("2"))),
|
||||||
|
(-1.9999999, Some(String::from("-2"))),
|
||||||
|
(1.000000001, Some(String::from("1"))),
|
||||||
|
(-1.000001, Some(String::from("-1"))),
|
||||||
|
(0.5, Some(String::from("1/2"))),
|
||||||
|
(-0.5, Some(String::from("-1/2"))),
|
||||||
|
(0.3333333333, Some(String::from("1/3"))),
|
||||||
|
(1.3333333333, Some(String::from("4/3"))),
|
||||||
|
(-0.666666666, Some(String::from("-2/3"))),
|
||||||
|
(-1.666666666, Some(String::from("-5/3"))),
|
||||||
|
(100.33333333, Some(String::from("100 + 1/3"))),
|
||||||
|
(-100.6666666, Some(String::from("-100 - 2/3"))),
|
||||||
|
(0.9932611, None),
|
||||||
|
(-0.9932611, None),
|
||||||
|
(-0.00001, None),
|
||||||
|
(1.9932611, None),
|
||||||
|
(-1.9932611, None),
|
||||||
|
(24f64, None),
|
||||||
|
(-24f64, None),
|
||||||
|
(1.23456f64, None),
|
||||||
|
(-1.23456f64, None),
|
||||||
|
(9999999999f64, None),
|
||||||
|
(-9999999999f64, None),
|
||||||
|
(1000000001f64, None),
|
||||||
|
(-1000000001f64, None),
|
||||||
|
(0.53f64, None),
|
||||||
|
(-0.53f64, None),
|
||||||
|
(-1.51f64, None),
|
||||||
|
(0.335f64, None),
|
||||||
|
(-0.335f64, None),
|
||||||
|
(0.665f64, None),
|
||||||
|
(-0.665f64, None),
|
||||||
|
(100f64, None),
|
||||||
|
(-100f64, None),
|
||||||
|
(1f64, None),
|
||||||
|
(0.1f64, None),
|
||||||
|
(1.23f64, None),
|
||||||
|
(1.234f64, None),
|
||||||
|
(1.2345f64, None),
|
||||||
|
(1.23456f64, None),
|
||||||
|
(1.234567f64, None),
|
||||||
|
(1.2345678f64, None),
|
||||||
|
(1.23456789f64, None),
|
||||||
|
(-0.1f64, None),
|
||||||
|
(-1.23f64, None),
|
||||||
|
(-1.234f64, None),
|
||||||
|
(-1.2345f64, None),
|
||||||
|
(-1.23456f64, None),
|
||||||
|
(-1.234567f64, None),
|
||||||
|
(-1.2345678f64, None),
|
||||||
|
(-1.23456789f64, None),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, output) in in_out {
|
||||||
|
let result = KalkValue::from(input).estimate();
|
||||||
|
println!("{}", input);
|
||||||
|
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!(equivalent_fraction(0.123f64).is_none());
|
||||||
|
assert!(equivalent_fraction(1f64).is_none());
|
||||||
|
assert!(equivalent_fraction(0.01f64).is_none());
|
||||||
|
assert!(equivalent_fraction(-0.9999999f64).is_none());
|
||||||
|
assert!(equivalent_fraction(0.9999999f64).is_none());
|
||||||
|
assert!(equivalent_fraction(1.9999999f64).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user