The Anzan language itself

✓ 48 examples

As a reader of docs/ANZAN.md I want the grammar's promises to be executable So that the spec and the engine can never quietly disagree

Operator precedence and associativityoutline · 6
2 + 3 * 4, 14, # multiplicative over additive
When I calculate "2 + 3 * 4"
Then the result is "14"
2^3^2, 512, # ^ is right-associative
When I calculate "2^3^2"
Then the result is "512"
-2^2, -4, # unary minus binds looser than ^
When I calculate "-2^2"
Then the result is "-4"
2^-2, 0.25, # the exponent may carry its own sign
When I calculate "2^-2"
Then the result is "0.25"
[10, 2][0]^2, 100, # postfix indexing binds tighter than ^
When I calculate "[10, 2][0]^2"
Then the result is "100"
√4 + 5, 7, # prefix √ is unary, not greedy
When I calculate "√4 + 5"
Then the result is "7"
Number literalsoutline · 6
1_000 + 0, 1000
When I calculate "1_000 + 0"
Then the result is "1000"
2.5e-3, 0.0025
When I calculate "2.5e-3"
Then the result is "0.0025"
.5 + .5, 1
When I calculate ".5 + .5"
Then the result is "1"
0xFF, 255
When I calculate "0xFF"
Then the result is "255"
0b1010, 10
When I calculate "0b1010"
Then the result is "10"
0xDEAD_BEEF, 3735928559
When I calculate "0xDEAD_BEEF"
Then the result is "3735928559"
Pinned cell references evaluate like plain onesoutline · 3
$A:$1 * 2
Given cell A:1 contains "21"
When I calculate "$A:$1 * 2"
Then the result is "42"
$A:1 + A:1
Given cell A:1 contains "21"
When I calculate "$A:1 + A:1"
Then the result is "42"
A:$1 * 2
Given cell A:1 contains "21"
When I calculate "A:$1 * 2"
Then the result is "42"
A dangling $ is a loud lex erroroutline · 3
$
When I calculate "$"
Then the calculation fails mentioning "pins a cell reference"
$x
When I calculate "$x"
Then the calculation fails mentioning "pins a cell reference"
2 + $rate
When I calculate "2 + $rate"
Then the calculation fails mentioning "pins a cell reference"
A comment-only line is a note, not an error
When I calculate "# the calc below confirms our test"
Then the result is "# the calc below confirms our test"
A standalone note never touches ans
When I calculate "21 * 2"
And I calculate "# just thinking out loud"
And I calculate "ans"
Then the result is "42"
A trailing comment is stripped before evaluation
When I calculate "5 + 3 # adds them"
Then the result is "8"
A hash inside a string is not a comment
When I calculate "len("a # b")"
Then the result is "5"
Malformed programmer literals fail loudlyoutline · 4
0xFG, malformed hex
When I calculate "0xFG"
Then the calculation fails mentioning "malformed hex"
0x1.5, malformed hex
When I calculate "0x1.5"
Then the calculation fails mentioning "malformed hex"
0x, needs digits
When I calculate "0x"
Then the calculation fails mentioning "needs digits"
0b12, malformed binary
When I calculate "0b12"
Then the calculation fails mentioning "malformed binary"
Implicit multiplication covers parens, names, and constants
When I calculate "x9 = 4"
And I calculate "(2)(3) + 2x9"
Then the result is "14"
Modulo is exact
When I calculate "mod(0.3, 0.1)"
Then the result is "0"
Percent literalsoutline · 5
3%, 0.03
When I calculate "3%"
Then the result is "0.03"
1 * 3%, 0.03
When I calculate "1 * 3%"
Then the result is "0.03"
100 + 5%, 100.05
When I calculate "100 + 5%"
Then the result is "100.05"
50% * 2, 1
When I calculate "50% * 2"
Then the result is "1"
(2 + 3)%, 0.05
When I calculate "(2 + 3)%"
Then the result is "0.05"
A number can't directly follow another valueoutline · 3
3 4
When I calculate "3 4"
Then the calculation fails mentioning "operator"
3 % 4
When I calculate "3 % 4"
Then the calculation fails mentioning "operator"
(1) 2
When I calculate "(1) 2"
Then the calculation fails mentioning "operator"
A string is not a condition
When I calculate "if("a", 1, 2)"
Then the calculation fails mentioning "number"
Empty reductions yield identities
When I calculate "∑_i=1^0(i) + ∏_i=1^0(i)"
Then the result is "1"
A reduction spanning too many terms is refused
When I calculate "∑_i=1^200000(i)"
Then the calculation fails mentioning "100,000"
Reserved words refuse assignmentoutline · 3
ans = 5
When I calculate "ans = 5"
Then the calculation fails mentioning "cannot assign"
sigma = 1
When I calculate "sigma = 1"
Then the calculation fails mentioning "cannot assign"
true = 0
When I calculate "true = 0"
Then the calculation fails mentioning "cannot assign"
Parameters shadow globals without clobbering them
When I calculate "shade = 10"
And I calculate "twice(shade) = shade * 2"
And I calculate "twice(3) + shade"
Then the result is "16"
Free variables resolve at call time
When I calculate "base = 10"
And I calculate "above(y) = base + y"
And I calculate "base = 100"
And I calculate "above(1)"
Then the result is "101"
Special forms ship their manual
When I calculate "man if"
Then documentation is shown mentioning "taken branch"
String escapes are honored
When I calculate "len("a\tb")"
Then the result is "3"
The word data is still an ordinary name
When I calculate "data = 5"
And I calculate "data * 2"
Then the result is "10"
Compact named arguments versus cell references
Given I calculate "data Q { a: Number, age: Number }"
When I calculate "Q(a: 1, age:36).age"
Then the result is "36"
When I calculate "sum(a:1)"
Then the result is "0"

Calculating in the log

✓ 25 examples

As someone doing money math I want exact decimal answers from typed expressions So that floating-point drift never lies to me

Everyday calculations are exactoutline · 10
0.1 + 0.2, 0.3
When I calculate "0.1 + 0.2"
Then the result is "0.3"
2(3 + 4), 14
When I calculate "2(3 + 4)"
Then the result is "14"
2^10, 1024
When I calculate "2^10"
Then the result is "1024"
∑(1, 2, 3), 6
When I calculate "∑(1, 2, 3)"
Then the result is "6"
∑_i=1^10(i^2), 385
When I calculate "∑_i=1^10(i^2)"
Then the result is "385"
pmt(0.05/12, 360, 200000), -1073.643246024277969656985158225109053609679713701
When I calculate "pmt(0.05/12, 360, 200000)"
Then the result is "-1073.643246024277969656985158225109053609679713701"
margin(100, 80), 20
When I calculate "margin(100, 80)"
Then the result is "20"
date(2026, 6, 6) - date(2026, 1, 1), 156
When I calculate "date(2026, 6, 6) - date(2026, 1, 1)"
Then the result is "156"
if(2 > 1, 10, 20), 10
When I calculate "if(2 > 1, 10, 20)"
Then the result is "10"
gcd(48, 36), 12
When I calculate "gcd(48, 36)"
Then the result is "12"
A hundred dimes make exactly a dollar bag
When I calculate "∑_i=1^100(0.1)"
Then the result is "10"
The previous answer carries forward
When I calculate "6 * 7"
And I calculate "ans + 8"
Then the result is "50"
Variables persist across calculations
When I calculate "rate2026 = 0.0825"
And I calculate "1200 * rate2026"
Then the result is "99"
A leading equals sign is tolerated
When I calculate "= 1 + 2"
Then the result is "3"
A failed calculation never clobbers the previous answer
When I calculate "6 * 7"
And I calculate "1 / 0"
And I calculate "ans + 0"
Then the result is "42"
Dividing by zero explains itself
When I calculate "1 / 0"
Then the calculation fails mentioning "division by zero"
Typos are caught, not guessed
When I calculate "12 * rte"
Then the calculation fails mentioning "unknown variable 'rte'"
Comparisons answer 1 or 0outline · 7
2 < 3, 1
When I calculate "2 < 3"
Then the result is "1"
3 < 2, 0
When I calculate "3 < 2"
Then the result is "0"
3 > 2, 1
When I calculate "3 > 2"
Then the result is "1"
2 <= 2, 1
When I calculate "2 <= 2"
Then the result is "1"
2 >= 3, 0
When I calculate "2 >= 3"
Then the result is "0"
2 == 2, 1
When I calculate "2 == 2"
Then the result is "1"
2 != 2, 0
When I calculate "2 != 2"
Then the result is "0"
Comparison chains are rejected, not misread
When I calculate "1 < 2 < 3"
Then the calculation fails mentioning "can't be chained"

Data types — declared records with named construction

✓ 55 examples

As a user modeling real things I want to declare a typed record once and build instances by field name So that my collections stay consistent and serialize honestly

