CPC/README.md
2020-08-20 06:49:10 +02:00

143 lines
3.5 KiB
Markdown

# cpc
calculation + conversion
cpc parses and evaluates strings of math, with support for units and conversion. 128-bit decimal floating points are used for high accuracy.
cpc lets you mix units, so for example `1 km - 1m` results in `Number { value: 999, unit: Meter }`.
## Usage
```rs
use cpc::{eval, Unit::*}
match eval("3m + 1cm", true, Celcius) {
Ok(answer) => {
// answer: Number { value: 301, unit: Unit::cm }
println!("Evaluated value: {} {:?}", answer.value, answer.unit)
},
Err(e) => {
println!(e)
}
}
```
## Examples
```
3 + 4 * 2
8 % 3
(4 + 1)km to light years
10m/2s * 5s
1 lightyear * 0.001mm in km2
1m/s + 1mi/h in kilometers per h
round(sqrt(2)^4)! liters
10% of abs(sin(pi)) horsepower to watts
```
## Supported unit types
- Normal numbers
- Time
- Length
- Area
- Volume
- Mass
- Digital storage (bytes etc)
- Energy
- Power
- Pressure
- Speed
- Temperature
## Accuracy
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.
## Performance
In my case, I can expect `eval()` to take 100-200ms, and this scales pretty alright. However, putting numbers with a lot of digits into functions result in pretty poor performance. `log(e)` is one of the worst, and takes 500ms for me.
## Errors
cpc returns `Result`s with basic strings as errors. Just to be safe, you may want to handle panics (You can do that using `std::panic::catch_unwind`).
# Dev Instructions
## Get started
Install [Rust](https://www.rust-lang.org). This project was built in Rust 1.40.
## Commands
Run cpc with a CLI argument as input:
```
cargo run -- '100ms to s'
```
Run with debugging, which shows some extra logs:
```
cargo run -- '100ms to s' --debug
```
Run tests:
```
cargo test
```
## Adding a unit
### 1. Add the unit
In `src/units.rs`, units are specified like this:
```rs
pub enum UnitType {
Time,
// etc
}
// ...
create_units!(
Nanosecond: (Time, d128!(1)),
Microsecond: (Time, d128!(1000)),
// etc
)
```
The number associated with a unit is it's "weight". For example, if a second's weight is `1`, then a minute's weight is `1000`.
I have found [translatorscafe.com](https://www.translatorscafe.com/unit-converter) and [calculateme.com](https://www.calculateme.com/) to be good websites for unit convertion. Wikipedia is worth looking at as well.
### 2. Add a test for the unit
Make sure to also add a test for each unit. The tests look like this:
```rs
assert_eq!(convert_test(1000.0, Meter, Kilometer), 1.0);
```
Basically, 1000 Meter == 1 Kilometer.
### 3. Add the unit to the lexer
Text is turned into tokens (some of which are units) in `lexer.rs`. Here's one example:
```rs
// ...
match string {
"h" | "hr" | "hrs" | "hour" | "hours" => tokens.push(Token::Unit(Hour)),
// etc
}
// ...
```
## Potential Improvements
### General
- Support for math in `6'4"` syntax, like `3'+2'4"`. Currently needs to be written like `3'+3'+4"`
- The functions in units.rs have a lot of manual if statements. This could probably be replaced with a pretty advanced macro.
- Support for lexing words, like `one billion`
### Potential unit types
Nice list of units: https://support.google.com/websearch/answer/3284611
- Currency: How would you go about dynamically updating the weights?
- Fuel consumption
- Data transfer rate
- Color codes
- Force
- Roman numerals
- Angles
- Electric current, capacitance, charge, conductance, volts
- Flow rate
- Frequency