mirror of
https://github.com/PaddiM8/kalker.git
synced 2025-01-19 03:38:13 +01:00
Estimation/rounding for final results
This commit is contained in:
parent
8399d810d8
commit
c2577ef477
49
README.md
49
README.md
@ -1,18 +1,15 @@
|
||||
# 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).
|
||||
|
||||
[Kanban](https://kolan.strct.net/Board/4RAdMjLDz) | [Website](https://kalk.strct.net)
|
||||
|
||||
![](example.png)
|
||||
|
||||
## Features
|
||||
|
||||
* Operators: +, -, \*, /, !
|
||||
* Groups: (), ⌈⌉, ⌋⌊
|
||||
* [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
|
||||
|
||||
There are currently three different libraries related to kalk.
|
||||
|
||||
* [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-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
|
||||
|
||||
### Binaries
|
||||
|
||||
Pre-compiled binaries for Linux, Windows, and macOS (64-bit) are available in the [releases page](https://github.com/PaddiM8/kalk/releases).
|
||||
|
||||
### 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).
|
||||
|
||||
#### Cargo
|
||||
|
||||
Run `cargo install kalk_cli`
|
||||
|
||||
#### Manually
|
||||
|
||||
1. Go into the `kalk_cli` directory.
|
||||
2. Run `cargo build --release`
|
||||
3. Grab the binary from `targets/release`
|
||||
|
||||
## Syntax
|
||||
|
||||
A more complete reference can be found on [the website](https://kalk.strct.net)
|
||||
|
||||
### Functions
|
||||
__Defining:__ name(parameter1, parameter2, ...) = expression
|
||||
**Example:** `f(x) = 2x+3`
|
||||
|
||||
__Using:__ name(argument1, argument2)
|
||||
**Example:** `f(2)`
|
||||
**Defining:** name(parameter1, parameter2, ...) = expression\
|
||||
**Example:** `f(x) = 2x+3`
|
||||
|
||||
**Using:** name(argument1, argument2)\
|
||||
**Example:** `f(2)`
|
||||
|
||||
### Variables
|
||||
__Defining:__ name = expression
|
||||
**Example:** `x = 3`
|
||||
|
||||
__Using:__ name
|
||||
**Example:** `x`
|
||||
**Defining:** name = expression\
|
||||
**Example:** `x = 3`
|
||||
|
||||
**Using:** name\
|
||||
**Example:** `x`
|
||||
|
||||
### 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
|
||||
**Example:** `unit deg = (rad*180)/π`
|
||||
|
||||
__Using:__ Use them freely in expressions.
|
||||
**Example:** `2m/50cm`
|
||||
*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)/π`
|
||||
|
||||
__Converting:__ expression `to` unit
|
||||
**Example:** `2 m to cm`
|
||||
**Using:** Use them freely in expressions.\
|
||||
**Example:** `2m/50cm`
|
||||
|
||||
**Converting:** expression `to` unit\
|
||||
**Example:** `2 m to cm`
|
@ -22,9 +22,10 @@ pub fn derive_func(
|
||||
let f_x =
|
||||
interpreter::eval_fn_call_expr(context, &new_identifier, &[argument_without_h], unit)?;
|
||||
|
||||
Ok(round(
|
||||
f_x_h.sub(context, f_x).div(context, (2f64 * H).into()),
|
||||
))
|
||||
Ok(f_x_h
|
||||
.sub(context, f_x)
|
||||
.div(context, (2f64 * H).into())
|
||||
.round_if_needed())
|
||||
}
|
||||
|
||||
pub fn integrate(
|
||||
@ -55,13 +56,7 @@ pub fn integrate(
|
||||
Box::new(Expr::Literal(1f64)),
|
||||
));
|
||||
|
||||
Ok(round(simpsons_rule(
|
||||
context,
|
||||
a,
|
||||
b,
|
||||
expr,
|
||||
integration_variable.unwrap(),
|
||||
)?))
|
||||
Ok(simpsons_rule(context, a, b, expr, integration_variable.unwrap())?.round_if_needed())
|
||||
}
|
||||
|
||||
/// Composite Simpson's 3/8 rule
|
||||
@ -75,8 +70,8 @@ fn simpsons_rule(
|
||||
let mut result = KalkNum::default();
|
||||
|
||||
const N: i32 = 900;
|
||||
let a = interpreter::eval_expr(context, a_expr, "")?.value.to_f64();
|
||||
let b = interpreter::eval_expr(context, b_expr, "")?.value.to_f64();
|
||||
let a = interpreter::eval_expr(context, a_expr, "")?.to_f64();
|
||||
let b = interpreter::eval_expr(context, b_expr, "")?.to_f64();
|
||||
let h = (b - a) / N as f64;
|
||||
for i in 0..=N {
|
||||
context.symbol_table.set(Stmt::VarDecl(
|
||||
@ -91,33 +86,10 @@ fn simpsons_rule(
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
#[cfg(not(feature = "rug"))]
|
||||
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()
|
||||
};
|
||||
|
||||
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!(""),
|
||||
Err(err) => print_err(&err.to_string()),
|
||||
|
Loading…
Reference in New Issue
Block a user