Declaring a type registers its constructor
When I calculate "data Person { name: String, age: Number, active: Boolean }"
Then the result is "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "Ada", age: 36, active: true)"
Then the result is "Person(name: "Ada", age: 36, active: true)"
Construction is named fields or one map — never positional
Given I calculate "data Pt { x: Number, y: Number }"
When I calculate "Pt(y: 4, x: 3)"
Then the result is "Pt(x: 3, y: 4)"
When I calculate "m = {x: 3, y: 4}"
And I calculate "Pt(m)"
Then the result is "Pt(x: 3, y: 4)"
When I calculate "Pt(3, 4)"
Then the calculation fails mentioning "takes named fields"
Field types accept any casing and Boolean fields render true/false
When I calculate "data Flag { on: boolean }"
Then the result is "data Flag { on: Boolean }"
When I calculate "Flag(on: 1 < 2)"
Then the result is "Flag(on: true)"
Fields read like map members, by dot or by key
Given I calculate "data Pt { x: Number, y: Number }"
And I calculate "p = Pt(x: 3, y: 4)"
When I calculate "sqrt(p.x^2 + p.y^2)"
Then the result is "5"
When I calculate "p["y"]"
Then the result is "4"
When I calculate "keys(p)"
Then the result is "["x", "y"]"
When I calculate "len(p)"
Then the result is "2"
Records collect into arrays and flow through higher-order functions
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
And I calculate "team = [Person(name: "Ada", age: 36, active: true), Person(name: "Grace", age: 30, active: false)]"
When I calculate "map(x -> x.age, team)"
Then the result is "[36, 30]"
When I calculate "sum(map(x -> x.age, team))"
Then the result is "66"
When I calculate "filter(x -> x.active, team)"
Then the result is "[Person(name: "Ada", age: 36, active: true)]"
When I calculate "map(Person, [{name: "Bo", age: 1, active: true}])"
Then the result is "[Person(name: "Bo", age: 1, active: true)]"
Instances canonicalize to declaration order and compare deeply
Given I calculate "data Pt { x: Number, y: Number }"
When I calculate "Pt(y: 2, x: 1) == Pt(x: 1, y: 2)"
Then the result is "1"
When I calculate "Pt(x: 1, y: 2) == {x: 1, y: 2}"
Then the result is "0"
toJson is pretty by default, compact on request, honest about Booleans
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
And I calculate "p = Person(name: "Ada", age: 36, active: true)"
When I calculate "toJson(p)"
Then the result is ""{\n \"name\": \"Ada\",\n \"age\": 36,\n \"active\": true\n}""
When I calculate "toJson(p, Json.Compact)"
Then the result is ""{\"name\":\"Ada\",\"age\":36,\"active\":true}""
When I calculate "toJson([1, 2])"
Then the result is ""[\n 1,\n 2\n]""
fromJson parses JSON into values, exactly
When I calculate "fromJson("[1, 2, 3]")"
Then the result is "[1, 2, 3]"
When I calculate "fromJson("{\"name\": \"Ada\", \"age\": 36}").age"
Then the result is "36"
When I calculate "fromJson("0.30000000000000004") == 0.30000000000000004"
Then the result is "1"
When I calculate "fromJson("123456789012345678901234567890.5") * 2"
Then the result is "246913578024691357802469135781"
When I calculate "fromJson("true") + fromJson("false")"
Then the result is "1"
When I calculate "fromJson("\"\\u0041da\"")"
Then the result is ""Ada""
fromJson and a constructor re-type a serialized record
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
And I calculate "p = Person(name: "Ada", age: 36, active: true)"
When I calculate "Person(fromJson(toJson(p))) == p"
Then the result is "1"
When I calculate "map(Person, fromJson(toJson([p, p])))[1].age"
Then the result is "36"
fromJson mistakes explain themselvesoutline · 5
fromJson("null"), has no Anzan value
When I calculate "fromJson("null")"
Then the calculation fails mentioning "has no Anzan value"
fromJson("[1,"), unexpected end
When I calculate "fromJson("[1,")"
Then the calculation fails mentioning "unexpected end"
fromJson("[1] junk"), trailing content
When I calculate "fromJson("[1] junk")"
Then the calculation fails mentioning "trailing content"
fromJson("{\"a\":1,\"a\":2}"), duplicate key
When I calculate "fromJson("{\"a\":1,\"a\":2}")"
Then the calculation fails mentioning "duplicate key"
fromJson(5), wants JSON text
When I calculate "fromJson(5)"
Then the calculation fails mentioning "wants JSON text"
Json options are named constants, not magic flags
When I calculate "Json.Pretty"
Then the result is ""pretty""
When I calculate "toJson([1, 2], Json.Compact)"
Then the result is ""[1,2]""
When I calculate "toJson([1, 2], "compact")"
Then the result is ""[1,2]""
When I calculate "Json = 5"
Then the calculation fails mentioning "cannot assign"
When I calculate "man Json"
Then documentation is shown mentioning "Formatting options"
Construction mistakes explain themselvesoutline · 9
Person(name: "Ada", age: 36), missing 'active'
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "Ada", age: 36)"
Then the calculation fails mentioning "missing 'active'"
Person(name: 7, age: 36, active: true), 'name' of Person is a String
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: 7, age: 36, active: true)"
Then the calculation fails mentioning "'name' of Person is a String"
Person(name: "A", age: "x", active: true), 'age' of Person is a Number
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", age: "x", active: true)"
Then the calculation fails mentioning "'age' of Person is a Number"
Person(name: "A", age: 36, active: 7), use true or false
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", age: 36, active: 7)"
Then the calculation fails mentioning "use true or false"
Person(name: "A", age: 36, active: true, pet: 1), no field 'pet'
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", age: 36, active: true, pet: 1)"
Then the calculation fails mentioning "no field 'pet'"
Person(name: "A", name: "B", age: 1, active: 1), duplicate field
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", name: "B", age: 1, active: 1)"
Then the calculation fails mentioning "duplicate field"
toJson(sqrt), can't serialize a function
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "toJson(sqrt)"
Then the calculation fails mentioning "can't serialize a function"
toJson(1, "wat"), unknown toJson option
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "toJson(1, "wat")"
Then the calculation fails mentioning "unknown toJson option"
toJson(1, true), Json.Pretty or Json.Compact
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "toJson(1, true)"
Then the calculation fails mentioning "Json.Pretty or Json.Compact"
Declaration mistakes explain themselvesoutline · 5
data person { a: Number }, capital letter
When I calculate "data person { a: Number }"
Then the calculation fails mentioning "capital letter"
data Bad { a: truthy }, declared data type
When I calculate "data Bad { a: truthy }"
Then the calculation fails mentioning "declared data type"
data Empty {}, at least one field
When I calculate "data Empty {}"
Then the calculation fails mentioning "at least one field"
data Dup { a: Number, A: Number }, duplicate field
When I calculate "data Dup { a: Number, A: Number }"
Then the calculation fails mentioning "duplicate field"
data Abs { a: Number }, built-in
When I calculate "data Abs { a: Number }"
Then the calculation fails mentioning "built-in"
Types and functions can't share a name, but redeclaring your own type works
Given I calculate "f(x) = x + 1"
When I calculate "data F { a: Number }"
Then the calculation fails mentioning "already a function"
When I calculate "data G { a: Number }"
And I calculate "g(x) = x"
Then the calculation fails mentioning "is a data type"
When I calculate "data G { a: Number, b: Number }"
And I calculate "G(a: 1, b: 2).b"
Then the result is "2"
A type documents itself through its trailing comment
When I calculate "data Invoice { total: Number, paid: Boolean } # one customer invoice"
And I calculate "man Invoice"
Then documentation is shown mentioning "one customer invoice"
toJson escapes strings and renders empty containers
When I calculate "toJson("a\tb")"
Then the result is ""\"a\\tb\"""
When I calculate "toJson([])"
Then the result is ""[]""
When I calculate "toJson({})"
Then the result is ""{}""
A constructor call works in tail position of a recursive function
Given I calculate "data Box { v: Number }"
And I calculate "wrap(n) = if(n <= 0, Box(v: n), wrap(n - 1))"
When I calculate "wrap(5).v"
Then the result is "0"
The explicit formula marker rejects declarations in cells
Given cell A:1 contains "=data Pt { x: Number }"
Then cell A:1 shows an error mentioning "drop the leading '='"
A plain declaration in a cell is a sheet-scoped 𝑫 type
Given cell A:1 contains "data Pt { x: Number, y: Number }"
And cell B:1 contains "Pt(x: 3, y: 4).x"
Then cell A:1 shows "𝑫 Pt"
And cell B:1 shows "3"
When I calculate "data Pt { x: Number }"
Then the calculation fails mentioning "defined in cell Sheet 1!A:1"
Types and record variables survive save and reopen
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
And I calculate "p = Person(name: "Ada", age: 36, active: true)"
When the workbook is saved and reopened
And I calculate "p.age + Person(name: "B", age: 4, active: false).age"
Then the result is "40"
A typed parameter dispatches a function by argument type
Given I calculate "kind(n: Number) = "number""
And I calculate "kind(s: String) = "string""
When I calculate "kind(42)"
Then the result is ""number""
When I calculate "kind("hi")"
Then the result is ""string""
A function can be written for a data type
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "midpoint(a: Point, b: Point) = Point(x: (a.x + b.x) / 2, y: (a.y + b.y) / 2)"
When I calculate "midpoint(Point(x: 0, y: 0), Point(x: 4, y: 10))"
Then the result is "Point(x: 2, y: 5)"
An operator can be overloaded for a data type
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "+(a: Point, b: Point) = Point(x: a.x + b.x, y: a.y + b.y)"
When I calculate "Point(x: 1, y: 2) + Point(x: 10, y: 20)"
Then the result is "Point(x: 11, y: 22)"
An overloaded operator can mix a data type and a scalar
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "*(a: Point, s: Number) = Point(x: a.x * s, y: a.y * s)"
When I calculate "Point(x: 1, y: 2) * 3"
Then the result is "Point(x: 3, y: 6)"
Built-in arithmetic is untouched by an overload
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "+(a: Point, b: Point) = Point(x: a.x + b.x, y: a.y + b.y)"
When I calculate "1 + 2"
Then the result is "3"
When I calculate ""Q" + 1"
Then the result is ""Q1""
An operator overload must involve a data type
When I calculate "+(a: Number, b: Number) = 5"
Then the calculation fails mentioning "must involve a data type"
Records compare equal by all of their state
Given I calculate "data Point { x: Number, y: Number }"
When I calculate "Point(x: 1, y: 2) == Point(x: 1, y: 2)"
Then the result is "1"
When I calculate "Point(x: 1, y: 2) == Point(x: 9, y: 9)"
Then the result is "0"
When I calculate "Point(x: 1, y: 2) != Point(x: 9, y: 9)"
Then the result is "1"
Operator overloads survive save and reopen
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "+(a: Point, b: Point) = Point(x: a.x + b.x, y: a.y + b.y)"
And I calculate "*(a: Point, s: Number) = Point(x: a.x * s, y: a.y * s)"
When the workbook is saved and reopened
And I calculate "(Point(x: 1, y: 1) + Point(x: 2, y: 3)).y"
Then the result is "4"
And I calculate "(Point(x: 2, y: 3) * 2).x"
Then the result is "4"
A data type can have fields of another data type
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "data Line { a: Point, b: Point }"
And I calculate "l = Line(a: Point(x: 1, y: 2), b: Point(x: 3, y: 4))"
When I calculate "l.b.y"
Then the result is "4"
When I calculate "l == Line(a: Point(x: 1, y: 2), b: Point(x: 3, y: 4))"
Then the result is "1"
A nested field rejects the wrong type
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "data Line { a: Point, b: Point }"
When I calculate "Line(a: 5, b: Point(x: 3, y: 4))"
Then the calculation fails mentioning "is a Point"
Nested records survive save and reopen
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "data Line { a: Point, b: Point }"
And I calculate "seg = Line(a: Point(x: 1, y: 1), b: Point(x: 4, y: 5))"
When the workbook is saved and reopened
And I calculate "seg.b.x - seg.a.x"
Then the result is "3"
A field can be a list of scalars
Given I calculate "data Tags { items: [String] }"
When I calculate "Tags(items: ["a", "b", "c"])"
Then the result is "Tags(items: ["a", "b", "c"])"
When I calculate "len(Tags(items: ["a", "b"]).items)"
Then the result is "2"
A field can be a list of records, nested freely
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "data Path { points: [Point] }"
And I calculate "p = Path(points: [Point(x: 1, y: 2), Point(x: 3, y: 4)])"
When I calculate "p.points[1].y"
Then the result is "4"
When I calculate "len(p.points)"
Then the result is "2"
A field can be a nested list
Given I calculate "data Grid { rows: [[Number]] }"
When I calculate "Grid(rows: [[1, 2], [3, 4]]).rows[1][0]"
Then the result is "3"
A field can be a string-keyed map
Given I calculate "data Config { opts: {String: Number} }"
When I calculate "Config(opts: {a: 1, b: 2}).opts.b"
Then the result is "2"
List and map fields validate their elementsoutline · 3
Tags(items: [1, 2]), is a String
Given I calculate "data Tags { items: [String] }"
And I calculate "data Config { opts: {String: Number} }"
When I calculate "Tags(items: [1, 2])"
Then the calculation fails mentioning "is a String"
Tags(items: "x"), is a [String]
Given I calculate "data Tags { items: [String] }"
And I calculate "data Config { opts: {String: Number} }"
When I calculate "Tags(items: "x")"
Then the calculation fails mentioning "is a [String]"
Config(opts: {a: "x"}), is a Number
Given I calculate "data Tags { items: [String] }"
And I calculate "data Config { opts: {String: Number} }"
When I calculate "Config(opts: {a: "x"})"
Then the calculation fails mentioning "is a Number"
List and map fields survive save and reopen
Given I calculate "data Point { x: Number, y: Number }"
And I calculate "data Path { points: [Point] }"
And I calculate "route = Path(points: [Point(x: 1, y: 1), Point(x: 5, y: 9)])"
When the workbook is saved and reopened
And I calculate "route.points[1].x"
Then the result is "5"

Fixed-precision decimals are SQL-style money values

✓ 20 examples

As a reader of docs/DECIMAL.md I want Decimal(p, s) to round to scale and check precision So that money math never silently loses digits

Fixed-precision decimal construction and arithmeticoutline · 12
Decimal(0.5), Decimal(0.5)
When I calculate "Decimal(0.5)"
Then the result is "Decimal(0.5)"
Decimal(3.14159), Decimal(3.14159)
When I calculate "Decimal(3.14159)"
Then the result is "Decimal(3.14159)"
Decimal(0.5, 2), Decimal(0.50, 2)
When I calculate "Decimal(0.5, 2)"
Then the result is "Decimal(0.50, 2)"
Decimal(10.5, 5, 2), Decimal(10.50, 5, 2)
When I calculate "Decimal(10.5, 5, 2)"
Then the result is "Decimal(10.50, 5, 2)"
Decimal(1.005, 5, 2), Decimal(1.00, 5, 2)
When I calculate "Decimal(1.005, 5, 2)"
Then the result is "Decimal(1.00, 5, 2)"
Decimal(1.005, 5, 2, Rounding.HalfUp), Decimal(1.01, 5, 2, Rounding.HalfUp)
When I calculate "Decimal(1.005, 5, 2, Rounding.HalfUp)"
Then the result is "Decimal(1.01, 5, 2, Rounding.HalfUp)"
Decimal(123, 5, 0), Decimal(123, 5, 0)
When I calculate "Decimal(123, 5, 0)"
Then the result is "Decimal(123, 5, 0)"
Decimal(2.50, 5, 2) + Decimal(1.25, 5, 2), Decimal(3.75, 5, 2)
When I calculate "Decimal(2.50, 5, 2) + Decimal(1.25, 5, 2)"
Then the result is "Decimal(3.75, 5, 2)"
Decimal(10.00, 5, 2) - Decimal(0.01, 5, 2), Decimal(9.99, 5, 2)
When I calculate "Decimal(10.00, 5, 2) - Decimal(0.01, 5, 2)"
Then the result is "Decimal(9.99, 5, 2)"
Decimal(2.50, 5, 2) * Decimal(4, 5, 2), Decimal(10.00, 5, 2)
When I calculate "Decimal(2.50, 5, 2) * Decimal(4, 5, 2)"
Then the result is "Decimal(10.00, 5, 2)"
Decimal(10, 5, 2) / Decimal(3, 5, 2), Decimal(3.33, 5, 2)
When I calculate "Decimal(10, 5, 2) / Decimal(3, 5, 2)"
Then the result is "Decimal(3.33, 5, 2)"
Decimal(10.00, 5, 2) + 0.005, Decimal(10.00, 5, 2)
When I calculate "Decimal(10.00, 5, 2) + 0.005"
Then the result is "Decimal(10.00, 5, 2)"
Decimal overflow and bad mixes are loud errorsoutline · 5
Decimal(12345, 4, 0), exceeds
When I calculate "Decimal(12345, 4, 0)"
Then the calculation fails mentioning "exceeds"
Decimal(999.99, 5, 2) + Decimal(0.01, 5, 2), exceeds
When I calculate "Decimal(999.99, 5, 2) + Decimal(0.01, 5, 2)"
Then the calculation fails mentioning "exceeds"
Decimal(1, 5, 2) + Decimal(1, 5, 2, Rounding.HalfUp), rounding
When I calculate "Decimal(1, 5, 2) + Decimal(1, 5, 2, Rounding.HalfUp)"
Then the calculation fails mentioning "rounding"
Decimal(5, 5, 2) + Int8(5), combine
When I calculate "Decimal(5, 5, 2) + Int8(5)"
Then the calculation fails mentioning "combine"
Decimal(1, 1001, 2), between 1 and 1000
When I calculate "Decimal(1, 1001, 2)"
Then the calculation fails mentioning "between 1 and 1000"
A Decimal coerces to its number outside typed arithmeticoutline · 3
Decimal(10.50, 5, 2) == 10.5, 1
When I calculate "Decimal(10.50, 5, 2) == 10.5"
Then the result is "1"
Decimal(2.50, 5, 2) < 3, 1
When I calculate "Decimal(2.50, 5, 2) < 3"
Then the result is "1"
sum(Decimal(1.5, 5, 2), Decimal(2.5, 5, 2)), 4
When I calculate "sum(Decimal(1.5, 5, 2), Decimal(2.5, 5, 2))"
Then the result is "4"

