Text calculator with support for units and conversion

Overview

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 }.

Crates.io Documentation

List of all supported units

CLI Installation

To install the CLI using cargo:

cargo install cpc

To install the CLI directly, grab the appropriate binary from cpc's Releases page on GitHub, then place it wherever you normally place binaries on your OS.

CLI Usage

cpc '20c to f'

If you installed the binary somewhere that doesn't make binaries global, you would need to specify the path:

/usr/local/bin/custom/cpc '10+10'
# OR
./cpc '1" in cm'

API Installation

To install the library as a Rust dependency, add cpc to your Cargo.toml like so:

[dependencies]
cpc = "1.*"

API Usage

use cpc::{eval};
use cpc::units::Unit;

match eval("3m + 1cm", true, Unit::Celsius, false) {
    Ok(answer) => {
        // answer: Number { value: 301, unit: Unit::Centimeter }
        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 * 5 trillion s

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
  • Electric current
  • Resistance
  • Voltage
  • Pressure
  • Frequency
  • 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

It's pretty fast and scales well. In my case, eval() usually runs under 0.1ms. The biggest performance hit is functions like log(). log(12345) evaluates in 0.12ms, and log(e) in 0.25ms.

To see how fast it is, you can pass the --debug flag in CLI, or the debug argument to eval().

Errors

cpc returns Results 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.

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

Build:

cargo build

Adding a unit

Nice resources for adding units:

1. Add the unit

In src/units.rs, units are specified like this:

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 60.

I have found translatorscafe.com and calculateme.com to be good websites for unit conversion. 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:

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:

// ...
match string {
  "h" | "hr" | "hrs" | "hour" | "hours" => tokens.push(Token::Unit(Hour)),
  // etc
}
// ...

Potential Improvements

General

  • Support for conversion between Power, Current, Resistance and Voltage. Multiplication and division is currently supported, but not conversions using sqrt or pow.

Potential unit types

  • Currency: How to go about dynamically updating the weights?
  • Fuel consumption
  • Data transfer rate
  • Color codes
  • Force
  • Roman numerals
  • Angles
  • Flow rate

Cross-compiling

  1. Install Docker
  2. Install cross:
    cargo install cross
    
  3. Build for x86_64 macOS, Linux and Windows:
    cross build --release --target x86_64-apple-darwin && cross build --release --target x86_64-unknown-linux-musl && cross build --release --target x86_64-pc-windows-gnu
    • Note that building for x86_64-apple-darwin only works on macOS
    • For more targets, check out the targets cross supports
    • If you run cross build in parallel, you might get a cargo not found error

The compiled binaries will now be available inside target/<target>/release/. The filename will be either cpc or cpc.exe.

Releasing a new version

  1. Update CHANGELOG.md
  2. Bump the version number in Cargo.toml
  3. Run cargo test
  4. Cross-compile cpc by following the steps above
  5. Commit and tag in format v#.#.#
  6. Publish on crates.io:
    1. Login by running cargo login and following the instructions
    2. Test publish to ensure there are no issues
      cargo publish --dry-run
      
    3. Publish
      cargo publish
      
  7. Publish on GitHub
    1. Zip the binaries and rename them like cpc-v1.0.0-macos-x64
    2. Create GitHub release with release notes and attach the zipped binaries
Comments
  • Fix clippy lints

    Fix clippy lints

    I fixed every clippy lint (except for the "this if has identical blocks" lint, because it would break the current structure). The fixes are separated in three commits to give it a better structure.

    opened by FinnRG 3
  • Add support for data rate units

    Add support for data rate units

    This permits the following calculations:

    10 kilobytes per second * 6 seconds = 60 kilobytes 500 megabytes / 100 megabytes per second = 5 seconds 1 gibibit per second * 1 hour = 3600 gibibits

    opened by djmattyg007 2
  • Dividing by a unit over time

    Dividing by a unit over time

    I haven't been able to find a way to make this kind of query work:

    ❯ ./cpc '500 megabytes / 3 megabytes per second'
    Parsing error: Expected end of input, found LexerKeyword(Per) at 5
    
    ❯ ./cpc '500 megabytes / 3 megabytes/second'
    Eval error: Unit has no child[0]
    

    Thanks for creating this project by the way, it looks amazing :) Queries like this are one thing I still tend to use Google for, so I see it as a way to continue de-googling my life.

    opened by djmattyg007 2
  • Rustfmt

    Rustfmt

    Hi @probablykasper, I know you mentioned that you decided not to use rustfmt because of how it messed up formatting for lookup.rs and units.rs.

    So I went ahead and used #[rustfmt::skip] for those modules and applied rustfmt.

    Feel free to ignore this PR if you prefer not to use rustfmt at all.

    opened by jqnatividad 1
  • AUR package

    AUR package

    I've published a package for cpc on the AUR:

    https://aur.archlinux.org/packages/cpc-calc/

    I figured I'd let you know in case you want to add a note to the readme.

    opened by djmattyg007 1
  • Further improvements to operator parsing in lexer

    Further improvements to operator parsing in lexer

    • Add support for phrases 'multiplied by' and 'divided by'
    • Add support for the division operator symbol ÷
    • Fixed lexing of revolutions per minute units
    opened by djmattyg007 1
  • Support non-US spelling of words

    Support non-US spelling of words

    These examples should work, but don't:

    ❯ ./cpc '24 kilometres per hour in metres per second'
    Lexing error: Invalid string: kilometres
    
    ❯ ./cpc '24 kilometers per hour in metres per second'
    Lexing error: Invalid string: metres
    

    I'm guessing there are probably others that need fixing up; I didn't perform a comprehensive test of all supported units.

    opened by djmattyg007 1
  • Inputs need to be quoted

    Inputs need to be quoted

    It doesn't appear to be stated anywhere in the readme, but as far as I can tell the full input query needs to be quoted and passed as a single argument on the shell. This was quite frustrating until I worked it out.

    I think the readme should be updated to state this. I also think the examples section of the readme should be updated to include the expected results. This will assist users in testing the program.

    opened by djmattyg007 1
  • Quarter unit treated as month

    Quarter unit treated as month

    I believe there's a typo in lexer.rs where (a) quarter is misspelled as quater (and quaters) and (b) the unit assigned is Unit::Month instead of Unit::Quarter.

    opened by ethwu 1
  • Output formatting improvements

    Output formatting improvements

    Currently the output doesn't produce nice human-readable descriptions, and doesn't differentiate between singular and plural values.

    ❯ target/release/cpc '123 kph'
    123 KilometersPerHour
    
    ❯ target/release/cpc '1 kph'
    1 KilometersPerHour
    

    It would be good if the output of the above was 123 kilometres per hour and 1 kilometre per hour.

    opened by djmattyg007 1
Releases(v1.9.0)
Text Expression Runner – Readable and easy to use text expressions

ter - Text Expression Runner ter is a cli to run text expressions and perform basic text operations such as filtering, ignoring and replacing on the c

Maximilian Schulke 72 Jul 31, 2022
Font independent text analysis support for shaping and layout.

lipi Lipi (Sanskrit for 'writing, letters, alphabet') is a pure Rust crate that provides font independent text analysis support for shaping and layout

Chad Brokaw 12 Sep 22, 2022
Find and replace text in source files

Ruplacer Find and replace text in source files: $ ruplacer old new src/ Patching src/a_dir/sub/foo.txt -- old is everywhere, old is old ++ new is ever

Tanker 331 Dec 28, 2022
An efficient and powerful Rust library for word wrapping text.

Textwrap Textwrap is a library for wrapping and indenting text. It is most often used by command-line programs to format dynamic output nicely so it l

Martin Geisler 322 Dec 26, 2022
👄 The most accurate natural language detection library in the Rust ecosystem, suitable for long and short text alike

Table of Contents What does this library do? Why does this library exist? Which languages are supported? How good is it? Why is it better than other l

Peter M. Stahl 569 Jan 3, 2023
Semantic text segmentation. For sentence boundary detection, compound splitting and more.

NNSplit A tool to split text using a neural network. The main application is sentence boundary detection, but e. g. compound splitting for German is a

Benjamin Minixhofer 273 Dec 29, 2022
A fast, low-resource Natural Language Processing and Text Correction library written in Rust.

nlprule A fast, low-resource Natural Language Processing and Error Correction library written in Rust. nlprule implements a rule- and lookup-based app

Benjamin Minixhofer 496 Jan 8, 2023
Source text parsing, lexing, and AST related functionality for Deno

Source text parsing, lexing, and AST related functionality for Deno.

Deno Land 90 Jan 1, 2023
lingua-rs Python binding. An accurate natural language detection library, suitable for long and short text alike.

lingua-py lingua-rs Python binding. An accurate natural language detection library, suitable for long and short text alike. Installation pip install l

messense 7 Dec 30, 2022
bottom encodes UTF-8 text into a sequence comprised of bottom emoji

bottom encodes UTF-8 text into a sequence comprised of bottom emoji (with , sprinkled in for good measure) followed by ????. It can encode any valid UTF-8 - being a bottom transcends language, after all - and decode back into UTF-8.

Bottom Software Foundation 345 Dec 30, 2022
fastest text uwuifier in the west

uwuify fastest text uwuifier in the west transforms Hey... I think I really love you. Do you want a headpat? into hey... i think i w-weawwy wuv you.

Daniel Liu 1.2k Dec 29, 2022
A crate using DeepSpeech bindings to convert mic audio from speech to text

DS-TRANSCRIBER Need an Offline Speech To Text converter? Records your mic, and returns a String containing what was said. Features Begins transcriptio

null 32 Oct 8, 2022
Sorta Text Format in UTF-8

STFU-8: Sorta Text Format in UTF-8 STFU-8 is a hacky text encoding/decoding protocol for data that might be not quite UTF-8 but is still mostly UTF-8.

Rett Berg 18 Sep 4, 2022
The fastest way to identify any mysterious text or analyze strings from a file, just ask `lemmeknow` !

The fastest way to identify anything lemmeknow ⚡ Identify any mysterious text or analyze strings from a file, just ask lemmeknow. lemmeknow can be use

Swanand Mulay 594 Dec 30, 2022
better tools for text parsing

nom-text Goal: a library that extends nom to provide better tools for text formats (programming languages, configuration files). current needs Recogni

null 5 Oct 18, 2022
Makdown-like text parser.

Makdown-like text parser.

Ryo Nakamura 1 Dec 7, 2021
A Rust wrapper for the Text synthesization service TextSynth API

A Rust wrapper for the Text synthesization service TextSynth API

ALinuxPerson 2 Mar 24, 2022
Ultra-fast, spookily accurate text summarizer that works on any language

pithy 0.1.0 - an absurdly fast, strangely accurate, summariser Quick example: pithy -f your_file_here.txt --sentences 4 --help: Print this help messa

Catherine Koshka 13 Oct 31, 2022
Fast suffix arrays for Rust (with Unicode support).

suffix Fast linear time & space suffix arrays for Rust. Supports Unicode! Dual-licensed under MIT or the UNLICENSE. Documentation https://docs.rs/suff

Andrew Gallant 207 Dec 26, 2022