cmd / calc
calc is a REPL for arithmetic. It reads Go expressions and computes
them with exact arithmetic.
It is open source.
Install:
go install github.com/croaky/calc@latest
Why
A calculator that uses float64 prints 0.1 + 0.2 as
0.30000000000000004. Go truncates integer division, so 7 / 2 is 3.
I wanted 0.3 and 3.5.
calc holds each value as an exact integer or rational, as the Go
compiler holds a constant. calc rounds to a float64 only to print a
fraction.
Usage
Start the REPL:
$ calc
> 7 / 2
3.5
> 0.1 + 0.2
0.3
> _ * 10
3
> 1 << 70
1180591620717411303424
> quit
_ is the result of the last line that gave one, as in irb.
Pass an expression as arguments or stdin to print results and exit:
calc '7 / 2'
printf '2 + 2\n_ * 10\n' | calc
Expressions
An expression is a Go constant expression: literals (1_000, 0xff,
1e-3), parentheses, and operators + - * / % & | ^ &^ << >>. %,
the bitwise operators, and shifts require integers.
/ is the one operator where calc differs from Go. It gives the exact
quotient.
How it works
calc uses two standard library packages. go/parser parses a line
as a Go expression. go/constant computes it.
The evalExpr function walks the tree. It accepts literals, _,
parentheses, and unary and binary operators. It refuses every other
node by name:
func evalExpr(e ast.Expr, prev constant.Value) (constant.Value, error) {
switch e := e.(type) {
case *ast.BasicLit:
if e.Kind != token.INT && e.Kind != token.FLOAT {
return nil, fmt.Errorf("%s is not a number", e.Value)
}
return constant.MakeFromLiteral(e.Value, e.Kind, 0), nil
case *ast.Ident:
if e.Name != "_" {
return nil, fmt.Errorf("unknown name %s", e.Name)
}
if prev == nil {
return nil, errors.New("_ has no value yet")
}
return prev, nil
constant.BinaryOp with token.QUO gives the exact quotient, even
for integers. go/constant holds integers of arbitrary size, so calc
limits shift counts to 10,000 to prevent memory exhaustion.
One step function evaluates lines for both piped input and the REPL.
Line editing
calc uses peterh/liner for line
editing and history navigation. History persists across sessions in
~/.calc_history.
GitHub repo is a mirror
I develop calc on sockeye and mirror main to
GitHub, which go install resolves.