Error handling — try, iferror, ifpanic, and the Result type

✓ 30 examples

As a user (and, one day, a self-hosted interpreter) I want to recover from errors as values So that a failure can be inspected or defaulted instead of halting

try turns success and domain errors into a Resultoutline · 10
try(2 + 2), Ok(4)
When I calculate "try(2 + 2)"
Then the result is "Ok(4)"
try(1 / 0), Err("division by zero")
When I calculate "try(1 / 0)"
Then the result is "Err("division by zero")"
try("abc"[9]), Err("index 9 is out of range (string has 3 characters)")
When I calculate "try("abc"[9])"
Then the result is "Err("index 9 is out of range (string has 3 characters)")"
try(fromJson("invalidJson")), Err("fromJson: unexpected character 'i' at character 1")
When I calculate "try(fromJson("invalidJson"))"
Then the result is "Err("fromJson: unexpected character 'i' at character 1")"
Ok(4), Ok(4)
When I calculate "Ok(4)"
Then the result is "Ok(4)"
Err("nope"), Err("nope")
When I calculate "Err("nope")"
Then the result is "Err("nope")"
try(2 + 2) == Ok(4), 1
When I calculate "try(2 + 2) == Ok(4)"
Then the result is "1"
Ok(4) == Ok(4), 1
When I calculate "Ok(4) == Ok(4)"
Then the result is "1"
Ok(4) == Ok(5), 0
When I calculate "Ok(4) == Ok(5)"
Then the result is "0"
Ok(4) == Err("x"), 0
When I calculate "Ok(4) == Err("x")"
Then the result is "0"
Result combinators read and default a Resultoutline · 9
isOk(try(2 + 2)), 1
When I calculate "isOk(try(2 + 2))"
Then the result is "1"
isError(try(1 / 0)), 1
When I calculate "isError(try(1 / 0))"
Then the result is "1"
isOk(try(1 / 0)), 0
When I calculate "isOk(try(1 / 0))"
Then the result is "0"
isError(try(2 + 2)), 0
When I calculate "isError(try(2 + 2))"
Then the result is "0"
unwrap(try(2 + 2)), 4
When I calculate "unwrap(try(2 + 2))"
Then the result is "4"
unwrapOr(try(1 / 0), -1), -1
When I calculate "unwrapOr(try(1 / 0), -1)"
Then the result is "-1"
unwrapOr(try(2 + 2), -1), 4
When I calculate "unwrapOr(try(2 + 2), -1)"
Then the result is "4"
unwrapErr(try(1 / 0)), "division by zero"
When I calculate "unwrapErr(try(1 / 0))"
Then the result is ""division by zero""
if(isOk(try(9)), unwrap(try(9)), 0), 9
When I calculate "if(isOk(try(9)), unwrap(try(9)), 0)"
Then the result is "9"
iferror recovers a domain error with a fallbackoutline · 3
iferror(1 / 0, 0), 0
When I calculate "iferror(1 / 0, 0)"
Then the result is "0"
iferror(2 + 2, 0), 4
When I calculate "iferror(2 + 2, 0)"
Then the result is "4"
iferror("x"[9], -1), -1
When I calculate "iferror("x"[9], -1)"
Then the result is "-1"
iferror does not evaluate its fallback on success
When I calculate "iferror(42, 1 / 0)"
Then the result is "42"
iferror does not catch a panic (runaway guard)
When I calculate "iferror(sigma_i=1^200000(i), -1)"
Then the calculation fails mentioning "100,000 terms"
try does not catch a panic either
When I calculate "try(sigma_i=1^200000(i))"
Then the calculation fails mentioning "100,000 terms"
ifpanic catches a panic and returns its fallback
When I calculate "ifpanic(sigma_i=1^200000(i), -1)"
Then the result is "-1"
ifpanic does not catch a domain error (wrong tier)
When I calculate "ifpanic(1 / 0, -1)"
Then the calculation fails mentioning "division by zero"
Result mistakes explain themselvesoutline · 3
unwrap(try(1 / 0)), division by zero
When I calculate "unwrap(try(1 / 0))"
Then the calculation fails mentioning "division by zero"
unwrap(5), Result
When I calculate "unwrap(5)"
Then the calculation fails mentioning "Result"
unwrapErr(Ok(4)), Ok
When I calculate "unwrapErr(Ok(4))"
Then the calculation fails mentioning "Ok"

Fixed-width integers are bounded and checked

✓ 15 examples

As a reader of docs/FIXED-WIDTH.md I want Int/UInt arithmetic to be exact and range-checked So that overflow is always a loud error, never a silent wraparound

Fixed-width construction and checked arithmeticoutline · 6
Int32(27374), Int32(27374)
When I calculate "Int32(27374)"
Then the result is "Int32(27374)"
Int(27374, 32), Int32(27374)
When I calculate "Int(27374, 32)"
Then the result is "Int32(27374)"
Int8(5) + Int8(3), Int8(8)
When I calculate "Int8(5) + Int8(3)"
Then the result is "Int8(8)"
Int8(5) + 3, Int8(8)
When I calculate "Int8(5) + 3"
Then the result is "Int8(8)"
Int(100, 8) + Int(100, 16), Int16(200)
When I calculate "Int(100, 8) + Int(100, 16)"
Then the result is "Int16(200)"
Int8(2) ^ 3, Int8(8)
When I calculate "Int8(2) ^ 3"
Then the result is "Int8(8)"
Overflow and bad mixes are loud errors, never wrapsoutline · 5
Int8(200), out of range
When I calculate "Int8(200)"
Then the calculation fails mentioning "out of range"
Int8(100) + Int8(100), out of range
When I calculate "Int8(100) + Int8(100)"
Then the calculation fails mentioning "out of range"
UInt8(0) - 1, out of range
When I calculate "UInt8(0) - 1"
Then the calculation fails mentioning "out of range"
Int8(5) + UInt8(5), signed and unsigned
When I calculate "Int8(5) + UInt8(5)"
Then the calculation fails mentioning "signed and unsigned"
Int8(5) + 1.5, whole numbers
When I calculate "Int8(5) + 1.5"
Then the calculation fails mentioning "whole numbers"
Programmer-mode bitwise preserves the fixed-width typeoutline · 4
UInt8(12) & UInt8(10), UInt8(8)
Given the calculator is in programmer mode
When I calculate "UInt8(12) & UInt8(10)"
Then the result is "UInt8(8)"
~UInt8(0), UInt8(255)
Given the calculator is in programmer mode
When I calculate "~UInt8(0)"
Then the result is "UInt8(255)"
~Int8(0), Int8(-1)
Given the calculator is in programmer mode
When I calculate "~Int8(0)"
Then the result is "Int8(-1)"
UInt8(1) << 4, UInt8(16)
Given the calculator is in programmer mode
When I calculate "UInt8(1) << 4"
Then the result is "UInt8(16)"

Number formats are presentation, never value

✓ 15 examples

As someone formatting a model for reading I want currency, percent, and date displays So that cells read naturally while the math stays exact

A formatted cell displays its value in costumeoutline · 13
1234567.5, number, 1,234,567.50
Given cell B:1 contains "1234567.5"
And cell B:1 is formatted as "number"
Then cell B:1 displays "1,234,567.50"
1234.5, dollars, $1,234.50
Given cell B:1 contains "1234.5"
And cell B:1 is formatted as "dollars"
Then cell B:1 displays "$1,234.50"
-1234.5, dollars, -$1,234.50
Given cell B:1 contains "-1234.5"
And cell B:1 is formatted as "dollars"
Then cell B:1 displays "-$1,234.50"
-2, euros, -€2.00
Given cell B:1 contains "-2"
And cell B:1 is formatted as "euros"
Then cell B:1 displays "-€2.00"
0.0825, percent, 8.25%
Given cell B:1 contains "0.0825"
And cell B:1 is formatted as "percent"
Then cell B:1 displays "8.25%"
1, percent, 100.00%
Given cell B:1 contains "1"
And cell B:1 is formatted as "percent"
Then cell B:1 displays "100.00%"
20610, a date, 2026-06-06
Given cell B:1 contains "20610"
And cell B:1 is formatted as "a date"
Then cell B:1 displays "2026-06-06"
0, a date, 1970-01-01
Given cell B:1 contains "0"
And cell B:1 is formatted as "a date"
Then cell B:1 displays "1970-01-01"
195, hex, 0xC3
Given cell B:1 contains "195"
And cell B:1 is formatted as "hex"
Then cell B:1 displays "0xC3"
-255, hex, -0xFF
Given cell B:1 contains "-255"
And cell B:1 is formatted as "hex"
Then cell B:1 displays "-0xFF"
195, binary, 0b1100_0011
Given cell B:1 contains "195"
And cell B:1 is formatted as "binary"
Then cell B:1 displays "0b1100_0011"
5, binary, 0b101
Given cell B:1 contains "5"
And cell B:1 is formatted as "binary"
Then cell B:1 displays "0b101"
1.5, hex, 1.5
Given cell B:1 contains "1.5"
And cell B:1 is formatted as "hex"
Then cell B:1 displays "1.5"
Formatting never touches the underlying math
Given cell B:1 contains "0.0825"
And cell B:1 is formatted as "percent"
And cell B:2 contains "=B:1 * 200"
Then cell B:1 displays "8.25%"
And cell B:2 shows "16.5"
Hex format is display-only, like every format
Given cell A:1 contains "=bitOr(0xC0, 0x03)"
And cell A:1 is formatted as "hex"
And cell A:2 contains "=A:1 + 1"
Then cell A:1 displays "0xC3"
And cell A:2 shows "196"

Defining your own functions

✓ 16 examples

As a user with my own formulas I want to define, compose, and document functions So that the calculator speaks my domain

Define a function and use it
When I calculate "tax(x) = x * 1.0825 # TX sales tax"
And I calculate "tax(100)"
Then the result is "108.25"
Functions compose regardless of definition order
When I calculate "g(x) = f(x) + 1"
And I calculate "f(x) = x * 2"
And I calculate "g(20)"
Then the result is "41"
Recursion works
When I calculate "fact(n) = if(n <= 1, 1, n * fact(n - 1))"
And I calculate "fact(10)"
Then the result is "3628800"
Lambdas are values
When I calculate "map(x -> x * 2, [1, 2, 3])"
Then the result is "[2, 4, 6]"
Structures carry data
When I calculate "people = [{name: "Ada", age: 36}, {name: "Bob", age: 32}]"
And I calculate "people[0].age"
Then the result is "36"
Machin's 1706 formula recovers pi
When I calculate "arctanInv(x, n) = ∑_k=0^(n)((-1)^k / ((2k + 1) * x^(2k + 1)))"
And I calculate "16 * arctanInv(5, 40) - 4 * arctanInv(239, 15) - pi"
Then the result is within "1e-45" of zero
Comments are for humans and ignored by the math
When I calculate "6 * 7 # the answer"
Then the result is "42"
A trailing comment becomes the function's documentation
When I calculate "tax(x) = x * 1.0825 # TX sales tax"
And I calculate "man tax"
Then documentation is shown mentioning "TX sales tax"
Built-in functions ship their manual
When I calculate "man pmt"
Then documentation is shown mentioning "payment"
Only the taken branch of if() runs
When I calculate "if(1, 2, 1/0)"
Then the result is "2"
Deep recursion is bounded by memory, not a counter
When I calculate "countdown(n) = if(n <= 0, 0, countdown(n - 1) + 1)"
And I calculate "countdown(2000)"
Then the result is "2000"
Tail-recursive functions run at constant stack — any depth
When I calculate "sumTo(n, acc) = if(n <= 0, acc, sumTo(n - 1, acc + n))"
And I calculate "sumTo(500000, 0)"
Then the result is "125000250000"
Forgetting a base case fails politely, with a hint
When I calculate "F(n) = F(n - 1) + F(n - 2)"
And I calculate "f(3)"
Then the calculation fails mentioning "base case"
Runaway recursion is cut off cleanly, not a crash
When I calculate "loop(n) = loop(n + 1)"
And I calculate "loop(1)"
Then the calculation fails mentioning "nested too deeply"
Built-in names are protected
When I calculate "abs(x) = x"
Then the calculation fails mentioning "built-in"
Function calls are case-insensitive
When I calculate "MIN(1, 2) + Sqrt(16)"
Then the result is "5"

