···11# Changelog
2233+## Next
44+- Switch to the fastnum crate's d128
55+- Fix exp function
66+37## 2.0.0 - 2025 May 30
48- Remove the `degrees` keyword which referred to `celcius` by default
59- Remove the `default_degrees` argument from `eval()` and `lex()`. Not necessary now that the `degrees` keyword is removed
···11111212[List of all supported units](https://docs.rs/cpc/latest/cpc/units/enum.Unit.html)
13131414-> [!TIP]
1515-> [fend](https://github.com/printfn/fend) is a great alternative to cpc
1616-1714## CLI Installation
1815Install using `cargo`:
1916```
···2522## CLI Usage
2623```
2724cpc '2h/3 to min'
2828-```
2929-3030-## API Installation
3131-Add `cpc` as a dependency in `Cargo.toml`.
3232-3333-## API Usage
3434-3535-```rust
3636-use cpc::eval;
3737-use cpc::units::Unit;
3838-3939-match eval("3m + 1cm", true, Unit::Celsius, false) {
4040- Ok(answer) => {
4141- // answer: Number { value: 301, unit: Unit::Centimeter }
4242- println!("Evaluated value: {} {:?}", answer.value, answer.unit)
4343- },
4444- Err(e) => {
4545- println!("{e}")
4646- }
4747-}
4825```
49265027## Examples
···8461- Speed
8562- Temperature
86638787-## Accuracy
8888-cpc uses 128-bit Decimal Floating Point (d128) numbers instead of Binary Coded Decimals for better accuracy. The result cpc gives will still not always be 100% accurate. I would recommend rounding the result to 20 decimals or less.
6464+## API Installation
6565+Add `cpc` as a dependency in `Cargo.toml`.
6666+6767+## API Usage
89689090-## Performance
9191-It's pretty fast and scales well. In my case, it usually runs in under 0.1ms. The biggest performance hit is functions like `log()`. `log(12345)` evaluates in 0.12ms, and `log(e)` in 0.25ms.
6969+```rust
7070+use cpc::eval;
7171+use cpc::units::Unit;
92729393-To see how fast it is, you can pass the `--verbose` flag in CLI, or the `verbose` argument to `eval()`.
7373+match eval("3m + 1cm", true, Unit::Celsius, false) {
7474+ Ok(answer) => {
7575+ // answer: Number { value: 301, unit: Unit::Centimeter }
7676+ println!("Evaluated value: {} {:?}", answer.value, answer.unit)
7777+ },
7878+ Err(e) => {
7979+ println!("{e}")
8080+ }
8181+}
8282+```
8383+8484+## Accuracy
8585+cpc uses 128-bit Decimal Floating Point (d128) numbers instead of Binary Coded Decimals for better accuracy. The result cpc gives will still not always be 100% accurate. I would recommend rounding the result to 20 decimals or less.
94869587## Dev Instructions
9688···165157166158### Potential Improvements
167159- Support for conversion between Power, Current, Resistance and Voltage. Multiplication and division is currently supported, but not conversions using sqrt or pow.
168168-- Move to pure-rust decimal implementation
169169- - `rust_decimal`: Only supports numbers up to ~1E+29
170170- - `bigdecimal`: Lacking math functions
171160- E notation, like 2E+10
172161- Unit types
173162 - Currency: How to go about dynamically updating the weights?
+40-76
src/evaluator.rs
···77use crate::TextOperator::{Of, To};
88use crate::UnaryOperator::{Factorial, Percent};
99use crate::{Number, Token};
1010-use decimal::d128;
1010+use fastnum::{dec128 as d, D128};
11111212/// Evaluate an [`AstNode`] into a [`Number`]
1313pub fn evaluate(ast: &AstNode) -> Result<Number, String> {
···2020/// Factorials do not work with decimal numbers.
2121///
2222/// All return values of this function are hard-coded.
2323-pub fn factorial(input: d128) -> d128 {
2424- lookup_factorial(input.into())
2323+pub fn factorial(input: D128) -> D128 {
2424+ lookup_factorial(input.try_into().unwrap())
2525}
26262727/// Returns the square root of a [`struct@d128`]
2828-pub fn sqrt(input: d128) -> d128 {
2929- let mut n = d128!(1);
3030- let half = d128!(0.5);
2828+pub fn sqrt(input: D128) -> D128 {
2929+ let mut n = d!(1);
3030+ let half = d!(0.5);
3131 for _ in 0..10 {
3232 n = (n + input / n) * half;
3333 }
···3535}
36363737/// Returns the cube root of a [`struct@d128`]
3838-pub fn cbrt(input: d128) -> d128 {
3939- let mut n: d128 = input;
3838+pub fn cbrt(input: D128) -> D128 {
3939+ let mut n: D128 = input;
4040 // hope that 20 iterations makes it accurate enough
4141- let three = d128!(3);
4141+ let three = d!(3);
4242 for _ in 0..20 {
4343 let z2 = n * n;
4444 n = n - ((n * z2 - input) / (three * z2));
···4646 n
4747}
48484949-fn pi() -> d128 {
5050- d128!(3.14159265358979323846264338327950288)
5151-}
5252-fn pi2() -> d128 {
5353- d128!(6.283185307179586476925286766559005768)
5454-}
5555-fn pi_half() -> d128 {
5656- d128!(1.570796326794896619231321691639751)
5757-}
5858-fn eulers_number() -> d128 {
5959- d128!(2.718281828459045235360287471352662)
6060-}
6161-fn rounding_base() -> d128 {
6262- // DO NOT add trailing zeroes
6363- d128!(0.0000000000000000000000000000001)
6464-}
6565-6649/// Returns the sine of a [`struct@d128`]
6767-pub fn sin(mut input: d128) -> d128 {
6868- input %= pi2();
6969-7070- let negative_correction = if input.is_negative() {
7171- input -= pi();
7272- d128!(-1)
7373- } else {
7474- d128!(1)
7575- };
7676-7777- let one = d128!(1);
7878- let two = d128!(2);
7979- let neg_one = -one;
8080-8181- let precision = 37;
8282- let mut result = d128!(0);
8383- for i_int in 0..precision {
8484- let i = d128::from(i_int);
8585- let calc_result = two * i + one;
8686- result += neg_one.pow(i) * (input.pow(calc_result) / factorial(calc_result));
5050+pub fn sin(input: D128) -> D128 {
5151+ let result =input.sin();
5252+ match result.is_zero() {
5353+ true => D128::ZERO,
5454+ false => result,
8755 }
8888-8989- let unrounded_result = negative_correction * result;
9090-9191- // This uses bankers rounding
9292- unrounded_result.quantize(rounding_base())
9356}
94579558/// Returns the cosine of a [`struct@d128`]
9696-pub fn cos(input: d128) -> d128 {
9797- sin(pi_half() - input)
5959+pub fn cos(input: D128) -> D128 {
6060+ input.cos()
9861}
996210063/// Returns the tangent of a [`struct@d128`]
101101-pub fn tan(input: d128) -> d128 {
102102- sin(input) / cos(input)
6464+pub fn tan(input: D128) -> D128 {
6565+ input.tan()
10366}
1046710568/// Evaluate an [`AstNode`] into a [`Number`]
···11073 Token::Number(number) => Ok(Number::new(*number, Unit::NoUnit)),
11174 Token::Constant(constant) => match constant {
11275 Pi => Ok(Number::new(
113113- pi(),
7676+ D128::PI,
11477 Unit::NoUnit,
11578 )),
11679 E => Ok(Number::new(
117117- eulers_number(),
8080+ D128::E,
11881 Unit::NoUnit,
11982 )),
12083 },
···148111 }
149112 Ln => {
150113 if child_answer.unit.category() == UnitType::NoType {
151151- let unrounded_result = child_answer.value.ln();
152152- let result = unrounded_result.quantize(unrounded_result * d128!(10));
114114+ let result = child_answer.value.ln();
153115 Ok(Number::new(result, child_answer.unit))
154116 } else {
155117 Err("ln() only accepts UnitType::NoType".to_string())
···157119 }
158120 Exp => {
159121 if child_answer.unit.category() == UnitType::NoType {
160160- let result = child_answer.value.exp(child_answer.value);
122122+ let result = child_answer.value.exp();
161123 Ok(Number::new(result, child_answer.unit))
162124 } else {
163125 Err("exp() only accepts UnitType::NoType".to_string())
···165127 }
166128 Round => {
167129 // .quantize() rounds .5 to nearest even integer, so we correct that
168168- let mut result = child_answer.value.quantize(d128!(1));
130130+ let mut result = child_answer.value.quantize(d!(1));
169131 let rounding_change = result - child_answer.value;
170132 // If the result was rounded down by 0.5, correct by +1
171171- if rounding_change == d128!(-0.5) {
172172- result += d128!(1);
133133+ if rounding_change == d!(-0.5) {
134134+ result += d!(1);
173135 }
174136 Ok(Number::new(result, child_answer.unit))
175137 }
176138 Ceil => {
177177- let mut result = child_answer.value.quantize(d128!(1));
139139+ let mut result = child_answer.value.quantize(d!(1));
178140 let rounding_change = result - child_answer.value;
179141 if rounding_change.is_negative() {
180180- result += d128!(1);
142142+ result += d!(1);
181143 }
182144 Ok(Number::new(result, child_answer.unit))
183145 }
184146 Floor => {
185185- let mut result = child_answer.value.quantize(d128!(1));
147147+ let mut result = child_answer.value.quantize(d!(1));
186148 let rounding_change = result - child_answer.value;
187149 if !rounding_change.is_negative() {
188188- result -= d128!(1);
150150+ result -= d!(1);
189151 }
190152 Ok(Number::new(result, child_answer.unit))
191153 }
192154 Abs => {
193155 let mut result = child_answer.value.abs();
194156 let rounding_change = result - child_answer.value;
195195- if rounding_change == d128!(-0.5) {
196196- result += d128!(1);
157157+ if rounding_change == d!(-0.5) {
158158+ result += d!(1);
197159 }
198160 Ok(Number::new(result, child_answer.unit))
199161 }
···232194 let child_answer = evaluate_node(child_node)?;
233195 match operator {
234196 Percent => Ok(Number::new(
235235- child_answer.value / d128!(100),
197197+ child_answer.value / d!(100),
236198 child_answer.unit,
237199 )),
238200 Factorial => {
···326288 let result = eval(input, true, false).unwrap();
327289 assert_eq!(result.unit, Unit::NoUnit);
328290329329- result.get_simplified_value().to_string()
291291+ result.to_string()
330292 }
331293332294 #[test]
···334296 assert_eq!(eval_num("-2(-3)"), "6");
335297 assert_eq!(eval_num("-2(3)"), "-6");
336298 assert_eq!(eval_num("(3)-2"), "1");
337337- assert_eq!(eval_default("-1km to m"), Number::new(d128!(-1000), Unit::Meter));
299299+ assert_eq!(eval_default("-1km to m"), Number::new(d!(-1000), Unit::Meter));
338300 assert_eq!(eval_num("2*-3*0.5"), "-3");
339301 assert_eq!(eval_num("-3^2"), "-9");
340302 assert_eq!(eval_num("-1+2"), "1");
···347309 assert_eq!(eval_num("sqrt(25)"), "5");
348310349311 assert_eq!(eval_num("log(100)"), "2");
350350- assert_eq!(eval_num("log(2)"), "0.301029995663981195213738894724493");
312312+ assert_eq!(eval_num("log(2)"), "0.301029995663981195213738894724493026768");
351313352314 assert_eq!(eval_num("ln(1)"), "0");
353353- assert_eq!(eval_num("ln(2)"), "0.693147180559945309417232121458177");
315315+ assert_eq!(eval_num("ln(2)"), "0.69314718055994530941723212145817656808");
354316 assert_eq!(eval_num("ln(e)"), "1");
355317 assert_eq!(eval_num("ln(e^2)"), "2");
318318+319319+ assert_eq!(eval_num("exp(1)"), "2.71828182845904523536028747135266249776");
356320357321 assert_eq!(eval_num("round(1.4)"), "1");
358322 assert_eq!(eval_num("round(1.6)"), "2");
···367331368332 assert_eq!(eval_num("abs(-3)"), "3");
369333370370- assert_eq!(eval_num("sin(2)"), "0.9092974268256816953960198659117");
371371- assert_eq!(eval_num("sin(-2)"), "-0.9092974268256816953960198659117");
334334+ assert_eq!(eval_num("sin(2)"), "0.9092974268256816953960198659117448427");
335335+ assert_eq!(eval_num("sin(-2)"), "-0.9092974268256816953960198659117448427");
372336 }
373337}