mirror of
https://github.com/PaddiM8/kalker.git
synced 2025-01-31 17:09:13 +01:00
Estimation/rounding for final results
This commit is contained in:
parent
8399d810d8
commit
c2577ef477
33
README.md
33
README.md
@ -1,10 +1,6 @@
|
|||||||
# kalk
|
# kalk
|
||||||
[![Crates.io](https://img.shields.io/crates/v/kalk_cli)](https://crates.io/crates/kalk_cli)
|
|
||||||
![npm](https://img.shields.io/npm/v/@paddim8/kalk)
|
|
||||||
[![GitHub](https://img.shields.io/github/license/PaddiM8/kalk)](https://github.com/PaddiM8/kalk/blob/master/LICENSE)
|
|
||||||
[![Docs.rs](https://docs.rs/kalk/badge.svg)](https://docs.rs/kalk/latest/kalk/)
|
|
||||||
![Build status](https://img.shields.io/github/workflow/status/PaddiM8/kalk/Rust?event=push&label=build%20%26%20test)
|
|
||||||
|
|
||||||
|
[![Crates.io](https://img.shields.io/crates/v/kalk_cli)](https://crates.io/crates/kalk_cli)![npm](https://img.shields.io/npm/v/@paddim8/kalk) [![GitHub](https://img.shields.io/github/license/PaddiM8/kalk)](https://github.com/PaddiM8/kalk/blob/master/LICENSE) [![Docs.rs](https://docs.rs/kalk/badge.svg)](https://docs.rs/kalk/latest/kalk/) ![Build status](https://img.shields.io/github/workflow/status/PaddiM8/kalk/Rust?event=push&label=build%20%26%20test)
|
||||||
|
|
||||||
Kalk is a calculator (both program and library) that supports user-defined variables, functions, and units (experimental, limited). It runs on Windows, macOS, Linux, Android, and in web browsers (with WebAssembly).
|
Kalk is a calculator (both program and library) that supports user-defined variables, functions, and units (experimental, limited). It runs on Windows, macOS, Linux, Android, and in web browsers (with WebAssembly).
|
||||||
|
|
||||||
@ -13,6 +9,7 @@ Kalk is a calculator (both program and library) that supports user-defined varia
|
|||||||
![](example.png)
|
![](example.png)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Operators: +, -, \*, /, !
|
* Operators: +, -, \*, /, !
|
||||||
* Groups: (), ⌈⌉, ⌋⌊
|
* Groups: (), ⌈⌉, ⌋⌊
|
||||||
* [Pre-defined functions and constants](https://github.com/PaddiM8/kalk/blob/master/kalk/src/prelude.rs)
|
* [Pre-defined functions and constants](https://github.com/PaddiM8/kalk/blob/master/kalk/src/prelude.rs)
|
||||||
@ -28,48 +25,58 @@ Kalk is a calculator (both program and library) that supports user-defined varia
|
|||||||
## Libraries
|
## Libraries
|
||||||
|
|
||||||
There are currently three different libraries related to kalk.
|
There are currently three different libraries related to kalk.
|
||||||
|
|
||||||
* [kalk](https://crates.io/crates/kalk): The Rust crate that powers it all.
|
* [kalk](https://crates.io/crates/kalk): The Rust crate that powers it all.
|
||||||
* [@paddim8/kalk](https://www.npmjs.com/package/@paddim8/kalk): JavaScript bindings to `kalk`. This lets you use it in the browser, thanks to WebAssembly.
|
* [@paddim8/kalk](https://www.npmjs.com/package/@paddim8/kalk): JavaScript bindings to `kalk`. This lets you use it in the browser, thanks to WebAssembly.
|
||||||
* [@paddim8/kalk-component](https://www.npmjs.com/package/@paddim8/kalk-component): A web component that acts as a frontend to `@paddim8/kalk`, which lets you use kalk in the browser with a command line-like interface.
|
* [@paddim8/kalk-component](https://www.npmjs.com/package/@paddim8/kalk-component): A web component that acts as a frontend to `@paddim8/kalk`, which lets you use kalk in the browser with a command line-like interface.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Binaries
|
### Binaries
|
||||||
|
|
||||||
Pre-compiled binaries for Linux, Windows, and macOS (64-bit) are available in the [releases page](https://github.com/PaddiM8/kalk/releases).
|
Pre-compiled binaries for Linux, Windows, and macOS (64-bit) are available in the [releases page](https://github.com/PaddiM8/kalk/releases).
|
||||||
|
|
||||||
### Compiling
|
### Compiling
|
||||||
|
|
||||||
**Minimum rust version: v1.36.0**. Make sure you have `diffutils` `gcc` `make` and `m4` installed. **If you use windows:** [follow the instructions here](https://docs.rs/gmp-mpfr-sys/1.2.3/gmp_mpfr_sys/index.html#building-on-windows) (don't forget to install `mingw-w64-x86_64-rust` in MSYS2).
|
**Minimum rust version: v1.36.0**. Make sure you have `diffutils` `gcc` `make` and `m4` installed. **If you use windows:** [follow the instructions here](https://docs.rs/gmp-mpfr-sys/1.2.3/gmp_mpfr_sys/index.html#building-on-windows) (don't forget to install `mingw-w64-x86_64-rust` in MSYS2).
|
||||||
|
|
||||||
#### Cargo
|
#### Cargo
|
||||||
|
|
||||||
Run `cargo install kalk_cli`
|
Run `cargo install kalk_cli`
|
||||||
|
|
||||||
#### Manually
|
#### Manually
|
||||||
|
|
||||||
1. Go into the `kalk_cli` directory.
|
1. Go into the `kalk_cli` directory.
|
||||||
2. Run `cargo build --release`
|
2. Run `cargo build --release`
|
||||||
3. Grab the binary from `targets/release`
|
3. Grab the binary from `targets/release`
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
A more complete reference can be found on [the website](https://kalk.strct.net)
|
A more complete reference can be found on [the website](https://kalk.strct.net)
|
||||||
|
|
||||||
### Functions
|
### Functions
|
||||||
__Defining:__ name(parameter1, parameter2, ...) = expression
|
|
||||||
|
**Defining:** name(parameter1, parameter2, ...) = expression\
|
||||||
**Example:** `f(x) = 2x+3`
|
**Example:** `f(x) = 2x+3`
|
||||||
|
|
||||||
__Using:__ name(argument1, argument2)
|
**Using:** name(argument1, argument2)\
|
||||||
**Example:** `f(2)`
|
**Example:** `f(2)`
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
__Defining:__ name = expression
|
|
||||||
|
**Defining:** name = expression\
|
||||||
**Example:** `x = 3`
|
**Example:** `x = 3`
|
||||||
|
|
||||||
__Using:__ name
|
**Using:** name\
|
||||||
**Example:** `x`
|
**Example:** `x`
|
||||||
|
|
||||||
### Units (experimental, are likely to not work properly)
|
### Units (experimental, are likely to not work properly)
|
||||||
*Note: You only need to define the relationship between two units once. You will be able to convert between both of them.*
|
|
||||||
__Defining:__ `unit` name = expression
|
*Note: You only need to define the relationship between two units once. You will be able to convert between both of them.* **Defining:** `unit` name = expression\
|
||||||
**Example:** `unit deg = (rad*180)/π`
|
**Example:** `unit deg = (rad*180)/π`
|
||||||
|
|
||||||
__Using:__ Use them freely in expressions.
|
**Using:** Use them freely in expressions.\
|
||||||
**Example:** `2m/50cm`
|
**Example:** `2m/50cm`
|
||||||
|
|
||||||
__Converting:__ expression `to` unit
|
**Converting:** expression `to` unit\
|
||||||
**Example:** `2 m to cm`
|
**Example:** `2 m to cm`
|
@ -22,9 +22,10 @@ pub fn derive_func(
|
|||||||
let f_x =
|
let f_x =
|
||||||
interpreter::eval_fn_call_expr(context, &new_identifier, &[argument_without_h], unit)?;
|
interpreter::eval_fn_call_expr(context, &new_identifier, &[argument_without_h], unit)?;
|
||||||
|
|
||||||
Ok(round(
|
Ok(f_x_h
|
||||||
f_x_h.sub(context, f_x).div(context, (2f64 * H).into()),
|
.sub(context, f_x)
|
||||||
))
|
.div(context, (2f64 * H).into())
|
||||||
|
.round_if_needed())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn integrate(
|
pub fn integrate(
|
||||||
@ -55,13 +56,7 @@ pub fn integrate(
|
|||||||
Box::new(Expr::Literal(1f64)),
|
Box::new(Expr::Literal(1f64)),
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(round(simpsons_rule(
|
Ok(simpsons_rule(context, a, b, expr, integration_variable.unwrap())?.round_if_needed())
|
||||||
context,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
expr,
|
|
||||||
integration_variable.unwrap(),
|
|
||||||
)?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Composite Simpson's 3/8 rule
|
/// Composite Simpson's 3/8 rule
|
||||||
@ -75,8 +70,8 @@ fn simpsons_rule(
|
|||||||
let mut result = KalkNum::default();
|
let mut result = KalkNum::default();
|
||||||
|
|
||||||
const N: i32 = 900;
|
const N: i32 = 900;
|
||||||
let a = interpreter::eval_expr(context, a_expr, "")?.value.to_f64();
|
let a = interpreter::eval_expr(context, a_expr, "")?.to_f64();
|
||||||
let b = interpreter::eval_expr(context, b_expr, "")?.value.to_f64();
|
let b = interpreter::eval_expr(context, b_expr, "")?.to_f64();
|
||||||
let h = (b - a) / N as f64;
|
let h = (b - a) / N as f64;
|
||||||
for i in 0..=N {
|
for i in 0..=N {
|
||||||
context.symbol_table.set(Stmt::VarDecl(
|
context.symbol_table.set(Stmt::VarDecl(
|
||||||
@ -91,33 +86,10 @@ fn simpsons_rule(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// factor * f(x_n)
|
// factor * f(x_n)
|
||||||
result.value += factor * interpreter::eval_expr(context, expr, "")?.value;
|
result.value += factor as f64 * interpreter::eval_expr(context, expr, "")?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.value *= (3f64 * h) / 8f64;
|
result.value *= (3f64 * h) / 8f64;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc.
|
|
||||||
fn round(num: KalkNum) -> KalkNum {
|
|
||||||
let fract = num.value.clone().fract();
|
|
||||||
let floored = num.value.clone().floor();
|
|
||||||
|
|
||||||
// If it's zero something, don't do the rounding as aggressively.
|
|
||||||
let (limit_floor, limit_ceil) = if floored.clone() == 0 {
|
|
||||||
(-15, -5)
|
|
||||||
} else {
|
|
||||||
(-4, -6)
|
|
||||||
};
|
|
||||||
|
|
||||||
if fract.clone().log10() < limit_floor {
|
|
||||||
// If eg. 0.00xxx
|
|
||||||
return KalkNum::new(floored, &num.unit);
|
|
||||||
} else if (1f64 - fract).log10() < limit_ceil {
|
|
||||||
// If eg. 0.999
|
|
||||||
return KalkNum::new(num.value.clone().ceil(), &num.unit);
|
|
||||||
} else {
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,3 +7,201 @@ pub use with_rug::*;
|
|||||||
pub mod regular;
|
pub mod regular;
|
||||||
#[cfg(not(feature = "rug"))]
|
#[cfg(not(feature = "rug"))]
|
||||||
pub use regular::*;
|
pub use regular::*;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CONSTANTS: HashMap<&'static str, &'static str> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert("3.141592", "π");
|
||||||
|
m.insert("2.718281", "e");
|
||||||
|
m.insert("6.283185", "tau");
|
||||||
|
m.insert("6.283185", "τ");
|
||||||
|
m.insert("1.618033", "phi");
|
||||||
|
m.insert("1.618033", "ϕ");
|
||||||
|
m.insert("1.414213", "√2");
|
||||||
|
// Radian values for common angles
|
||||||
|
m.insert("0.523598", "π/6");
|
||||||
|
m.insert("0.785398", "π/4");
|
||||||
|
m.insert("1.047197", "π/3");
|
||||||
|
m.insert("1.570796", "π/2");
|
||||||
|
m.insert("2.094395", "2π/3");
|
||||||
|
m.insert("2.356194", "3π/4");
|
||||||
|
m.insert("2.617993", "5π/6");
|
||||||
|
m.insert("3.665191", "7π/6");
|
||||||
|
m.insert("3.926990", "5π/4");
|
||||||
|
m.insert("4.188790", "4π/3");
|
||||||
|
m.insert("4.712388", "3π/2");
|
||||||
|
m.insert("5.23598", "5π/3");
|
||||||
|
m.insert("5.497787", "7π/4");
|
||||||
|
m.insert("5.759586", "11π/6");
|
||||||
|
m.insert("6.283185", "2π");
|
||||||
|
m.insert("0.866025", "√3/2");
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KalkNum {
|
||||||
|
// Get an estimate of what the number is, eg. 3.141592 => π
|
||||||
|
pub fn estimate(&self) -> Option<String> {
|
||||||
|
let fract = self.value.clone().fract().abs();
|
||||||
|
let integer = self.value.clone().trunc();
|
||||||
|
|
||||||
|
// If it's an integer, there's nothing that would be done to it.
|
||||||
|
if fract == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eg. 0.5 to 1/2
|
||||||
|
let as_abs_string = self.to_string().trim_start_matches("-").to_string();
|
||||||
|
let sign = if self.value < 0 { "-" } else { "" };
|
||||||
|
let fract_as_string = fract.to_string();
|
||||||
|
if as_abs_string.starts_with("0.5") {
|
||||||
|
if as_abs_string.len() == 3 || (as_abs_string.len() > 6 && &as_abs_string[3..5] == "00")
|
||||||
|
{
|
||||||
|
return Some(format!("{}1/2", sign));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eg. 1.33333333 to 1 + 1/3
|
||||||
|
if fract_as_string.len() >= 5 {
|
||||||
|
let first_five_decimals = &fract_as_string[2..7];
|
||||||
|
if first_five_decimals == "33333" || first_five_decimals == "66666" {
|
||||||
|
let fraction = match first_five_decimals.as_ref() {
|
||||||
|
"33333" => "1/3",
|
||||||
|
"66666" => "2/3",
|
||||||
|
_ => "?",
|
||||||
|
};
|
||||||
|
|
||||||
|
if integer == 0 {
|
||||||
|
return Some(format!("{}{}", sign, fraction));
|
||||||
|
} else {
|
||||||
|
let explicit_sign = if sign == "" { "+" } else { "-" };
|
||||||
|
return Some(format!(
|
||||||
|
"{} {} {}",
|
||||||
|
trim_zeroes(&integer.to_string()),
|
||||||
|
explicit_sign,
|
||||||
|
fraction
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match with common numbers, eg. π, 2π/3, √2
|
||||||
|
if as_abs_string.len() >= 8 {
|
||||||
|
if let Some(constant) = CONSTANTS.get(&as_abs_string[0..8]) {
|
||||||
|
return Some(format!("{}{}", sign, constant.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing above was relevant, simply round it off a bit, eg. from 0.99999 to 1
|
||||||
|
let rounded = self.round()?.to_string();
|
||||||
|
Some(trim_zeroes(&rounded))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc.
|
||||||
|
pub fn round(&self) -> Option<KalkNum> {
|
||||||
|
let sign = if self.value < 0 { -1 } else { 1 };
|
||||||
|
let fract = self.value.clone().abs().fract();
|
||||||
|
let integer = self.value.clone().abs().trunc();
|
||||||
|
|
||||||
|
// If it's zero something, don't do the rounding as aggressively.
|
||||||
|
let (limit_floor, limit_ceil) = if integer == 0f64 {
|
||||||
|
(-8f64, -5f64)
|
||||||
|
} else {
|
||||||
|
(-4f64, -6f64)
|
||||||
|
};
|
||||||
|
|
||||||
|
if fract.clone().log10() < limit_floor {
|
||||||
|
// If eg. 0.00xxx
|
||||||
|
Some(KalkNum::new(integer * sign, &self.unit))
|
||||||
|
} else if (1f64 - fract.clone()).log10() < limit_ceil {
|
||||||
|
// If eg. 0.999
|
||||||
|
// .abs() this before ceiling to make sure it rounds correctly. The sign is re-added afterwards.
|
||||||
|
Some(KalkNum::new(
|
||||||
|
self.value.clone().abs().ceil() * sign,
|
||||||
|
&self.unit,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn round_if_needed(&self) -> KalkNum {
|
||||||
|
if let Some(value) = self.round() {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
self.clone() // Hmm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim_zeroes(input: &str) -> String {
|
||||||
|
if input.contains(".") {
|
||||||
|
input
|
||||||
|
.trim_end_matches("0")
|
||||||
|
.trim_end_matches(".")
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
input.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::kalk_num::KalkNum;
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, output) in in_out {
|
||||||
|
let result = KalkNum::from(input).estimate();
|
||||||
|
println!("{}", input);
|
||||||
|
assert_eq!(output, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,16 @@ pub fn eval(parser: &mut parser::Context, input: &str, precision: u32) {
|
|||||||
result.to_string_big()
|
result.to_string_big()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{} {}", result_str, result.get_unit());
|
let unit = result.get_unit();
|
||||||
|
if let Some(estimate) = result.estimate() {
|
||||||
|
if unit == "" {
|
||||||
|
println!("{} ≈ {}", result_str, estimate);
|
||||||
|
} else {
|
||||||
|
println!("{} {} ≈ {}", result_str, unit, estimate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("{} {}", result_str, unit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => print!(""),
|
Ok(None) => print!(""),
|
||||||
Err(err) => print_err(&err.to_string()),
|
Err(err) => print_err(&err.to_string()),
|
||||||
|
Loading…
Reference in New Issue
Block a user