The expanded function library

✓ 114 examples

As a spreadsheet refugee and a terminal dweller I want combinatorics, deeper statistics, amortization, business days, and bit math So that one exact engine covers what I'd otherwise scatter across tools

Exact combinatorics — BigInt keeps every digitoutline · 7
choose(5, 2), 10
When I calculate "choose(5, 2)"
Then the result is "10"
choose(52, 5), 2598960
When I calculate "choose(52, 5)"
Then the result is "2598960"
choose(100, 50), 100891344545564193334812497256
When I calculate "choose(100, 50)"
Then the result is "100891344545564193334812497256"
choose(2, 5), 0
When I calculate "choose(2, 5)"
Then the result is "0"
perm(5, 2), 20
When I calculate "perm(5, 2)"
Then the result is "20"
perm(10, 3), 720
When I calculate "perm(10, 3)"
Then the result is "720"
perm(10, 10), 3628800
When I calculate "perm(10, 10)"
Then the result is "3628800"
Array plumbingoutline · 11
sort([3, 1, 2]), [1, 2, 3]
When I calculate "sort([3, 1, 2])"
Then the result is "[1, 2, 3]"
sort(["pear", "fig", "kiwi"]), ["fig", "kiwi", "pear"]
When I calculate "sort(["pear", "fig", "kiwi"])"
Then the result is "["fig", "kiwi", "pear"]"
unique([3, 1, 3, 2, 1]), [3, 1, 2]
When I calculate "unique([3, 1, 3, 2, 1])"
Then the result is "[3, 1, 2]"
reverse([1, 2, 3]), [3, 2, 1]
When I calculate "reverse([1, 2, 3])"
Then the result is "[3, 2, 1]"
reverse("abc"), "cba"
When I calculate "reverse("abc")"
Then the result is ""cba""
seq(1, 5), [1, 2, 3, 4, 5]
When I calculate "seq(1, 5)"
Then the result is "[1, 2, 3, 4, 5]"
seq(10, 0, -2), [10, 8, 6, 4, 2, 0]
When I calculate "seq(10, 0, -2)"
Then the result is "[10, 8, 6, 4, 2, 0]"
sum(map(x -> x^2, seq(1, 10))), 385
When I calculate "sum(map(x -> x^2, seq(1, 10)))"
Then the result is "385"
list(1, 2, 3), [1, 2, 3]
When I calculate "list(1, 2, 3)"
Then the result is "[1, 2, 3]"
sumproduct([2, 3], [10, 100]), 320
When I calculate "sumproduct([2, 3], [10, 100])"
Then the result is "320"
sumproduct(1, 2, 3, 4, 5, 6), 32
When I calculate "sumproduct(1, 2, 3, 4, 5, 6)"
Then the result is "32"
list() turns a range into an array — higher-order functions over cells
Given the sheet contains:
When I calculate "sum(filter(x -> x > 10, list(A:1..A:4)))"
Then the result is "42"
When I calculate "map(x -> x * 2, list(A:1..A:2))"
Then the result is "[10, 24]"
Statistics depth (sample conventions, full precision)outline · 11
variance(2, 4, 4, 4, 5, 5, 7, 9), 4.5714285714285714285714285714285714285714285714286
When I calculate "variance(2, 4, 4, 4, 5, 5, 7, 9)"
Then the result is "4.5714285714285714285714285714285714285714285714286"
mode(1, 2, 2, 3, 3, 3), 3
When I calculate "mode(1, 2, 2, 3, 3, 3)"
Then the result is "3"
mode(4, 9, 4, 9), 4
When I calculate "mode(4, 9, 4, 9)"
Then the result is "4"
percentile(15, 20, 35, 40, 50, 0.4), 29
When I calculate "percentile(15, 20, 35, 40, 50, 0.4)"
Then the result is "29"
percentile(1, 2, 3, 4, 0.75), 3.25
When I calculate "percentile(1, 2, 3, 4, 0.75)"
Then the result is "3.25"
percentile(1, 2, 3, 1), 3
When I calculate "percentile(1, 2, 3, 1)"
Then the result is "3"
geomean(4, 9), 6
When I calculate "geomean(4, 9)"
Then the result is "6"
correl(1, 2, 3, 2, 4, 6), 1
When I calculate "correl(1, 2, 3, 2, 4, 6)"
Then the result is "1"
slope(3, 5, 7, 1, 2, 3), 2
When I calculate "slope(3, 5, 7, 1, 2, 3)"
Then the result is "2"
intercept(3, 5, 7, 1, 2, 3), 1
When I calculate "intercept(3, 5, 7, 1, 2, 3)"
Then the result is "1"
forecast(4, 3, 5, 7, 1, 2, 3), 9
When I calculate "forecast(4, 3, 5, 7, 1, 2, 3)"
Then the result is "9"
Amortization and depreciationoutline · 10
round(ipmt(0.05/12, 1, 360, 200000), 10), -833.3333333333
When I calculate "round(ipmt(0.05/12, 1, 360, 200000), 10)"
Then the result is "-833.3333333333"
round(ipmt(0.05/12, 360, 360, 200000), 10), -4.4549512283
When I calculate "round(ipmt(0.05/12, 360, 360, 200000), 10)"
Then the result is "-4.4549512283"
round(cumipmt(0.05/12, 360, 200000, 1, 12), 2), -9932.99
When I calculate "round(cumipmt(0.05/12, 360, 200000, 1, 12), 2)"
Then the result is "-9932.99"
round(cumprinc(0.05/12, 360, 200000, 1, 12), 2), -2950.73
When I calculate "round(cumprinc(0.05/12, 360, 200000, 1, 12), 2)"
Then the result is "-2950.73"
ipmt(0.05/12, 7, 360, 200000) + ppmt(0.05/12, 7, 360, 200000) == pmt(0.05/12, 360, 200000), 1
When I calculate "ipmt(0.05/12, 7, 360, 200000) + ppmt(0.05/12, 7, 360, 200000) == pmt(0.05/12, 360, 200000)"
Then the result is "1"
sln(30000, 7500, 10), 2250
When I calculate "sln(30000, 7500, 10)"
Then the result is "2250"
round(syd(30000, 7500, 10, 1), 2), 4090.91
When I calculate "round(syd(30000, 7500, 10, 1), 2)"
Then the result is "4090.91"
ddb(30000, 7500, 10, 1), 6000
When I calculate "ddb(30000, 7500, 10, 1)"
Then the result is "6000"
ddb(30000, 7500, 10, 10), 0
When I calculate "ddb(30000, 7500, 10, 10)"
Then the result is "0"
round(nominal(effectiveRate(0.06, 12), 12), 10), 0.06
When I calculate "round(nominal(effectiveRate(0.06, 12), 12), 10)"
Then the result is "0.06"
Business days and calendar positionsoutline · 9
quarter(date(2026, 6, 6)), 2
When I calculate "quarter(date(2026, 6, 6))"
Then the result is "2"
quarter(date(2026, 11, 1)), 4
When I calculate "quarter(date(2026, 11, 1))"
Then the result is "4"
weeknum(date(2026, 1, 1)), 1
When I calculate "weeknum(date(2026, 1, 1))"
Then the result is "1"
weeknum(date(2026, 1, 4)), 2
When I calculate "weeknum(date(2026, 1, 4))"
Then the result is "2"
workday(date(2026, 6, 5), 1) == date(2026, 6, 8), 1
When I calculate "workday(date(2026, 6, 5), 1) == date(2026, 6, 8)"
Then the result is "1"
workday(date(2026, 6, 8), -1) == date(2026, 6, 5), 1
When I calculate "workday(date(2026, 6, 8), -1) == date(2026, 6, 5)"
Then the result is "1"
networkdays(date(2026, 6, 1), date(2026, 6, 30)), 22
When I calculate "networkdays(date(2026, 6, 1), date(2026, 6, 30))"
Then the result is "22"
networkdays(date(2026, 6, 1), date(2026, 6, 30), date(2026, 6, 19)), 21
When I calculate "networkdays(date(2026, 6, 1), date(2026, 6, 30), date(2026, 6, 19))"
Then the result is "21"
networkdays(date(2026, 6, 5), date(2026, 6, 1)), -5
When I calculate "networkdays(date(2026, 6, 5), date(2026, 6, 1))"
Then the result is "-5"
Scientific completionsoutline · 9
deg(pi), 180
When I calculate "deg(pi)"
Then the result is "180"
deg(pi / 4), 45
When I calculate "deg(pi / 4)"
Then the result is "45"
sin(rad(90)), 1
When I calculate "sin(rad(90))"
Then the result is "1"
sinh(0), 0
When I calculate "sinh(0)"
Then the result is "0"
cosh(0), 1
When I calculate "cosh(0)"
Then the result is "1"
tanh(0), 0
When I calculate "tanh(0)"
Then the result is "0"
asinh(0), 0
When I calculate "asinh(0)"
Then the result is "0"
acosh(1), 0
When I calculate "acosh(1)"
Then the result is "0"
atanh(0), 0
When I calculate "atanh(0)"
Then the result is "0"
atan2 knows its quadrant
When I calculate "atan2(1, 1) - pi/4"
Then the result is within "1e-15" of zero
When I calculate "atan2(-1, -1) + 3 * pi/4"
Then the result is within "1e-15" of zero
Programmer tools — exact at any widthoutline · 14
toBase(255, 16), "FF"
When I calculate "toBase(255, 16)"
Then the result is ""FF""
toBase(10, 2), "1010"
When I calculate "toBase(10, 2)"
Then the result is ""1010""
fromBase("ff", 16), 255
When I calculate "fromBase("ff", 16)"
Then the result is "255"
fromBase("1010", 2), 10
When I calculate "fromBase("1010", 2)"
Then the result is "10"
toBase(-255, 16), "-FF"
When I calculate "toBase(-255, 16)"
Then the result is ""-FF""
fromBase("-ff", 16), -255
When I calculate "fromBase("-ff", 16)"
Then the result is "-255"
fromBase(toBase(123456789012345678901234567890, 36), 36), 123456789012345678901234567890
When I calculate "fromBase(toBase(123456789012345678901234567890, 36), 36)"
Then the result is "123456789012345678901234567890"
bitAnd(12, 10), 8
When I calculate "bitAnd(12, 10)"
Then the result is "8"
bitOr(12, 10), 14
When I calculate "bitOr(12, 10)"
Then the result is "14"
bitXor(12, 10), 6
When I calculate "bitXor(12, 10)"
Then the result is "6"
bitShift(1, 100), 1.267650600228229401496703205376e+30
When I calculate "bitShift(1, 100)"
Then the result is "1.267650600228229401496703205376e+30"
bitShift(256, -4), 16
When I calculate "bitShift(256, -4)"
Then the result is "16"
bitAnd(0xFF, 0x0F), 15
When I calculate "bitAnd(0xFF, 0x0F)"
Then the result is "15"
toBase(0b1111, 16), "F"
When I calculate "toBase(0b1111, 16)"
Then the result is ""F""
solve() is goal seek in a formula
When I calculate "solve(x -> x^2, 2) - sqrt(2)"
Then the result is within "1e-12" of zero
When I calculate "solve(cos, 0, 1) - pi/2"
Then the result is within "1e-12" of zero
When I calculate "solve(x -> if(x < 1, -1, 1), 0) - 1"
Then the result is within "1e-12" of zero
When I calculate "solve(r -> fv(r, 120, -100), 20000, 0.01) * 12"
Then the result is within "1e-9" of "0.0958092381724"
Derived builtins equal their definitionsoutline · 16
avg(2, 4, 9) == sum(2, 4, 9) / count(2, 4, 9)
When I calculate "avg(2, 4, 9) == sum(2, 4, 9) / count(2, 4, 9)"
Then the result is "1"
stdev(2, 4, 4, 7) == sqrt(variance(2, 4, 4, 7))
When I calculate "stdev(2, 4, 4, 7) == sqrt(variance(2, 4, 4, 7))"
Then the result is "1"
variance(2, 4) == ((2 - 3)^2 + (4 - 3)^2) / 1
When I calculate "variance(2, 4) == ((2 - 3)^2 + (4 - 3)^2) / 1"
Then the result is "1"
cbrt(27) == root(27, 3)
When I calculate "cbrt(27) == root(27, 3)"
Then the result is "1"
geomean(2, 4, 8) == root(product(2, 4, 8), 3)
When I calculate "geomean(2, 4, 8) == root(product(2, 4, 8), 3)"
Then the result is "1"
percent(8.25) == 8.25 / 100
When I calculate "percent(8.25) == 8.25 / 100"
Then the result is "1"
deg(2) == 2 * 180 / pi
When I calculate "deg(2) == 2 * 180 / pi"
Then the result is "1"
choose(10, 4) == fact(10) / (fact(4) * fact(6))
When I calculate "choose(10, 4) == fact(10) / (fact(4) * fact(6))"
Then the result is "1"
perm(10, 4) == fact(10) / fact(6)
When I calculate "perm(10, 4) == fact(10) / fact(6)"
Then the result is "1"
lcm(12, 18) == 12 * 18 / gcd(12, 18)
When I calculate "lcm(12, 18) == 12 * 18 / gcd(12, 18)"
Then the result is "1"
median(4, 1, 3) == sort([4, 1, 3])[1]
When I calculate "median(4, 1, 3) == sort([4, 1, 3])[1]"
Then the result is "1"
sumproduct([2, 3], [10, 100]) == 2 * 10 + 3 * 100
When I calculate "sumproduct([2, 3], [10, 100]) == 2 * 10 + 3 * 100"
Then the result is "1"
npv(0.1, 300, 420) == 300 / 1.1 + 420 / 1.1^2
When I calculate "npv(0.1, 300, 420) == 300 / 1.1 + 420 / 1.1^2"
Then the result is "1"
forecast(4, 3, 5, 7, 1, 2, 3) == intercept(3, 5, 7, 1, 2, 3) + slope(3, 5, 7, 1, 2, 3) * 4
When I calculate "forecast(4, 3, 5, 7, 1, 2, 3) == intercept(3, 5, 7, 1, 2, 3) + slope(3, 5, 7, 1, 2, 3) * 4"
Then the result is "1"
quarter(date(2026, 11, 1)) == trunc((month(date(2026, 11, 1)) - 1) / 3) + 1
When I calculate "quarter(date(2026, 11, 1)) == trunc((month(date(2026, 11, 1)) - 1) / 3) + 1"
Then the result is "1"
bitShift(5, 3) == 5 * 2^3
When I calculate "bitShift(5, 3) == 5 * 2^3"
Then the result is "1"
The new functions explain their mistakesoutline · 24
choose(-1, 2), non-negative
When I calculate "choose(-1, 2)"
Then the calculation fails mentioning "non-negative"
sort([1, "a"]), all numbers or all strings
When I calculate "sort([1, "a"])"
Then the calculation fails mentioning "all numbers or all strings"
seq(1, 10, 0), can't be 0
When I calculate "seq(1, 10, 0)"
Then the calculation fails mentioning "can't be 0"
mode(1, 2, 3), no value repeats
When I calculate "mode(1, 2, 3)"
Then the calculation fails mentioning "no value repeats"
percentile(1, 2, 3, 1.5), between 0 and 1
When I calculate "percentile(1, 2, 3, 1.5)"
Then the calculation fails mentioning "between 0 and 1"
geomean(4, -9), positive
When I calculate "geomean(4, -9)"
Then the calculation fails mentioning "positive"
correl(1, 2, 3, 4, 5), equal-length
When I calculate "correl(1, 2, 3, 4, 5)"
Then the calculation fails mentioning "equal-length"
ipmt(0.05, 0, 12, 1000), 1 ≤ per ≤ nper
When I calculate "ipmt(0.05, 0, 12, 1000)"
Then the calculation fails mentioning "1 ≤ per ≤ nper"
syd(30000, 7500, 10, 11), 1 ≤ per ≤ life
When I calculate "syd(30000, 7500, 10, 11)"
Then the calculation fails mentioning "1 ≤ per ≤ life"
toBase(1.5, 16), integer
When I calculate "toBase(1.5, 16)"
Then the calculation fails mentioning "integer"
fromBase("xyz", 16), not a base-16
When I calculate "fromBase("xyz", 16)"
Then the calculation fails mentioning "not a base-16"
bitAnd(-1, 2), non-negative
When I calculate "bitAnd(-1, 2)"
Then the calculation fails mentioning "non-negative"
solve(x -> x^2 + 1, 0), did not converge
When I calculate "solve(x -> x^2 + 1, 0)"
Then the calculation fails mentioning "did not converge"
ipmt(0.05, 1, 0, 1000), 1 ≤ per ≤ nper
When I calculate "ipmt(0.05, 1, 0, 1000)"
Then the calculation fails mentioning "1 ≤ per ≤ nper"
cumipmt(0.05/12, 12, 1000, 5, 2), 1 ≤ start ≤ end
When I calculate "cumipmt(0.05/12, 12, 1000, 5, 2)"
Then the calculation fails mentioning "1 ≤ start ≤ end"
nominal(-2, 12), above -100%
When I calculate "nominal(-2, 12)"
Then the calculation fails mentioning "above -100%"
ddb(30000, 7500, 10, 11), 1 ≤ per ≤ life
When I calculate "ddb(30000, 7500, 10, 11)"
Then the calculation fails mentioning "1 ≤ per ≤ life"
sln(1, 1, 0), can't be 0
When I calculate "sln(1, 1, 0)"
Then the calculation fails mentioning "can't be 0"
toBase(255, 50), 2–36
When I calculate "toBase(255, 50)"
Then the calculation fails mentioning "2–36"
fromBase("", 16), at least one digit
When I calculate "fromBase("", 16)"
Then the calculation fails mentioning "at least one digit"
bitShift(1, 1.5), integer
When I calculate "bitShift(1, 1.5)"
Then the calculation fails mentioning "integer"
seq(1, 1000000), 100,000
When I calculate "seq(1, 1000000)"
Then the calculation fails mentioning "100,000"
workday(date(2026, 1, 1), 200001), too many
When I calculate "workday(date(2026, 1, 1), 200001)"
Then the calculation fails mentioning "too many"
networkdays(date(1, 1, 1), date(9999, 1, 1)), too many
When I calculate "networkdays(date(1, 1, 1), date(9999, 1, 1))"
Then the calculation fails mentioning "too many"

The mathematics a user can reach

✓ 108 examples

As a user of one calculator for everything I want every domain — algebra, trig, stats, finance, dates — to answer correctly So that I never need a second tool

Core arithmetic and algebraoutline · 28
1 + 2 * 3, 7
When I calculate "1 + 2 * 3"
Then the result is "7"
(1 + 2) * 3, 9
When I calculate "(1 + 2) * 3"
Then the result is "9"
10 / 4, 2.5
When I calculate "10 / 4"
Then the result is "2.5"
mod(7, 3), 1
When I calculate "mod(7, 3)"
Then the result is "1"
-2^2, -4
When I calculate "-2^2"
Then the result is "-4"
2^-1, 0.5
When I calculate "2^-1"
Then the result is "0.5"
abs(-5), 5
When I calculate "abs(-5)"
Then the result is "5"
min(3, 1, 2), 1
When I calculate "min(3, 1, 2)"
Then the result is "1"
max(3, 1, 2), 3
When I calculate "max(3, 1, 2)"
Then the result is "3"
floor(2.7), 2
When I calculate "floor(2.7)"
Then the result is "2"
floor(-1.5), -2
When I calculate "floor(-1.5)"
Then the result is "-2"
ceil(2.1), 3
When I calculate "ceil(2.1)"
Then the result is "3"
ceil(-1.5), -1
When I calculate "ceil(-1.5)"
Then the result is "-1"
trunc(-2.7), -2
When I calculate "trunc(-2.7)"
Then the result is "-2"
round(2.345, 2), 2.34
When I calculate "round(2.345, 2)"
Then the result is "2.34"
round(2.567, 2), 2.57
When I calculate "round(2.567, 2)"
Then the result is "2.57"
round(2.5), 2
When I calculate "round(2.5)"
Then the result is "2"
mod(7, 3), 1
When I calculate "mod(7, 3)"
Then the result is "1"
fact(5), 120
When I calculate "fact(5)"
Then the result is "120"
fact(20), 2432902008176640000
When I calculate "fact(20)"
Then the result is "2432902008176640000"
gcd(12, 18), 6
When I calculate "gcd(12, 18)"
Then the result is "6"
lcm(4, 6), 12
When I calculate "lcm(4, 6)"
Then the result is "12"
percent(8.25), 0.0825
When I calculate "percent(8.25)"
Then the result is "0.0825"
sqrt(144), 12
When I calculate "sqrt(144)"
Then the result is "12"
cbrt(27), 3
When I calculate "cbrt(27)"
Then the result is "3"
cbrt(-27), -3
When I calculate "cbrt(-27)"
Then the result is "-3"
root(32, 5), 2
When I calculate "root(32, 5)"
Then the result is "2"
pow(4, 0.5), 2
When I calculate "pow(4, 0.5)"
Then the result is "2"
Mathematical symbols are first-classoutline · 11
√16, 4
When I calculate "√16"
Then the result is "4"
√(2 + 2), 2
When I calculate "√(2 + 2)"
Then the result is "2"
√2^2, 2
When I calculate "√2^2"
Then the result is "2"
6 × 7 ÷ 2, 21
When I calculate "6 × 7 ÷ 2"
Then the result is "21"
6 ÷ 2 − 1, 2
When I calculate "6 ÷ 2 − 1"
Then the result is "2"
3 · 4, 12
When I calculate "3 · 4"
Then the result is "12"
π − pi, 0
When I calculate "π − pi"
Then the result is "0"
τ ÷ π, 2
When I calculate "τ ÷ π"
Then the result is "2"
pi, 3.14159265358979323846264338327950288419716939937510582097494
When I calculate "pi"
Then the result is "3.14159265358979323846264338327950288419716939937510582097494"
3 ≠ 2, 1
When I calculate "3 ≠ 2"
Then the result is "1"
2 ≤ 2, 1
When I calculate "2 ≤ 2"
Then the result is "1"
Constants are protected
When I calculate "π = 3"
Then the calculation fails mentioning "cannot assign to 'π'"
Trigonometry (radians)outline · 6
sin(pi / 2), 1
When I calculate "sin(pi / 2)"
Then the result is "1"
cos(pi), -1
When I calculate "cos(pi)"
Then the result is "-1"
tan(pi / 4), 0.9999999999999999
When I calculate "tan(pi / 4)"
Then the result is "0.9999999999999999"
asin(1) * 2, 3.1415926535897932
When I calculate "asin(1) * 2"
Then the result is "3.1415926535897932"
acos(0) * 2, 3.1415926535897932
When I calculate "acos(0) * 2"
Then the result is "3.1415926535897932"
atan(1) * 4, 3.1415926535897932
When I calculate "atan(1) * 4"
Then the result is "3.1415926535897932"
The degree postfix converts to radiansoutline · 3
sin(90°), 1
When I calculate "sin(90°)"
Then the result is "1"
cos(180°), -1
When I calculate "cos(180°)"
Then the result is "-1"
90° == pi / 2, 1
When I calculate "90° == pi / 2"
Then the result is "1"
Exponentials and logarithmsoutline · 6
exp(0), 1
When I calculate "exp(0)"
Then the result is "1"
ln(e), 1
When I calculate "ln(e)"
Then the result is "1"
log10(1000), 3
When I calculate "log10(1000)"
Then the result is "3"
log(2, 8), 3
When I calculate "log(2, 8)"
Then the result is "3"
sin(0), 0
When I calculate "sin(0)"
Then the result is "0"
cos(0), 1
When I calculate "cos(0)"
Then the result is "1"
exp inverts ln to within the Double seam
When I calculate "exp(ln(7))"
Then the result is within "0.000000000000002" of "7"
Statistics over lists, arrays, and rangesoutline · 8
avg(2, 4, 9), 5
When I calculate "avg(2, 4, 9)"
Then the result is "5"
median(1, 9, 5), 5
When I calculate "median(1, 9, 5)"
Then the result is "5"
median(1, 2, 3, 4), 2.5
When I calculate "median(1, 2, 3, 4)"
Then the result is "2.5"
count(1, 2, 3), 3
When I calculate "count(1, 2, 3)"
Then the result is "3"
product(2, 3, 4), 24
When I calculate "product(2, 3, 4)"
Then the result is "24"
stdev(2, 4, 4, 4, 5, 5, 7, 9), 2.1380899352993950774764278470380281724320113187307
When I calculate "stdev(2, 4, 4, 4, 5, 5, 7, 9)"
Then the result is "2.1380899352993950774764278470380281724320113187307"
avg([2, 4, 9]), 5
When I calculate "avg([2, 4, 9])"
Then the result is "5"
stdev(1, 3) - sqrt(2), 0
When I calculate "stdev(1, 3) - sqrt(2)"
Then the result is "0"
Products mirror summation
When I calculate "∏(2, 3, 4)"
Then the result is "24"
When I calculate "∏_i=1^5(i)"
Then the result is "120"
Euler's number from its factorial series
When I calculate "∑_k=0^45(1 / fact(k)) - e"
Then the result is within "1e-45" of zero
Nicomachus — the sum of cubes is the square of the sum
When I calculate "reduce((a, b) -> a + b, map(x -> x^3, [1,2,3,4,5,6,7,8,9,10]), 0) == (∑_i=1^10(i))^2"
Then the result is "1"
Three roads to twenty factorial agree exactly
When I calculate "rec(n) = if(n <= 1, 1, n * rec(n - 1))"
And I calculate "∏_i=1^20(i) == fact(20)"
Then the result is "1"
When I calculate "rec(20) == fact(20)"
Then the result is "1"
Compound growth's product form equals its closed form, exactly
When I calculate "grow(r, n) = ∏_i=1^(n)(1 + r)"
And I calculate "grow(0.05, 30) == 1.05^30"
Then the result is "1"
A mortgage payment round-trips the principal to 30 decimal places
When I calculate "pv(0.05/12, 360, pmt(0.05/12, 360, 200000)) - 200000"
Then the result is within "1e-30" of zero
Finance agrees with Excel (independent cross-validation)outline · 14
pmt(0.05/12, 360, 200000), -1073.64, 0.01
When I calculate "pmt(0.05/12, 360, 200000)"
Then the result is within "0.01" of "-1073.64"
pmt(0, 12, 1200), -100, 0.000001
When I calculate "pmt(0, 12, 1200)"
Then the result is within "0.000001" of "-100"
fv(0.06/12, 120, -100), 16387.93, 0.01
When I calculate "fv(0.06/12, 120, -100)"
Then the result is within "0.01" of "16387.93"
fv(0, 12, -100), 1200, 0.000001
When I calculate "fv(0, 12, -100)"
Then the result is within "0.000001" of "1200"
pv(0.04/12, 60, -500), 27149.53, 0.01
When I calculate "pv(0.04/12, 60, -500)"
Then the result is within "0.01" of "27149.53"
nper(0.05/12, -1073.64, 200000), 360, 0.01
When I calculate "nper(0.05/12, -1073.64, 200000)"
Then the result is within "0.01" of "360"
nper(0, -100, 1200), 12, 0.000001
When I calculate "nper(0, -100, 1200)"
Then the result is within "0.000001" of "12"
rate(360, -1073.64, 200000) * 12, 0.05, 0.0001
When I calculate "rate(360, -1073.64, 200000) * 12"
Then the result is within "0.0001" of "0.05"
npv(0.1, 3000, 4200, 6800), 11307.29, 0.01
When I calculate "npv(0.1, 3000, 4200, 6800)"
Then the result is within "0.01" of "11307.29"
irr(-70000, 12000, 15000, 18000, 21000, 26000), 0.0866, 0.0001
When I calculate "irr(-70000, 12000, 15000, 18000, 21000, 26000)"
Then the result is within "0.0001" of "0.0866"
effectiveRate(0.06, 12), 0.061678, 0.000001
When I calculate "effectiveRate(0.06, 12)"
Then the result is within "0.000001" of "0.061678"
markup(80, 25), 100, 0.000001
When I calculate "markup(80, 25)"
Then the result is within "0.000001" of "100"
percentOf(30, 120), 25, 0.000001
When I calculate "percentOf(30, 120)"
Then the result is within "0.000001" of "25"
percentChange(80, 100), 25, 0.000001
When I calculate "percentChange(80, 100)"
Then the result is within "0.000001" of "25"
A mortgage's lifetime cost, built up across lines
When I calculate "r = 0.05 / 12"
And I calculate "payment = pmt(r, 360, 200000)"
And I calculate "payment * 360"
Then the result is within "0.01" of "-386511.57"
IRR refuses an unsolvable stream
When I calculate "irr(1000, 2000)"
Then the calculation fails mentioning "both positive and negative"
Finance (spreadsheet sign convention)outline · 6
fv(0.1, 2, -100), 210
When I calculate "fv(0.1, 2, -100)"
Then the result is "210"
npv(0, 10, 20, 30), 60
When I calculate "npv(0, 10, 20, 30)"
Then the result is "60"
npv(0.1, 110), 100
When I calculate "npv(0.1, 110)"
Then the result is "100"
nper(0, -10, 100), 10
When I calculate "nper(0, -10, 100)"
Then the result is "10"
irr(-100, 110), 0.1
When I calculate "irr(-100, 110)"
Then the result is "0.1"
effectiveRate(0.12, 12), 0.126825030131969720661201
When I calculate "effectiveRate(0.12, 12)"
Then the result is "0.126825030131969720661201"
Accounting shorthandsoutline · 4
margin(100, 80), 20
When I calculate "margin(100, 80)"
Then the result is "20"
markup(80, 25), 100
When I calculate "markup(80, 25)"
Then the result is "100"
percentOf(50, 200), 25
When I calculate "percentOf(50, 200)"
Then the result is "25"
percentChange(100, 150), 50
When I calculate "percentChange(100, 150)"
Then the result is "50"
Date arithmetic on exact day serialsoutline · 11
weekday(date(2026, 6, 6)), 6
When I calculate "weekday(date(2026, 6, 6))"
Then the result is "6"
weekday(date(2026, 6, 8)), 1
When I calculate "weekday(date(2026, 6, 8))"
Then the result is "1"
year(date(2026, 6, 6)), 2026
When I calculate "year(date(2026, 6, 6))"
Then the result is "2026"
month(date(2026, 6, 6)), 6
When I calculate "month(date(2026, 6, 6))"
Then the result is "6"
day(date(2026, 6, 6)), 6
When I calculate "day(date(2026, 6, 6))"
Then the result is "6"
days(date(2026, 3, 1), date(2026, 2, 1)), 28
When I calculate "days(date(2026, 3, 1), date(2026, 2, 1))"
Then the result is "28"
edate(date(2026, 1, 31), 1) == date(2026, 2, 28), 1
When I calculate "edate(date(2026, 1, 31), 1) == date(2026, 2, 28)"
Then the result is "1"
eomonth(date(2024, 2, 1), 0) == date(2024, 2, 29), 1
When I calculate "eomonth(date(2024, 2, 1), 0) == date(2024, 2, 29)"
Then the result is "1"
day(eomonth(date(1900, 2, 1), 0)), 28
When I calculate "day(eomonth(date(1900, 2, 1), 0))"
Then the result is "28"
day(eomonth(date(2000, 2, 1), 0)), 29
When I calculate "day(eomonth(date(2000, 2, 1), 0))"
Then the result is "29"
days(date(2001, 1, 1), date(2000, 1, 1)), 366
When I calculate "days(date(2001, 1, 1), date(2000, 1, 1))"
Then the result is "366"
Logic composes with everything
When I calculate "and(1 < 2, or(0, not(0)))"
Then the result is "1"

Input/display modes are dialects over one canonical language

✓ 61 examples

As a reader of docs/MODES.md I want each mode's glyphs pinned to their meaning So that switching modes never changes what a stored formula means

Programmer mode reads glyphs as bitwise and modulooutline · 6
5 ^ 3, 6
Given the calculator is in programmer mode
When I calculate "5 ^ 3"
Then the result is "6"
12 & 10, 8
Given the calculator is in programmer mode
When I calculate "12 & 10"
Then the result is "8"
1 << 4, 16
Given the calculator is in programmer mode
When I calculate "1 << 4"
Then the result is "16"
8 >> 2, 2
Given the calculator is in programmer mode
When I calculate "8 >> 2"
Then the result is "2"
17 % 5, 2
Given the calculator is in programmer mode
When I calculate "17 % 5"
Then the result is "2"
pow(2, 10), 1024
Given the calculator is in programmer mode
When I calculate "pow(2, 10)"
Then the result is "1024"
Programmer mode reads pipe as bitwise OR
Given the calculator is in programmer mode
When I calculate "12 | 3"
Then the result is "15"
Programmer-mode bitwise precedence follows Python
Given the calculator is in programmer mode
When I calculate "1 & 1 == 1"
Then the result is "1"
Bitwise glyphs are Programmer-mode onlyoutline · 3
5 & 3
When I calculate "5 & 3"
Then the calculation fails mentioning "Programmer-mode operator"
1 << 2
When I calculate "1 << 2"
Then the calculation fails mentioning "Programmer-mode operator"
8 >> 1
When I calculate "8 >> 1"
Then the calculation fails mentioning "Programmer-mode operator"
Scientific mode keeps the Normal arithmetic coreoutline · 3
2 ^ 3, 8
Given the calculator is in scientific mode
When I calculate "2 ^ 3"
Then the result is "8"
3%, 0.03
Given the calculator is in scientific mode
When I calculate "3%"
Then the result is "0.03"
50%, 0.5
Given the calculator is in scientific mode
When I calculate "50%"
Then the result is "0.5"
Scientific mode still rejects the Programmer-only glyphs
Given the calculator is in scientific mode
When I calculate "5 & 3"
Then the calculation fails mentioning "Programmer-mode operator"
Scientific mode echoes plain numbers in scientific notationoutline · 4
123456 * 2, 246912, 2.46912e5
Given the calculator is in scientific mode
When I calculate "123456 * 2"
Then the result is "246912"
And the log echoes "2.46912e5"
5, 5, 5e0
Given the calculator is in scientific mode
When I calculate "5"
Then the result is "5"
And the log echoes "5e0"
1 / 8, 0.125, 1.25e-1
Given the calculator is in scientific mode
When I calculate "1 / 8"
Then the result is "0.125"
And the log echoes "1.25e-1"
-2500 * 2, -5000, -5e3
Given the calculator is in scientific mode
When I calculate "-2500 * 2"
Then the result is "-5000"
And the log echoes "-5e3"
The engineering style snaps the exponent to a multiple of 3outline · 3
123456 * 2, 246.912e3
Given the calculator is in scientific mode
And the scientific style is "eng"
When I calculate "123456 * 2"
Then the log echoes "246.912e3"
0.05, 50e-3
Given the calculator is in scientific mode
And the scientific style is "eng"
When I calculate "0.05"
Then the log echoes "50e-3"
5, 5e0
Given the calculator is in scientific mode
And the scientific style is "eng"
When I calculate "5"
Then the log echoes "5e0"
Money keeps its own display in scientific mode
Given the calculator is in scientific mode
When I calculate "$10 * 5%"
Then the log echoes "$0.50"
A grouped number keeps its grouping in scientific mode
Given the calculator is in scientific mode
When I calculate "138,561 * 9%"
Then the log echoes "12,470.49"
Degrees work in scientific mode
Given the calculator is in scientific mode
When I calculate "sin(90°)"
Then the result is "1"
And the log echoes "1e0"
The finance mode is retired in favor of core currency
When I calculate ":mode finance"
Then the calculation fails mentioning "currency now works"
Currency carries through arithmeticoutline · 8
$10, Money(10, "USD"), $10.00
When I calculate "$10"
Then the result is "Money(10, "USD")"
And the log echoes "$10.00"
$10 * 5%, Money(0.5, "USD"), $0.50
When I calculate "$10 * 5%"
Then the result is "Money(0.5, "USD")"
And the log echoes "$0.50"
€10 * .4, Money(4, "EUR"), €4.00
When I calculate "€10 * .4"
Then the result is "Money(4, "EUR")"
And the log echoes "€4.00"
$10 + $5, Money(15, "USD"), $15.00
When I calculate "$10 + $5"
Then the result is "Money(15, "USD")"
And the log echoes "$15.00"
$10 - $2.50, Money(7.5, "USD"), $7.50
When I calculate "$10 - $2.50"
Then the result is "Money(7.5, "USD")"
And the log echoes "$7.50"
$100 / 4, Money(25, "USD"), $25.00
When I calculate "$100 / 4"
Then the result is "Money(25, "USD")"
And the log echoes "$25.00"
£1234.567, Money(1234.567, "GBP"), £1,234.57
When I calculate "£1234.567"
Then the result is "Money(1234.567, "GBP")"
And the log echoes "£1,234.57"
¥5000 * 2, Money(10000, "JPY"), ¥10,000.00
When I calculate "¥5000 * 2"
Then the result is "Money(10000, "JPY")"
And the log echoes "¥10,000.00"
The Money constructor works in normal mode
When I calculate "Money(10, "USD")"
Then the result is "Money(10, "USD")"
And the log echoes "$10.00"
An unknown currency code is refused
When I calculate "Money(10, "XYZ")"
Then the calculation fails mentioning "unknown currency"
An unsupported currency glyph is refused
When I calculate "₫100"
Then the calculation fails mentioning "unsupported currency"
Percent on a currency amount is refused
When I calculate "$9%"
Then the calculation fails mentioning "can't apply % to a currency amount"
A plain number absorbs into the currencyoutline · 3
$10 + 5, $15.00
When I calculate "$10 + 5"
Then the log echoes "$15.00"
5 + $10, $15.00
When I calculate "5 + $10"
Then the log echoes "$15.00"
$10 * 3, $30.00
When I calculate "$10 * 3"
Then the log echoes "$30.00"
Mixing currencies is refusedoutline · 3
$10 + €5
When I calculate "$10 + €5"
Then the calculation fails mentioning "can't mix currencies"
€10 - $5
When I calculate "€10 - $5"
Then the calculation fails mentioning "can't mix currencies"
$10 * £2
When I calculate "$10 * £2"
Then the calculation fails mentioning "can't mix currencies"
The currency survives every arithmetic operatoroutline · 4
$10 * $2, $20.00
When I calculate "$10 * $2"
Then the log echoes "$20.00"
$10 / $2, $5.00
When I calculate "$10 / $2"
Then the log echoes "$5.00"
$10 + $2, $12.00
When I calculate "$10 + $2"
Then the log echoes "$12.00"
$10 - $2, $8.00
When I calculate "$10 - $2"
Then the log echoes "$8.00"
Negation keeps the currency and puts the sign outside the symbol
When I calculate "-$1234.5"
Then the result is "Money(-1234.5, "USD")"
And the log echoes "-$1,234.50"
Grouped numbers echo their groupingoutline · 6
138,561, 138561, 138,561
When I calculate "138,561"
Then the result is "138561"
And the log echoes "138,561"
138,561 * 9%, 12470.49, 12,470.49
When I calculate "138,561 * 9%"
Then the result is "12470.49"
And the log echoes "12,470.49"
1,000 + 1, 1001, 1,001
When I calculate "1,000 + 1"
Then the result is "1001"
And the log echoes "1,001"
1,234,567, 1234567, 1,234,567
When I calculate "1,234,567"
Then the result is "1234567"
And the log echoes "1,234,567"
12,470.49 * 2, 24940.98, 24,940.98
When I calculate "12,470.49 * 2"
Then the result is "24940.98"
And the log echoes "24,940.98"
-138,561, -138561, -138,561
When I calculate "-138,561"
Then the result is "-138561"
And the log echoes "-138,561"
Grouped currency composes through a subexpression
When I calculate "$10,000 + ($15,000 * 5%)"
Then the result is "Money(10750, "USD")"
And the log echoes "$10,750.00"
The argument separator wins over grouping
When I calculate "max(138,561)"
Then the result is "561"
Grouping is suppressed inside an array literal
When I calculate "sum([1,500])"
Then the result is "501"
Malformed thousands groups are refusedoutline · 3
1,23
When I calculate "1,23"
Then the calculation fails mentioning "thousands group"
1,2345
When I calculate "1,2345"
Then the calculation fails mentioning "thousands group"
1234,567
When I calculate "1234,567"
Then the calculation fails mentioning "thousands group"

Namespaces group declarations under a qualified name

✓ 33 examples

As someone organizing types and functions I want a namespace so generic names don't collide globally So that I can say Bits::Format instead of confiscating Format

A namespace exposes its data types by qualified name
Given I calculate "namespace Geo { data Point { x: Number, y: Number } }"
When I calculate "Geo::Point(x: 3, y: 4).x"
Then the result is "3"
A namespaced record renders and re-parses with its qualified name
Given I calculate "namespace Geo { data Point { x: Number, y: Number } }"
When I calculate "Geo::Point(x: 3, y: 4)"
Then the result is "Geo::Point(x: 3, y: 4)"
A field referencing a sibling type resolves within the namespace
Given I calculate "namespace Geo { data Point { x: Number, y: Number }; data Line { a: Point, b: Point } }"
And I calculate "seg = Geo::Line(a: Geo::Point(x: 1, y: 1), b: Geo::Point(x: 4, y: 5))"
When I calculate "seg.b.x"
Then the result is "4"
List fields nest namespaced records
Given I calculate "namespace Bits { data Field { name: String, flags: [String] }; data Format { fields: [Field] } }"
And I calculate "f = Bits::Format(fields: [Bits::Field(name: "owner", flags: ["r", "w", "x"])])"
When I calculate "len(f.fields)"
Then the result is "1"
When I calculate "len(f.fields[0].flags)"
Then the result is "3"
A namespaced function resolves sibling functions and types unqualified
Given I calculate "namespace Geo { data Point { x: Number, y: Number }; dist(p: Point) = sqrt(p.x^2 + p.y^2); origin() = Point(x: 0, y: 0) }"
When I calculate "Geo::dist(Geo::Point(x: 3, y: 4))"
Then the result is "5"
When I calculate "Geo::dist(Geo::origin())"
Then the result is "0"
A namespaced function calls a sibling function unqualified
Given I calculate "namespace M { a(n) = b(n) + 1; b(n) = n * 2 }"
When I calculate "M::a(10)"
Then the result is "21"
Namespaced recursion resolves itself through the home namespace
Given I calculate "namespace R { fact(n) = if(n <= 1, 1, n * fact(n - 1)) }"
When I calculate "R::fact(6)"
Then the result is "720"
Qualified construction validates and resolves loudlyoutline · 2
Bits::Format(fields: [5]), is a Bits::Field
Given I calculate "namespace Bits { data Field { name: String }; data Format { fields: [Field] } }"
When I calculate "Bits::Format(fields: [5])"
Then the calculation fails mentioning "is a Bits::Field"
Bits::Nope(x: 1), unknown function
Given I calculate "namespace Bits { data Field { name: String }; data Format { fields: [Field] } }"
When I calculate "Bits::Nope(x: 1)"
Then the calculation fails mentioning "unknown function"
A namespace needs at least one declaration
When I calculate "namespace Empty { }"
Then the calculation fails mentioning "at least one declaration"
Namespace members are separated by a semicolon
When I calculate "namespace Bad { data A { x: Number } data B { y: Number } }"
Then the calculation fails mentioning "separate namespace declarations with ';'"
A namespace exposes a constant by qualified name
Given I calculate "namespace Phys { c = 299792458 }"
When I calculate "Phys::c"
Then the result is "299792458"
A namespaced function uses a sibling constant unqualified
Given I calculate "namespace Circle { k = 3.14159; area(r) = k * r * r }"
When I calculate "Circle::area(10)"
Then the result is "314.159"
An imported namespace's constant resolves unqualified
Given I calculate "namespace Phys { c = 299792458 }"
And I calculate "import Phys"
When I calculate "c"
Then the result is "299792458"
Importing a constant that collides with a global is loud
Given I calculate "speed = 5"
And I calculate "namespace V { data Q { x: Number }; speed = 10 }"
When I calculate "import V"
Then the calculation fails mentioning "would shadow 'speed'"
A nested namespace resolves by its full qualified name
Given I calculate "namespace A { data Point { x: Number, y: Number }; namespace B { dist(p: Point) = sqrt(p.x^2 + p.y^2) } }"
When I calculate "A::B::dist(A::Point(x: 3, y: 4))"
Then the result is "5"
An inner member uses a parent constant and function unqualified
Given I calculate "namespace A { base = 10; twice(n) = n * 2; namespace B { f(x) = twice(x) + base } }"
When I calculate "A::B::f(5)"
Then the result is "20"
A nested record renders and re-parses with its full qualified name
Given I calculate "namespace A { namespace B { data Point { x: Number, y: Number } } }"
When I calculate "A::B::Point(x: 3, y: 4)"
Then the result is "A::B::Point(x: 3, y: 4)"
A nested namespace survives save and reopen
Given I calculate "namespace A { k = 7; namespace B { triple(n) = n * 3 } }"
When the workbook is saved and reopened
And I calculate "A::B::triple(A::k)"
Then the result is "21"
import brings a namespace's members into scope unqualified
Given I calculate "namespace Geo { data Point { x: Number, y: Number }; dist(p: Point) = sqrt(p.x^2 + p.y^2) }"
And I calculate "import Geo"
When I calculate "dist(Point(x: 3, y: 4))"
Then the result is "5"
When I calculate "Geo::dist(Geo::Point(x: 6, y: 8))"
Then the result is "10"
Importing an unknown namespace errors
When I calculate "import Nope"
Then the calculation fails mentioning "no namespace 'Nope'"
Importing a name that collides with a global is loud
Given I calculate "data Pt { x: Number }"
And I calculate "namespace NS { data Pt { x: Number } }"
When I calculate "import NS"
Then the calculation fails mentioning "would shadow 'Pt'"
Importing a name that collides with a builtin is loud
Given I calculate "namespace M2 { sqrt(x) = x * x }"
When I calculate "import M2"
Then the calculation fails mentioning "would shadow 'sqrt'"
Re-importing a namespace is a harmless no-op
Given I calculate "namespace Ok { area(r) = 3 * r * r }"
And I calculate "import Ok"
And I calculate "import Ok"
When I calculate "area(2)"
Then the result is "12"
A builtin is reachable by its module, and the bare name stays global
When I calculate "Core::sqrt(16)"
Then the result is "4"
When I calculate "Core::sqrt(16) == sqrt(16)"
Then the result is "1"
A qualified builtin must belong to that module
When I calculate "Finance::sqrt(16)"
Then the calculation fails mentioning "unknown function 'Finance::sqrt'"
A qualified builtin works as a value
When I calculate "map(Core::sqrt, [4, 9, 16])"
Then the result is "[2, 3, 4]"
Importing a builtin module is a no-op — its members are in the prelude
Given I calculate "import Stats"
When I calculate "median(1, 2, 3, 4, 5)"
Then the result is "3"
A namespace and its import survive save and reopen
Given I calculate "namespace Geo { data Point { x: Number, y: Number }; dist(p: Point) = sqrt(p.x^2 + p.y^2) }"
And I calculate "import Geo"
When the workbook is saved and reopened
And I calculate "Geo::dist(Geo::Point(x: 3, y: 4))"
Then the result is "5"
When I calculate "dist(Point(x: 6, y: 8))"
Then the result is "10"
A namespaced constant survives save and reopen
Given I calculate "namespace Phys { c = 299792458; energy(m) = m * c * c }"
When the workbook is saved and reopened
And I calculate "Phys::c"
Then the result is "299792458"
When I calculate "Phys::energy(2)"
Then the result is "179751035747363528"
An IPv4 subnetting toolkit composes from namespaces and fixed-width ints
Given I calculate "namespace Net { ip(a,b,c,d) = UInt32(a)*16777216 + UInt32(b)*65536 + UInt32(c)*256 + UInt32(d); mask(p) = UInt32(2^32 - 2^(32 - p)); wildcard(p) = bitNot(mask(p)); network(addr, p) = bitAnd(addr, mask(p)); broadcast(addr, p) = bitOr(addr, wildcard(p)); hosts(p) = 2^(32 - p) - 2 }"
And I calculate "addr = Net::ip(192, 168, 1, 130)"
When I calculate "Net::network(addr, 24) == 3232235776"
Then the result is "1"
When I calculate "Net::broadcast(addr, 24) == 3232236031"
Then the result is "1"
When I calculate "Net::hosts(24)"
Then the result is "254"
When I calculate "Net::network(addr, 26) == 3232235904"
Then the result is "1"
When I calculate "Net::hosts(26)"
Then the result is "62"
IPv6 subnetting scales the same toolkit to a 128-bit UInt128
Given I calculate "namespace Net6 { mask(p) = UInt128(2^128 - 2^(128 - p)); network(addr, p) = bitAnd(addr, mask(p)); hosts(p) = 2^(128 - p); hextet(addr, i) = bitAnd(bitShift(addr, -16 * (7 - i)), 65535) }"
And I calculate "addr = UInt128(2) * 2^112 + UInt128(0xdb8) * 2^96 + UInt128(0xabcd)"
When I calculate "Net6::hextet(addr, 0) == 2"
Then the result is "1"
When I calculate "Net6::hextet(addr, 1) == 3512"
Then the result is "1"
When I calculate "Net6::network(addr, 64) == bitAnd(addr, UInt128(2^128 - 2^64))"
Then the result is "1"
When I calculate "Net6::hosts(64)"
Then the result is "18446744073709551616"
A MAC address splits into OUI and device with a UInt64
Given I calculate "mac = UInt64(0x001B44113AB7)"
When I calculate "bitAnd(bitShift(mac, -24), 0xFFFFFF) == 0x001B44"
Then the result is "1"
When I calculate "bitAnd(mac, 0xFFFFFF) == 0x113AB7"
Then the result is "1"

Inspecting the workbook

✓ 13 examples

As someone modelling in the grid I want to read the workbook's own structure from a formula So that a calculation can adapt to its sheets and cells

Workbook reports its sheets
Given a sheet named "Budget"
When I calculate "Workbook.count"
Then the result is "2"
Worksheet names are readable
Given a sheet named "Budget"
When I calculate "Workbook.worksheets[1].name == "Budget""
Then the result is "1"
A worksheet is reachable by name
Given a sheet named "Budget"
When I calculate "Workbook.worksheets["Budget"].name == "Budget""
Then the result is "1"
A negative index counts from the end
Given a sheet named "Budget"
When I calculate "Workbook.worksheets[-1].name == "Budget""
Then the result is "1"
Reading a cell's value through the object graph
Given a sheet named "Budget"
And cell B:1 on "Budget" contains "1200"
When I calculate "Workbook.worksheets["Budget"].cell("B", 1).value * 2"
Then the result is "2400"
A cell value is reachable by position
Given cell A:1 contains "=2 + 3"
When I calculate "Workbook.worksheets[0].cell("A", 1).value"
Then the result is "5"
The flat cell() accessor reads the active sheet
Given cell A:1 contains "42"
When I calculate "cell("A", 1).value"
Then the result is "42"
The flat cell() accessor reaches another sheet by name
Given a sheet named "Budget"
And cell A:1 on "Budget" contains "99"
When I calculate "cell("Budget", "A", 1).value"
Then the result is "99"
sheetNames() lists every sheet
Given a sheet named "Budget"
When I calculate "len(sheetNames())"
Then the result is "2"
rowCount() reports the grid size
When I calculate "rowCount()"
Then the result is "1000"
A formula reading a cell through reflection recomputes live
Given cell A:1 contains "10"
And cell B:1 contains "=cell("A", 1).value + 1"
Then cell B:1 shows "11"
A user's own function shadows a reflection accessor
When I calculate "cell(x) = x * 10"
And I calculate "cell(5)"
Then the result is "50"
An unknown sheet is reported clearly
When I calculate "cell("Nope", "A", 1).value"
Then the calculation fails mentioning "unknown sheet"

Scripts — multi-line sources split into logical statements

✓ 8 examples

As a user running .anzan files (or pasting multi-line programs) I want statements to continue across lines inside open brackets So that pretty-formatted programs run exactly like their one-line forms

A pretty multi-line namespace block runs as one statement
When I run the script:
Then the result is "3"
Parentheses continue a statement across lines
When I run the script:
Then the result is "10"
Brackets continue a statement across lines
When I run the script:
Then the result is "3"
Shebang and comment lines pass between statements
When I run the script:
Then the result is "2"
A first-line comment documents a multi-line definition
When I run the script:
When I calculate "man triple"
Then documentation is shown mentioning "three of x"
Braces inside a string do not open a continuation
When I run the script:
Then the result is "13"
An unclosed block at end of input is a loud error
When I run the script:
Then the calculation fails mentioning "unterminated"
Thousands grouping works inside a continued statement
When I run the script:
Then the result is "12470.49"
And the log echoes "12,470.49"

Working in the grid

✓ 21 examples

As a spreadsheet user I want cells, names, definitions, and controls to cooperate So that my model reads like what I mean

Cells reference each other
Given cell B:1 contains "1200"
And cell B:2 contains "=B:1 * 2"
Then cell B:2 shows "2400"
Labels never become errors
Given cell A:1 contains "Q1 revenue"
Then cell A:1 shows "Q1 revenue"
A comment cell is a note that holds no value
Given cell A:1 contains "# tentative — revisit in Q4"
Then cell A:1 shows "# tentative — revisit in Q4"
A note cell is skipped in ranges and errors when referenced
Given the sheet contains:
And cell B:4 contains "=sum(B:1..B:3)"
Then cell B:4 shows "150"
And cell B:5 contains "=B:2 + 1"
Then cell B:5 shows an error mentioning "not a number"
A trailing comment on a formula is kept and ignored by evaluation
Given cell B:1 contains "200"
And cell B:2 contains "=B:1 * 1.08 # with sales tax"
Then cell B:2 shows "216"
Ranges aggregate a column
Given the sheet contains:
And cell B:4 contains "=sum(B:1..B:3)"
Then cell B:4 shows "400"
A named cell reads like prose
Given cell B:7 contains "0.08"
And cell B:7 is named "Projected Rate"
And cell A:1 contains "='Projected Rate' * 100"
Then cell A:1 shows "8"
Sheet definitions belong to their cells
Given cell A:1 contains "rate = 0.1"
And cell A:2 contains "=100 * rate"
Then cell A:1 shows "𝑖 rate"
And cell A:2 shows "10"
When I calculate "rate = 0.5"
Then the calculation fails mentioning "defined in cell"
A function defined in a cell is callable
Given cell A:1 contains "tax(x) = x * 1.0825"
And cell A:2 contains "=tax(200)"
Then cell A:1 shows "λ tax(x)"
And cell A:2 shows "216.5"
A slider is a value with a range
Given cell A:1 contains "r = slider(0.11, 0, 0.2)"
And cell A:2 contains "=r * 100"
Then cell A:1 is a slider set to "0.11"
And cell A:2 shows "11"
The log sees the sheet
Given cell B:1 contains "1200"
When I calculate "B:1 * 2"
Then the result is "2400"
Explicit markers override auto-detection
Given cell A:1 contains ""123""
And cell A:2 contains "=12 * rte"
Then cell A:1 shows "123"
And cell A:2 shows an error mentioning "unknown variable"
A formula mistake is an error, not a guess
Given cell B:1 contains "100"
And cell B:2 contains "B:1 / 0"
Then cell B:2 shows an error mentioning "division by zero"
Empty cells read as zero
Given cell A:1 contains "=B:9 + 5"
Then cell A:1 shows "5"
Circular references are caught, not infinite
Given cell A:1 contains "=A:2 + 1"
And cell A:2 contains "=A:1 + 1"
Then cell A:1 shows an error mentioning "circular reference"
Referencing a text cell is an error
Given cell A:1 contains "Q1 revenue"
And cell A:2 contains "=A:1 * 2"
Then cell A:2 shows an error mentioning "not a number"
The controls family holds values
Given cell A:1 contains "flag = checkbox(true)"
And cell A:2 contains "n = stepper(5, 1, 20)"
And cell A:3 contains "region = dropdown("EU", ["EU", "US"])"
And cell B:1 contains "=if(flag, n * 2, 0)"
And cell B:2 contains "=if(region == "EU", 1, 2)"
Then cell B:1 shows "10"
And cell B:2 shows "1"
A column of checkboxes counts its checked ones
Given the sheet contains:
And cell C:4 contains "=sum(C:1..C:3)"
Then cell C:4 shows "2"
Formulas reach across worksheets by name
Given a sheet named "Budget"
And cell B:1 on "Budget" contains "1200"
And cell A:1 contains "=Budget!B:1 * 2"
Then cell A:1 shows "2400"
When I calculate "sum(Budget!B:1..B:1) + 1"
Then the result is "1201"
A renamed-away sheet breaks loudly, not silently
Given cell A:1 contains "=Nowhere!B:1"
Then cell A:1 shows an error mentioning "unknown sheet"
A workbook round-trips through its file format
Given a sheet named "Loan"
And cell B:1 on "Loan" contains "350000"
And cell A:1 contains "=Loan!B:1 / 100"
And cell B:7 contains "0.08"
And cell B:7 is named "Projected Rate"
And cell A:2 contains "='Projected Rate' * 100"
When I calculate "growth = 1.5"
And the workbook is saved and reopened
Then cell A:1 shows "3500"
And cell A:2 shows "8"
When I calculate "growth * 2"
Then the result is "3"

Strings, arrays, maps, and the functions that work them

✓ 45 examples

As a user with more than numbers I want text and structured data to be first-class So that records and lists live next to my math

Text and structure functionsoutline · 30
len([1, 2, 3]), 3
When I calculate "len([1, 2, 3])"
Then the result is "3"
len("hello"), 5
When I calculate "len("hello")"
Then the result is "5"
len({a: 1, b: 2}), 2
When I calculate "len({a: 1, b: 2})"
Then the result is "2"
first([5, 6, 7]), 5
When I calculate "first([5, 6, 7])"
Then the result is "5"
last([5, 6, 7]), 7
When I calculate "last([5, 6, 7])"
Then the result is "7"
keys({name: "Ada", age: 36}), ["name", "age"]
When I calculate "keys({name: "Ada", age: 36})"
Then the result is "["name", "age"]"
values({a: 1, b: 2}), [1, 2]
When I calculate "values({a: 1, b: 2})"
Then the result is "[1, 2]"
sum(values({a: 1, b: 2})), 3
When I calculate "sum(values({a: 1, b: 2}))"
Then the result is "3"
concat("Q", 1, "-", 2026), "Q1-2026"
When I calculate "concat("Q", 1, "-", 2026)"
Then the result is ""Q1-2026""
concat([1, 2], [3]), [1, 2, 3]
When I calculate "concat([1, 2], [3])"
Then the result is "[1, 2, 3]"
"Q" + 1, "Q1"
When I calculate ""Q" + 1"
Then the result is ""Q1""
"a" + "b" + "c", "abc"
When I calculate ""a" + "b" + "c""
Then the result is ""abc""
"abc"[0], "a"
When I calculate ""abc"[0]"
Then the result is ""a""
slice("hello", 0, 2), "he"
When I calculate "slice("hello", 0, 2)"
Then the result is ""he""
slice("hello", 2), "llo"
When I calculate "slice("hello", 2)"
Then the result is ""llo""
slice("hello", 1, 4), "ell"
When I calculate "slice("hello", 1, 4)"
Then the result is ""ell""
slice("hello", 0, len("hello")), "hello"
When I calculate "slice("hello", 0, len("hello"))"
Then the result is ""hello""
slice([1, 2, 3, 4], 1, 3), [2, 3]
When I calculate "slice([1, 2, 3, 4], 1, 3)"
Then the result is "[2, 3]"
slice([1, 2, 3, 4], 2), [3, 4]
When I calculate "slice([1, 2, 3, 4], 2)"
Then the result is "[3, 4]"
split("a,b,c", ","), ["a", "b", "c"]
When I calculate "split("a,b,c", ",")"
Then the result is "["a", "b", "c"]"
split("nosep", ","), ["nosep"]
When I calculate "split("nosep", ",")"
Then the result is "["nosep"]"
len(split("a,b,c", ",")), 3
When I calculate "len(split("a,b,c", ","))"
Then the result is "3"
charCode("A"), 65
When I calculate "charCode("A")"
Then the result is "65"
fromCharCode(65), "A"
When I calculate "fromCharCode(65)"
Then the result is ""A""
fromCharCode(charCode("z")), "z"
When I calculate "fromCharCode(charCode("z"))"
Then the result is ""z""
[[1, 2], [3, 4]][1][0], 3
When I calculate "[[1, 2], [3, 4]][1][0]"
Then the result is "3"
{a: [1, 2]}.a[1], 2
When I calculate "{a: [1, 2]}.a[1]"
Then the result is "2"
[1, 2] == [1, 2], 1
When I calculate "[1, 2] == [1, 2]"
Then the result is "1"
{a: 1, b: 2} == {b: 2, a: 1}, 1
When I calculate "{a: 1, b: 2} == {b: 2, a: 1}"
Then the result is "1"
"x" != 5, 1
When I calculate ""x" != 5"
Then the result is "1"
Structure mistakes explain themselvesoutline · 11
[1, 2][5], out of range
When I calculate "[1, 2][5]"
Then the calculation fails mentioning "out of range"
{a: 1}.b, no key 'b'
When I calculate "{a: 1}.b"
Then the calculation fails mentioning "no key 'b'"
first([]), empty array
When I calculate "first([])"
Then the calculation fails mentioning "empty array"
sum(["a"]), works on numbers
When I calculate "sum(["a"])"
Then the calculation fails mentioning "works on numbers"
"a" * 2, expected a number
When I calculate ""a" * 2"
Then the calculation fails mentioning "expected a number"
{a: 1, a: 2}, duplicate key
When I calculate "{a: 1, a: 2}"
Then the calculation fails mentioning "duplicate key"
slice("abc", 0, 9), out of range
When I calculate "slice("abc", 0, 9)"
Then the calculation fails mentioning "out of range"
slice("abc", -1, 2), out of range
When I calculate "slice("abc", -1, 2)"
Then the calculation fails mentioning "out of range"
split("abc", ""), non-empty separator
When I calculate "split("abc", "")"
Then the calculation fails mentioning "non-empty separator"
charCode("ab"), single character
When I calculate "charCode("ab")"
Then the calculation fails mentioning "single character"
fromCharCode(-1), valid character
When I calculate "fromCharCode(-1)"
Then the calculation fails mentioning "valid character"
charCode enables ordered character-class tests
When I calculate "and(charCode("5") >= charCode("0"), charCode("5") <= charCode("9"))"
Then the result is "1"
When I calculate "and(charCode("x") >= charCode("0"), charCode("x") <= charCode("9"))"
Then the result is "0"
Filter and reduce shape data like a user thinks
When I calculate "filter(x -> x > 1, [1, 2, 3])"
Then the result is "[2, 3]"
When I calculate "reduce((a, b) -> a + b, [], 42)"
Then the result is "42"
Lambdas live in variables and close over parameters
When I calculate "f = x -> x * 2"
And I calculate "f(21)"
Then the result is "42"
When I calculate "scale(arr, n) = map(x -> x * n, arr)"
And I calculate "scale([1, 2, 3], 10)"
Then the result is "[10, 20, 30]"
A named function reference follows redefinition
When I calculate "h(x) = x + 1"
And I calculate "alias = h"
And I calculate "h(x) = x + 100"
And I calculate "alias(1)"
Then the result is "101"