The Anzan language itself

48 scenarios
Operator precedence and associativity 4 ms
  • When I calculate "2 + 3 * 4"
  • Then the result is "14"
Operator precedence and associativity 2 ms
  • When I calculate "2^3^2"
  • Then the result is "512"
Operator precedence and associativity 1 ms
  • When I calculate "-2^2"
  • Then the result is "-4"
Operator precedence and associativity 1 ms
  • When I calculate "2^-2"
  • Then the result is "0.25"
Operator precedence and associativity 1 ms
  • When I calculate "[10, 2][0]^2"
  • Then the result is "100"
Operator precedence and associativity 1 ms
  • When I calculate "√4 + 5"
  • Then the result is "7"
Number literals 1 ms
  • When I calculate "1_000 + 0"
  • Then the result is "1000"
Number literals 1 ms
  • When I calculate "2.5e-3"
  • Then the result is "0.0025"
Number literals 1 ms
  • When I calculate ".5 + .5"
  • Then the result is "1"
Number literals 1 ms
  • When I calculate "0xFF"
  • Then the result is "255"
Number literals 1 ms
  • When I calculate "0b1010"
  • Then the result is "10"
Number literals 1 ms
  • When I calculate "0xDEAD_BEEF"
  • Then the result is "3735928559"
Pinned cell references evaluate like plain ones 3 ms
  • Given cell A:1 contains "21"
  • When I calculate "$A:$1 * 2"
  • Then the result is "42"
Pinned cell references evaluate like plain ones 2 ms
  • Given cell A:1 contains "21"
  • When I calculate "$A:1 + A:1"
  • Then the result is "42"
Pinned cell references evaluate like plain ones 2 ms
  • Given cell A:1 contains "21"
  • When I calculate "A:$1 * 2"
  • Then the result is "42"
A dangling $ is a loud lex error 1 ms
  • When I calculate "$"
  • Then the calculation fails mentioning "pins a cell reference"
A dangling $ is a loud lex error 1 ms
  • When I calculate "$x"
  • Then the calculation fails mentioning "pins a cell reference"
A dangling $ is a loud lex error 1 ms
  • When I calculate "2 + $rate"
  • Then the calculation fails mentioning "pins a cell reference"
A comment-only line is a note, not an error 1 ms
  • 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 3 ms
  • 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 1 ms
  • When I calculate "5 + 3 # adds them"
  • Then the result is "8"
A hash inside a string is not a comment 1 ms
  • When I calculate "len("a # b")"
  • Then the result is "5"
Malformed programmer literals fail loudly 1 ms
  • When I calculate "0xFG"
  • Then the calculation fails mentioning "malformed hex"
Malformed programmer literals fail loudly 1 ms
  • When I calculate "0x1.5"
  • Then the calculation fails mentioning "malformed hex"
Malformed programmer literals fail loudly 1 ms
  • When I calculate "0x"
  • Then the calculation fails mentioning "needs digits"
Malformed programmer literals fail loudly 1 ms
  • When I calculate "0b12"
  • Then the calculation fails mentioning "malformed binary"
Implicit multiplication covers parens, names, and constants 2 ms
  • When I calculate "x9 = 4"
  • And I calculate "(2)(3) + 2x9"
  • Then the result is "14"
Modulo is exact 1 ms
  • When I calculate "mod(0.3, 0.1)"
  • Then the result is "0"
Percent literals 1 ms
  • When I calculate "3%"
  • Then the result is "0.03"
Percent literals 1 ms
  • When I calculate "1 * 3%"
  • Then the result is "0.03"
Percent literals 1 ms
  • When I calculate "100 + 5%"
  • Then the result is "100.05"
Percent literals 1 ms
  • When I calculate "50% * 2"
  • Then the result is "1"
Percent literals 1 ms
  • When I calculate "(2 + 3)%"
  • Then the result is "0.05"
A number can't directly follow another value 1 ms
  • When I calculate "3 4"
  • Then the calculation fails mentioning "operator"
A number can't directly follow another value 1 ms
  • When I calculate "3 % 4"
  • Then the calculation fails mentioning "operator"
A number can't directly follow another value 1 ms
  • When I calculate "(1) 2"
  • Then the calculation fails mentioning "operator"
A string is not a condition 1 ms
  • When I calculate "if("a", 1, 2)"
  • Then the calculation fails mentioning "number"
Empty reductions yield identities 1 ms
  • When I calculate "∑_i=1^0(i) + ∏_i=1^0(i)"
  • Then the result is "1"
A reduction spanning too many terms is refused 1 ms
  • When I calculate "∑_i=1^200000(i)"
  • Then the calculation fails mentioning "100,000"
Reserved words refuse assignment 1 ms
  • When I calculate "ans = 5"
  • Then the calculation fails mentioning "cannot assign"
Reserved words refuse assignment 1 ms
  • When I calculate "sigma = 1"
  • Then the calculation fails mentioning "cannot assign"
Reserved words refuse assignment 1 ms
  • When I calculate "true = 0"
  • Then the calculation fails mentioning "cannot assign"
Parameters shadow globals without clobbering them 4 ms
  • 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 5 ms
  • 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 2 ms
  • When I calculate "man if"
  • Then documentation is shown mentioning "taken branch"
String escapes are honored 1 ms
  • When I calculate "len("a\tb")"
  • Then the result is "3"
The word data is still an ordinary name 2 ms
  • When I calculate "data = 5"
  • And I calculate "data * 2"
  • Then the result is "10"
Compact named arguments versus cell references 6 ms
  • 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 scenarios
Everyday calculations are exact 4 ms
  • When I calculate "0.1 + 0.2"
  • Then the result is "0.3"
Everyday calculations are exact 2 ms
  • When I calculate "2(3 + 4)"
  • Then the result is "14"
Everyday calculations are exact 0.8 ms
  • When I calculate "2^10"
  • Then the result is "1024"
Everyday calculations are exact 1 ms
  • When I calculate "∑(1, 2, 3)"
  • Then the result is "6"
Everyday calculations are exact 0.9 ms
  • When I calculate "∑_i=1^10(i^2)"
  • Then the result is "385"
Everyday calculations are exact 17 ms
  • When I calculate "pmt(0.05/12, 360, 200000)"
  • Then the result is "-1073.643246024277969656985158225109053609679713701"
Everyday calculations are exact 1 ms
  • When I calculate "margin(100, 80)"
  • Then the result is "20"
Everyday calculations are exact 0.9 ms
  • When I calculate "date(2026, 6, 6) - date(2026, 1, 1)"
  • Then the result is "156"
Everyday calculations are exact 0.8 ms
  • When I calculate "if(2 > 1, 10, 20)"
  • Then the result is "10"
Everyday calculations are exact 0.7 ms
  • When I calculate "gcd(48, 36)"
  • Then the result is "12"
A hundred dimes make exactly a dollar bag 0.9 ms
  • When I calculate "∑_i=1^100(0.1)"
  • Then the result is "10"
The previous answer carries forward 1 ms
  • When I calculate "6 * 7"
  • And I calculate "ans + 8"
  • Then the result is "50"
Variables persist across calculations 1 ms
  • When I calculate "rate2026 = 0.0825"
  • And I calculate "1200 * rate2026"
  • Then the result is "99"
A leading equals sign is tolerated 0.6 ms
  • When I calculate "= 1 + 2"
  • Then the result is "3"
A failed calculation never clobbers the previous answer 2 ms
  • 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 0.7 ms
  • When I calculate "1 / 0"
  • Then the calculation fails mentioning "division by zero"
Typos are caught, not guessed 0.7 ms
  • When I calculate "12 * rte"
  • Then the calculation fails mentioning "unknown variable 'rte'"
Comparisons answer 1 or 0 0.7 ms
  • When I calculate "2 < 3"
  • Then the result is "1"
Comparisons answer 1 or 0 0.7 ms
  • When I calculate "3 < 2"
  • Then the result is "0"
Comparisons answer 1 or 0 0.7 ms
  • When I calculate "3 > 2"
  • Then the result is "1"
Comparisons answer 1 or 0 0.7 ms
  • When I calculate "2 <= 2"
  • Then the result is "1"
Comparisons answer 1 or 0 0.7 ms
  • When I calculate "2 >= 3"
  • Then the result is "0"
Comparisons answer 1 or 0 0.6 ms
  • When I calculate "2 == 2"
  • Then the result is "1"
Comparisons answer 1 or 0 0.6 ms
  • When I calculate "2 != 2"
  • Then the result is "0"
Comparison chains are rejected, not misread 0.6 ms
  • When I calculate "1 < 2 < 3"
  • Then the calculation fails mentioning "can't be chained"

Data types — declared records with named construction

55 scenarios
Declaring a type registers its constructor 8 ms
  • 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 13 ms
  • 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 5 ms
  • 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 14 ms
  • 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 15 ms
  • 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 6 ms
  • 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 11 ms
  • 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 17 ms
  • 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 8 ms
  • 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 themselves 2 ms
  • When I calculate "fromJson("null")"
  • Then the calculation fails mentioning "has no Anzan value"
fromJson mistakes explain themselves 2 ms
  • When I calculate "fromJson("[1,")"
  • Then the calculation fails mentioning "unexpected end"
fromJson mistakes explain themselves 2 ms
  • When I calculate "fromJson("[1] junk")"
  • Then the calculation fails mentioning "trailing content"
fromJson mistakes explain themselves 2 ms
  • When I calculate "fromJson("{\"a\":1,\"a\":2}")"
  • Then the calculation fails mentioning "duplicate key"
fromJson mistakes explain themselves 2 ms
  • When I calculate "fromJson(5)"
  • Then the calculation fails mentioning "wants JSON text"
Json options are named constants, not magic flags 15 ms
  • 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 themselves 3 ms
  • 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'"
Construction mistakes explain themselves 3 ms
  • 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"
Construction mistakes explain themselves 3 ms
  • 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"
Construction mistakes explain themselves 3 ms
  • 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"
Construction mistakes explain themselves 3 ms
  • 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'"
Construction mistakes explain themselves 3 ms
  • 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"
Construction mistakes explain themselves 4 ms
  • 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"
Construction mistakes explain themselves 4 ms
  • 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"
Construction mistakes explain themselves 3 ms
  • 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 themselves 2 ms
  • When I calculate "data person { a: Number }"
  • Then the calculation fails mentioning "capital letter"
Declaration mistakes explain themselves 2 ms
  • When I calculate "data Bad { a: truthy }"
  • Then the calculation fails mentioning "declared data type"
Declaration mistakes explain themselves 2 ms
  • When I calculate "data Empty {}"
  • Then the calculation fails mentioning "at least one field"
Declaration mistakes explain themselves 2 ms
  • When I calculate "data Dup { a: Number, A: Number }"
  • Then the calculation fails mentioning "duplicate field"
Declaration mistakes explain themselves 2 ms
  • 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 13 ms
  • 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 3 ms
  • 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 8 ms
  • 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 5 ms
  • 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 2 ms
  • 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 8 ms
  • 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 8 ms
  • 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 9 ms
  • 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 6 ms
  • 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 5 ms
  • 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 6 ms
  • 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 9 ms
  • 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 2 ms
  • 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 10 ms
  • 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 13 ms
  • 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 11 ms
  • 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 5 ms
  • 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 11 ms
  • 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 8 ms
  • 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 12 ms
  • 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 4 ms
  • 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 4 ms
  • 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 elements 6 ms
  • 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"
List and map fields validate their elements 5 ms
  • 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]"
List and map fields validate their elements 5 ms
  • 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 9 ms
  • 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 scenarios
Fixed-precision decimal construction and arithmetic 4 ms
  • When I calculate "Decimal(0.5)"
  • Then the result is "Decimal(0.5)"
Fixed-precision decimal construction and arithmetic 1 ms
  • When I calculate "Decimal(3.14159)"
  • Then the result is "Decimal(3.14159)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(0.5, 2)"
  • Then the result is "Decimal(0.50, 2)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(10.5, 5, 2)"
  • Then the result is "Decimal(10.50, 5, 2)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(1.005, 5, 2)"
  • Then the result is "Decimal(1.00, 5, 2)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(1.005, 5, 2, Rounding.HalfUp)"
  • Then the result is "Decimal(1.01, 5, 2, Rounding.HalfUp)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(123, 5, 0)"
  • Then the result is "Decimal(123, 5, 0)"
Fixed-precision decimal construction and arithmetic 0.8 ms
  • When I calculate "Decimal(2.50, 5, 2) + Decimal(1.25, 5, 2)"
  • Then the result is "Decimal(3.75, 5, 2)"
Fixed-precision decimal construction and arithmetic 1.0 ms
  • When I calculate "Decimal(10.00, 5, 2) - Decimal(0.01, 5, 2)"
  • Then the result is "Decimal(9.99, 5, 2)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(2.50, 5, 2) * Decimal(4, 5, 2)"
  • Then the result is "Decimal(10.00, 5, 2)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • When I calculate "Decimal(10, 5, 2) / Decimal(3, 5, 2)"
  • Then the result is "Decimal(3.33, 5, 2)"
Fixed-precision decimal construction and arithmetic 0.9 ms
  • 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 errors 0.9 ms
  • When I calculate "Decimal(12345, 4, 0)"
  • Then the calculation fails mentioning "exceeds"
Decimal overflow and bad mixes are loud errors 1 ms
  • When I calculate "Decimal(999.99, 5, 2) + Decimal(0.01, 5, 2)"
  • Then the calculation fails mentioning "exceeds"
Decimal overflow and bad mixes are loud errors 1 ms
  • When I calculate "Decimal(1, 5, 2) + Decimal(1, 5, 2, Rounding.HalfUp)"
  • Then the calculation fails mentioning "rounding"
Decimal overflow and bad mixes are loud errors 1 ms
  • When I calculate "Decimal(5, 5, 2) + Int8(5)"
  • Then the calculation fails mentioning "combine"
Decimal overflow and bad mixes are loud errors 0.9 ms
  • When I calculate "Decimal(1, 1001, 2)"
  • Then the calculation fails mentioning "between 1 and 1000"
A Decimal coerces to its number outside typed arithmetic 0.9 ms
  • When I calculate "Decimal(10.50, 5, 2) == 10.5"
  • Then the result is "1"
A Decimal coerces to its number outside typed arithmetic 1.0 ms
  • When I calculate "Decimal(2.50, 5, 2) < 3"
  • Then the result is "1"
A Decimal coerces to its number outside typed arithmetic 1.0 ms
  • 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 scenarios
try turns success and domain errors into a Result 3 ms
  • When I calculate "try(2 + 2)"
  • Then the result is "Ok(4)"
try turns success and domain errors into a Result 2 ms
  • When I calculate "try(1 / 0)"
  • Then the result is "Err("division by zero")"
try turns success and domain errors into a Result 1 ms
  • When I calculate "try("abc"[9])"
  • Then the result is "Err("index 9 is out of range (string has 3 characters)")"
try turns success and domain errors into a Result 1 ms
  • When I calculate "try(fromJson("invalidJson"))"
  • Then the result is "Err("fromJson: unexpected character 'i' at character 1")"
try turns success and domain errors into a Result 1 ms
  • When I calculate "Ok(4)"
  • Then the result is "Ok(4)"
try turns success and domain errors into a Result 1.0 ms
  • When I calculate "Err("nope")"
  • Then the result is "Err("nope")"
try turns success and domain errors into a Result 2 ms
  • When I calculate "try(2 + 2) == Ok(4)"
  • Then the result is "1"
try turns success and domain errors into a Result 1 ms
  • When I calculate "Ok(4) == Ok(4)"
  • Then the result is "1"
try turns success and domain errors into a Result 1 ms
  • When I calculate "Ok(4) == Ok(5)"
  • Then the result is "0"
try turns success and domain errors into a Result 1 ms
  • When I calculate "Ok(4) == Err("x")"
  • Then the result is "0"
Result combinators read and default a Result 1 ms
  • When I calculate "isOk(try(2 + 2))"
  • Then the result is "1"
Result combinators read and default a Result 1 ms
  • When I calculate "isError(try(1 / 0))"
  • Then the result is "1"
Result combinators read and default a Result 1.0 ms
  • When I calculate "isOk(try(1 / 0))"
  • Then the result is "0"
Result combinators read and default a Result 1 ms
  • When I calculate "isError(try(2 + 2))"
  • Then the result is "0"
Result combinators read and default a Result 1.0 ms
  • When I calculate "unwrap(try(2 + 2))"
  • Then the result is "4"
Result combinators read and default a Result 1 ms
  • When I calculate "unwrapOr(try(1 / 0), -1)"
  • Then the result is "-1"
Result combinators read and default a Result 1 ms
  • When I calculate "unwrapOr(try(2 + 2), -1)"
  • Then the result is "4"
Result combinators read and default a Result 1 ms
  • When I calculate "unwrapErr(try(1 / 0))"
  • Then the result is ""division by zero""
Result combinators read and default a Result 1 ms
  • When I calculate "if(isOk(try(9)), unwrap(try(9)), 0)"
  • Then the result is "9"
iferror recovers a domain error with a fallback 1.0 ms
  • When I calculate "iferror(1 / 0, 0)"
  • Then the result is "0"
iferror recovers a domain error with a fallback 1 ms
  • When I calculate "iferror(2 + 2, 0)"
  • Then the result is "4"
iferror recovers a domain error with a fallback 1 ms
  • When I calculate "iferror("x"[9], -1)"
  • Then the result is "-1"
iferror does not evaluate its fallback on success 1 ms
  • When I calculate "iferror(42, 1 / 0)"
  • Then the result is "42"
iferror does not catch a panic (runaway guard) 1 ms
  • 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 1 ms
  • 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 1 ms
  • When I calculate "ifpanic(sigma_i=1^200000(i), -1)"
  • Then the result is "-1"
ifpanic does not catch a domain error (wrong tier) 1.0 ms
  • When I calculate "ifpanic(1 / 0, -1)"
  • Then the calculation fails mentioning "division by zero"
Result mistakes explain themselves 0.9 ms
  • When I calculate "unwrap(try(1 / 0))"
  • Then the calculation fails mentioning "division by zero"
Result mistakes explain themselves 1.0 ms
  • When I calculate "unwrap(5)"
  • Then the calculation fails mentioning "Result"
Result mistakes explain themselves 1.0 ms
  • When I calculate "unwrapErr(Ok(4))"
  • Then the calculation fails mentioning "Ok"

Fixed-width integers are bounded and checked

15 scenarios
Fixed-width construction and checked arithmetic 2 ms
  • When I calculate "Int32(27374)"
  • Then the result is "Int32(27374)"
Fixed-width construction and checked arithmetic 0.8 ms
  • When I calculate "Int(27374, 32)"
  • Then the result is "Int32(27374)"
Fixed-width construction and checked arithmetic 0.5 ms
  • When I calculate "Int8(5) + Int8(3)"
  • Then the result is "Int8(8)"
Fixed-width construction and checked arithmetic 0.5 ms
  • When I calculate "Int8(5) + 3"
  • Then the result is "Int8(8)"
Fixed-width construction and checked arithmetic 0.5 ms
  • When I calculate "Int(100, 8) + Int(100, 16)"
  • Then the result is "Int16(200)"
Fixed-width construction and checked arithmetic 0.5 ms
  • When I calculate "Int8(2) ^ 3"
  • Then the result is "Int8(8)"
Overflow and bad mixes are loud errors, never wraps 0.7 ms
  • When I calculate "Int8(200)"
  • Then the calculation fails mentioning "out of range"
Overflow and bad mixes are loud errors, never wraps 0.6 ms
  • When I calculate "Int8(100) + Int8(100)"
  • Then the calculation fails mentioning "out of range"
Overflow and bad mixes are loud errors, never wraps 0.5 ms
  • When I calculate "UInt8(0) - 1"
  • Then the calculation fails mentioning "out of range"
Overflow and bad mixes are loud errors, never wraps 0.5 ms
  • When I calculate "Int8(5) + UInt8(5)"
  • Then the calculation fails mentioning "signed and unsigned"
Overflow and bad mixes are loud errors, never wraps 0.5 ms
  • When I calculate "Int8(5) + 1.5"
  • Then the calculation fails mentioning "whole numbers"
Programmer-mode bitwise preserves the fixed-width type 1 ms
  • Given the calculator is in programmer mode
  • When I calculate "UInt8(12) & UInt8(10)"
  • Then the result is "UInt8(8)"
Programmer-mode bitwise preserves the fixed-width type 0.9 ms
  • Given the calculator is in programmer mode
  • When I calculate "~UInt8(0)"
  • Then the result is "UInt8(255)"
Programmer-mode bitwise preserves the fixed-width type 1.0 ms
  • Given the calculator is in programmer mode
  • When I calculate "~Int8(0)"
  • Then the result is "Int8(-1)"
Programmer-mode bitwise preserves the fixed-width type 1 ms
  • 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 scenarios
A formatted cell displays its value in costume 3 ms
  • Given cell B:1 contains "1234567.5"
  • And cell B:1 is formatted as "number"
  • Then cell B:1 displays "1,234,567.50"
A formatted cell displays its value in costume 2 ms
  • Given cell B:1 contains "1234.5"
  • And cell B:1 is formatted as "dollars"
  • Then cell B:1 displays "$1,234.50"
A formatted cell displays its value in costume 1 ms
  • Given cell B:1 contains "-1234.5"
  • And cell B:1 is formatted as "dollars"
  • Then cell B:1 displays "-$1,234.50"
A formatted cell displays its value in costume 2 ms
  • Given cell B:1 contains "-2"
  • And cell B:1 is formatted as "euros"
  • Then cell B:1 displays "-€2.00"
A formatted cell displays its value in costume 2 ms
  • Given cell B:1 contains "0.0825"
  • And cell B:1 is formatted as "percent"
  • Then cell B:1 displays "8.25%"
A formatted cell displays its value in costume 1 ms
  • Given cell B:1 contains "1"
  • And cell B:1 is formatted as "percent"
  • Then cell B:1 displays "100.00%"
A formatted cell displays its value in costume 1 ms
  • Given cell B:1 contains "20610"
  • And cell B:1 is formatted as "a date"
  • Then cell B:1 displays "2026-06-06"
A formatted cell displays its value in costume 1 ms
  • Given cell B:1 contains "0"
  • And cell B:1 is formatted as "a date"
  • Then cell B:1 displays "1970-01-01"
A formatted cell displays its value in costume 2 ms
  • Given cell B:1 contains "195"
  • And cell B:1 is formatted as "hex"
  • Then cell B:1 displays "0xC3"
A formatted cell displays its value in costume 2 ms
  • Given cell B:1 contains "-255"
  • And cell B:1 is formatted as "hex"
  • Then cell B:1 displays "-0xFF"
A formatted cell displays its value in costume 2 ms
  • Given cell B:1 contains "195"
  • And cell B:1 is formatted as "binary"
  • Then cell B:1 displays "0b1100_0011"
A formatted cell displays its value in costume 1 ms
  • Given cell B:1 contains "5"
  • And cell B:1 is formatted as "binary"
  • Then cell B:1 displays "0b101"
A formatted cell displays its value in costume 1 ms
  • 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 3 ms
  • 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 3 ms
  • 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 scenarios
Define a function and use it 2 ms
  • 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 1 ms
  • 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 0.7 ms
  • 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 0.4 ms
  • When I calculate "map(x -> x * 2, [1, 2, 3])"
  • Then the result is "[2, 4, 6]"
Structures carry data 0.6 ms
  • 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 2 ms
  • 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 0.4 ms
  • When I calculate "6 * 7 # the answer"
  • Then the result is "42"
A trailing comment becomes the function's documentation 0.7 ms
  • 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 0.4 ms
  • When I calculate "man pmt"
  • Then documentation is shown mentioning "payment"
Only the taken branch of if() runs 0.3 ms
  • When I calculate "if(1, 2, 1/0)"
  • Then the result is "2"
Deep recursion is bounded by memory, not a counter 50 ms
  • 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 4120 ms
  • 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 515 ms
  • 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 4726 ms
  • 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 0.4 ms
  • When I calculate "abs(x) = x"
  • Then the calculation fails mentioning "built-in"
Function calls are case-insensitive 0.4 ms
  • When I calculate "MIN(1, 2) + Sqrt(16)"
  • Then the result is "5"

The expanded function library

114 scenarios
Exact combinatorics — BigInt keeps every digit 13 ms
  • When I calculate "choose(5, 2)"
  • Then the result is "10"
Exact combinatorics — BigInt keeps every digit 7 ms
  • When I calculate "choose(52, 5)"
  • Then the result is "2598960"
Exact combinatorics — BigInt keeps every digit 10 ms
  • When I calculate "choose(100, 50)"
  • Then the result is "100891344545564193334812497256"
Exact combinatorics — BigInt keeps every digit 5 ms
  • When I calculate "choose(2, 5)"
  • Then the result is "0"
Exact combinatorics — BigInt keeps every digit 5 ms
  • When I calculate "perm(5, 2)"
  • Then the result is "20"
Exact combinatorics — BigInt keeps every digit 5 ms
  • When I calculate "perm(10, 3)"
  • Then the result is "720"
Exact combinatorics — BigInt keeps every digit 5 ms
  • When I calculate "perm(10, 10)"
  • Then the result is "3628800"
Array plumbing 5 ms
  • When I calculate "sort([3, 1, 2])"
  • Then the result is "[1, 2, 3]"
Array plumbing 4 ms
  • When I calculate "sort(["pear", "fig", "kiwi"])"
  • Then the result is "["fig", "kiwi", "pear"]"
Array plumbing 4 ms
  • When I calculate "unique([3, 1, 3, 2, 1])"
  • Then the result is "[3, 1, 2]"
Array plumbing 4 ms
  • When I calculate "reverse([1, 2, 3])"
  • Then the result is "[3, 2, 1]"
Array plumbing 5 ms
  • When I calculate "reverse("abc")"
  • Then the result is ""cba""
Array plumbing 4 ms
  • When I calculate "seq(1, 5)"
  • Then the result is "[1, 2, 3, 4, 5]"
Array plumbing 4 ms
  • When I calculate "seq(10, 0, -2)"
  • Then the result is "[10, 8, 6, 4, 2, 0]"
Array plumbing 4 ms
  • When I calculate "sum(map(x -> x^2, seq(1, 10)))"
  • Then the result is "385"
Array plumbing 4 ms
  • When I calculate "list(1, 2, 3)"
  • Then the result is "[1, 2, 3]"
Array plumbing 5 ms
  • When I calculate "sumproduct([2, 3], [10, 100])"
  • Then the result is "320"
Array plumbing 5 ms
  • 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 18 ms
  • 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) 5 ms
  • When I calculate "variance(2, 4, 4, 4, 5, 5, 7, 9)"
  • Then the result is "4.5714285714285714285714285714285714285714285714286"
Statistics depth (sample conventions, full precision) 5 ms
  • When I calculate "mode(1, 2, 2, 3, 3, 3)"
  • Then the result is "3"
Statistics depth (sample conventions, full precision) 5 ms
  • When I calculate "mode(4, 9, 4, 9)"
  • Then the result is "4"
Statistics depth (sample conventions, full precision) 4 ms
  • When I calculate "percentile(15, 20, 35, 40, 50, 0.4)"
  • Then the result is "29"
Statistics depth (sample conventions, full precision) 4 ms
  • When I calculate "percentile(1, 2, 3, 4, 0.75)"
  • Then the result is "3.25"
Statistics depth (sample conventions, full precision) 5 ms
  • When I calculate "percentile(1, 2, 3, 1)"
  • Then the result is "3"
Statistics depth (sample conventions, full precision) 5 ms
  • When I calculate "geomean(4, 9)"
  • Then the result is "6"
Statistics depth (sample conventions, full precision) 5 ms
  • When I calculate "correl(1, 2, 3, 2, 4, 6)"
  • Then the result is "1"
Statistics depth (sample conventions, full precision) 5 ms
  • When I calculate "slope(3, 5, 7, 1, 2, 3)"
  • Then the result is "2"
Statistics depth (sample conventions, full precision) 4 ms
  • When I calculate "intercept(3, 5, 7, 1, 2, 3)"
  • Then the result is "1"
Statistics depth (sample conventions, full precision) 4 ms
  • When I calculate "forecast(4, 3, 5, 7, 1, 2, 3)"
  • Then the result is "9"
Amortization and depreciation 20 ms
  • When I calculate "round(ipmt(0.05/12, 1, 360, 200000), 10)"
  • Then the result is "-833.3333333333"
Amortization and depreciation 211 ms
  • When I calculate "round(ipmt(0.05/12, 360, 360, 200000), 10)"
  • Then the result is "-4.4549512283"
Amortization and depreciation 70 ms
  • When I calculate "round(cumipmt(0.05/12, 360, 200000, 1, 12), 2)"
  • Then the result is "-9932.99"
Amortization and depreciation 32 ms
  • When I calculate "round(cumprinc(0.05/12, 360, 200000, 1, 12), 2)"
  • Then the result is "-2950.73"
Amortization and depreciation 52 ms
  • 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"
Amortization and depreciation 5 ms
  • When I calculate "sln(30000, 7500, 10)"
  • Then the result is "2250"
Amortization and depreciation 5 ms
  • When I calculate "round(syd(30000, 7500, 10, 1), 2)"
  • Then the result is "4090.91"
Amortization and depreciation 6 ms
  • When I calculate "ddb(30000, 7500, 10, 1)"
  • Then the result is "6000"
Amortization and depreciation 5 ms
  • When I calculate "ddb(30000, 7500, 10, 10)"
  • Then the result is "0"
Amortization and depreciation 6 ms
  • When I calculate "round(nominal(effectiveRate(0.06, 12), 12), 10)"
  • Then the result is "0.06"
Business days and calendar positions 6 ms
  • When I calculate "quarter(date(2026, 6, 6))"
  • Then the result is "2"
Business days and calendar positions 5 ms
  • When I calculate "quarter(date(2026, 11, 1))"
  • Then the result is "4"
Business days and calendar positions 5 ms
  • When I calculate "weeknum(date(2026, 1, 1))"
  • Then the result is "1"
Business days and calendar positions 8 ms
  • When I calculate "weeknum(date(2026, 1, 4))"
  • Then the result is "2"
Business days and calendar positions 5 ms
  • When I calculate "workday(date(2026, 6, 5), 1) == date(2026, 6, 8)"
  • Then the result is "1"
Business days and calendar positions 6 ms
  • When I calculate "workday(date(2026, 6, 8), -1) == date(2026, 6, 5)"
  • Then the result is "1"
Business days and calendar positions 5 ms
  • When I calculate "networkdays(date(2026, 6, 1), date(2026, 6, 30))"
  • Then the result is "22"
Business days and calendar positions 6 ms
  • When I calculate "networkdays(date(2026, 6, 1), date(2026, 6, 30), date(2026, 6, 19))"
  • Then the result is "21"
Business days and calendar positions 7 ms
  • When I calculate "networkdays(date(2026, 6, 5), date(2026, 6, 1))"
  • Then the result is "-5"
Scientific completions 6 ms
  • When I calculate "deg(pi)"
  • Then the result is "180"
Scientific completions 6 ms
  • When I calculate "deg(pi / 4)"
  • Then the result is "45"
Scientific completions 5 ms
  • When I calculate "sin(rad(90))"
  • Then the result is "1"
Scientific completions 6 ms
  • When I calculate "sinh(0)"
  • Then the result is "0"
Scientific completions 5 ms
  • When I calculate "cosh(0)"
  • Then the result is "1"
Scientific completions 5 ms
  • When I calculate "tanh(0)"
  • Then the result is "0"
Scientific completions 4 ms
  • When I calculate "asinh(0)"
  • Then the result is "0"
Scientific completions 4 ms
  • When I calculate "acosh(1)"
  • Then the result is "0"
Scientific completions 4 ms
  • When I calculate "atanh(0)"
  • Then the result is "0"
atan2 knows its quadrant 13 ms
  • 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 width 5 ms
  • When I calculate "toBase(255, 16)"
  • Then the result is ""FF""
Programmer tools — exact at any width 5 ms
  • When I calculate "toBase(10, 2)"
  • Then the result is ""1010""
Programmer tools — exact at any width 6 ms
  • When I calculate "fromBase("ff", 16)"
  • Then the result is "255"
Programmer tools — exact at any width 7 ms
  • When I calculate "fromBase("1010", 2)"
  • Then the result is "10"
Programmer tools — exact at any width 7 ms
  • When I calculate "toBase(-255, 16)"
  • Then the result is ""-FF""
Programmer tools — exact at any width 5 ms
  • When I calculate "fromBase("-ff", 16)"
  • Then the result is "-255"
Programmer tools — exact at any width 5 ms
  • When I calculate "fromBase(toBase(123456789012345678901234567890, 36), 36)"
  • Then the result is "123456789012345678901234567890"
Programmer tools — exact at any width 5 ms
  • When I calculate "bitAnd(12, 10)"
  • Then the result is "8"
Programmer tools — exact at any width 5 ms
  • When I calculate "bitOr(12, 10)"
  • Then the result is "14"
Programmer tools — exact at any width 5 ms
  • When I calculate "bitXor(12, 10)"
  • Then the result is "6"
Programmer tools — exact at any width 6 ms
  • When I calculate "bitShift(1, 100)"
  • Then the result is "1.267650600228229401496703205376e+30"
Programmer tools — exact at any width 5 ms
  • When I calculate "bitShift(256, -4)"
  • Then the result is "16"
Programmer tools — exact at any width 8 ms
  • When I calculate "bitAnd(0xFF, 0x0F)"
  • Then the result is "15"
Programmer tools — exact at any width 5 ms
  • When I calculate "toBase(0b1111, 16)"
  • Then the result is ""F""
solve() is goal seek in a formula 43 ms
  • 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 definitions 5 ms
  • When I calculate "avg(2, 4, 9) == sum(2, 4, 9) / count(2, 4, 9)"
  • Then the result is "1"
Derived builtins equal their definitions 5 ms
  • When I calculate "stdev(2, 4, 4, 7) == sqrt(variance(2, 4, 4, 7))"
  • Then the result is "1"
Derived builtins equal their definitions 5 ms
  • When I calculate "variance(2, 4) == ((2 - 3)^2 + (4 - 3)^2) / 1"
  • Then the result is "1"
Derived builtins equal their definitions 11 ms
  • When I calculate "cbrt(27) == root(27, 3)"
  • Then the result is "1"
Derived builtins equal their definitions 6 ms
  • When I calculate "geomean(2, 4, 8) == root(product(2, 4, 8), 3)"
  • Then the result is "1"
Derived builtins equal their definitions 6 ms
  • When I calculate "percent(8.25) == 8.25 / 100"
  • Then the result is "1"
Derived builtins equal their definitions 6 ms
  • When I calculate "deg(2) == 2 * 180 / pi"
  • Then the result is "1"
Derived builtins equal their definitions 6 ms
  • When I calculate "choose(10, 4) == fact(10) / (fact(4) * fact(6))"
  • Then the result is "1"
Derived builtins equal their definitions 5 ms
  • When I calculate "perm(10, 4) == fact(10) / fact(6)"
  • Then the result is "1"
Derived builtins equal their definitions 5 ms
  • When I calculate "lcm(12, 18) == 12 * 18 / gcd(12, 18)"
  • Then the result is "1"
Derived builtins equal their definitions 7 ms
  • When I calculate "median(4, 1, 3) == sort([4, 1, 3])[1]"
  • Then the result is "1"
Derived builtins equal their definitions 6 ms
  • When I calculate "sumproduct([2, 3], [10, 100]) == 2 * 10 + 3 * 100"
  • Then the result is "1"
Derived builtins equal their definitions 5 ms
  • When I calculate "npv(0.1, 300, 420) == 300 / 1.1 + 420 / 1.1^2"
  • Then the result is "1"
Derived builtins equal their definitions 7 ms
  • 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"
Derived builtins equal their definitions 5 ms
  • When I calculate "quarter(date(2026, 11, 1)) == trunc((month(date(2026, 11, 1)) - 1) / 3) + 1"
  • Then the result is "1"
Derived builtins equal their definitions 5 ms
  • When I calculate "bitShift(5, 3) == 5 * 2^3"
  • Then the result is "1"
The new functions explain their mistakes 5 ms
  • When I calculate "choose(-1, 2)"
  • Then the calculation fails mentioning "non-negative"
The new functions explain their mistakes 5 ms
  • When I calculate "sort([1, "a"])"
  • Then the calculation fails mentioning "all numbers or all strings"
The new functions explain their mistakes 5 ms
  • When I calculate "seq(1, 10, 0)"
  • Then the calculation fails mentioning "can't be 0"
The new functions explain their mistakes 5 ms
  • When I calculate "mode(1, 2, 3)"
  • Then the calculation fails mentioning "no value repeats"
The new functions explain their mistakes 9 ms
  • When I calculate "percentile(1, 2, 3, 1.5)"
  • Then the calculation fails mentioning "between 0 and 1"
The new functions explain their mistakes 9 ms
  • When I calculate "geomean(4, -9)"
  • Then the calculation fails mentioning "positive"
The new functions explain their mistakes 7 ms
  • When I calculate "correl(1, 2, 3, 4, 5)"
  • Then the calculation fails mentioning "equal-length"
The new functions explain their mistakes 9 ms
  • When I calculate "ipmt(0.05, 0, 12, 1000)"
  • Then the calculation fails mentioning "1 ≤ per ≤ nper"
The new functions explain their mistakes 7 ms
  • When I calculate "syd(30000, 7500, 10, 11)"
  • Then the calculation fails mentioning "1 ≤ per ≤ life"
The new functions explain their mistakes 7 ms
  • When I calculate "toBase(1.5, 16)"
  • Then the calculation fails mentioning "integer"
The new functions explain their mistakes 6 ms
  • When I calculate "fromBase("xyz", 16)"
  • Then the calculation fails mentioning "not a base-16"
The new functions explain their mistakes 6 ms
  • When I calculate "bitAnd(-1, 2)"
  • Then the calculation fails mentioning "non-negative"
The new functions explain their mistakes 5 ms
  • When I calculate "solve(x -> x^2 + 1, 0)"
  • Then the calculation fails mentioning "did not converge"
The new functions explain their mistakes 7 ms
  • When I calculate "ipmt(0.05, 1, 0, 1000)"
  • Then the calculation fails mentioning "1 ≤ per ≤ nper"
The new functions explain their mistakes 5 ms
  • When I calculate "cumipmt(0.05/12, 12, 1000, 5, 2)"
  • Then the calculation fails mentioning "1 ≤ start ≤ end"
The new functions explain their mistakes 5 ms
  • When I calculate "nominal(-2, 12)"
  • Then the calculation fails mentioning "above -100%"
The new functions explain their mistakes 5 ms
  • When I calculate "ddb(30000, 7500, 10, 11)"
  • Then the calculation fails mentioning "1 ≤ per ≤ life"
The new functions explain their mistakes 6 ms
  • When I calculate "sln(1, 1, 0)"
  • Then the calculation fails mentioning "can't be 0"
The new functions explain their mistakes 5 ms
  • When I calculate "toBase(255, 50)"
  • Then the calculation fails mentioning "2–36"
The new functions explain their mistakes 5 ms
  • When I calculate "fromBase("", 16)"
  • Then the calculation fails mentioning "at least one digit"
The new functions explain their mistakes 6 ms
  • When I calculate "bitShift(1, 1.5)"
  • Then the calculation fails mentioning "integer"
The new functions explain their mistakes 65 ms
  • When I calculate "seq(1, 1000000)"
  • Then the calculation fails mentioning "100,000"
The new functions explain their mistakes 5 ms
  • When I calculate "workday(date(2026, 1, 1), 200001)"
  • Then the calculation fails mentioning "too many"
The new functions explain their mistakes 6 ms
  • 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 scenarios
Core arithmetic and algebra 15 ms
  • When I calculate "1 + 2 * 3"
  • Then the result is "7"
Core arithmetic and algebra 9 ms
  • When I calculate "(1 + 2) * 3"
  • Then the result is "9"
Core arithmetic and algebra 5 ms
  • When I calculate "10 / 4"
  • Then the result is "2.5"
Core arithmetic and algebra 6 ms
  • When I calculate "mod(7, 3)"
  • Then the result is "1"
Core arithmetic and algebra 5 ms
  • When I calculate "-2^2"
  • Then the result is "-4"
Core arithmetic and algebra 5 ms
  • When I calculate "2^-1"
  • Then the result is "0.5"
Core arithmetic and algebra 6 ms
  • When I calculate "abs(-5)"
  • Then the result is "5"
Core arithmetic and algebra 4 ms
  • When I calculate "min(3, 1, 2)"
  • Then the result is "1"
Core arithmetic and algebra 5 ms
  • When I calculate "max(3, 1, 2)"
  • Then the result is "3"
Core arithmetic and algebra 5 ms
  • When I calculate "floor(2.7)"
  • Then the result is "2"
Core arithmetic and algebra 4 ms
  • When I calculate "floor(-1.5)"
  • Then the result is "-2"
Core arithmetic and algebra 5 ms
  • When I calculate "ceil(2.1)"
  • Then the result is "3"
Core arithmetic and algebra 4 ms
  • When I calculate "ceil(-1.5)"
  • Then the result is "-1"
Core arithmetic and algebra 4 ms
  • When I calculate "trunc(-2.7)"
  • Then the result is "-2"
Core arithmetic and algebra 4 ms
  • When I calculate "round(2.345, 2)"
  • Then the result is "2.34"
Core arithmetic and algebra 4 ms
  • When I calculate "round(2.567, 2)"
  • Then the result is "2.57"
Core arithmetic and algebra 5 ms
  • When I calculate "round(2.5)"
  • Then the result is "2"
Core arithmetic and algebra 4 ms
  • When I calculate "mod(7, 3)"
  • Then the result is "1"
Core arithmetic and algebra 4 ms
  • When I calculate "fact(5)"
  • Then the result is "120"
Core arithmetic and algebra 4 ms
  • When I calculate "fact(20)"
  • Then the result is "2432902008176640000"
Core arithmetic and algebra 4 ms
  • When I calculate "gcd(12, 18)"
  • Then the result is "6"
Core arithmetic and algebra 5 ms
  • When I calculate "lcm(4, 6)"
  • Then the result is "12"
Core arithmetic and algebra 5 ms
  • When I calculate "percent(8.25)"
  • Then the result is "0.0825"
Core arithmetic and algebra 5 ms
  • When I calculate "sqrt(144)"
  • Then the result is "12"
Core arithmetic and algebra 4 ms
  • When I calculate "cbrt(27)"
  • Then the result is "3"
Core arithmetic and algebra 4 ms
  • When I calculate "cbrt(-27)"
  • Then the result is "-3"
Core arithmetic and algebra 4 ms
  • When I calculate "root(32, 5)"
  • Then the result is "2"
Core arithmetic and algebra 4 ms
  • When I calculate "pow(4, 0.5)"
  • Then the result is "2"
Mathematical symbols are first-class 4 ms
  • When I calculate "√16"
  • Then the result is "4"
Mathematical symbols are first-class 4 ms
  • When I calculate "√(2 + 2)"
  • Then the result is "2"
Mathematical symbols are first-class 4 ms
  • When I calculate "√2^2"
  • Then the result is "2"
Mathematical symbols are first-class 6 ms
  • When I calculate "6 × 7 ÷ 2"
  • Then the result is "21"
Mathematical symbols are first-class 5 ms
  • When I calculate "6 ÷ 2 − 1"
  • Then the result is "2"
Mathematical symbols are first-class 4 ms
  • When I calculate "3 · 4"
  • Then the result is "12"
Mathematical symbols are first-class 6 ms
  • When I calculate "π − pi"
  • Then the result is "0"
Mathematical symbols are first-class 4 ms
  • When I calculate "τ ÷ π"
  • Then the result is "2"
Mathematical symbols are first-class 4 ms
  • When I calculate "pi"
  • Then the result is "3.14159265358979323846264338327950288419716939937510582097494"
Mathematical symbols are first-class 5 ms
  • When I calculate "3 ≠ 2"
  • Then the result is "1"
Mathematical symbols are first-class 5 ms
  • When I calculate "2 ≤ 2"
  • Then the result is "1"
Constants are protected 7 ms
  • When I calculate "π = 3"
  • Then the calculation fails mentioning "cannot assign to 'π'"
Trigonometry (radians) 4 ms
  • When I calculate "sin(pi / 2)"
  • Then the result is "1"
Trigonometry (radians) 4 ms
  • When I calculate "cos(pi)"
  • Then the result is "-1"
Trigonometry (radians) 4 ms
  • When I calculate "tan(pi / 4)"
  • Then the result is "0.9999999999999999"
Trigonometry (radians) 4 ms
  • When I calculate "asin(1) * 2"
  • Then the result is "3.1415926535897932"
Trigonometry (radians) 4 ms
  • When I calculate "acos(0) * 2"
  • Then the result is "3.1415926535897932"
Trigonometry (radians) 4 ms
  • When I calculate "atan(1) * 4"
  • Then the result is "3.1415926535897932"
The degree postfix converts to radians 4 ms
  • When I calculate "sin(90°)"
  • Then the result is "1"
The degree postfix converts to radians 4 ms
  • When I calculate "cos(180°)"
  • Then the result is "-1"
The degree postfix converts to radians 4 ms
  • When I calculate "90° == pi / 2"
  • Then the result is "1"
Exponentials and logarithms 4 ms
  • When I calculate "exp(0)"
  • Then the result is "1"
Exponentials and logarithms 4 ms
  • When I calculate "ln(e)"
  • Then the result is "1"
Exponentials and logarithms 4 ms
  • When I calculate "log10(1000)"
  • Then the result is "3"
Exponentials and logarithms 4 ms
  • When I calculate "log(2, 8)"
  • Then the result is "3"
Exponentials and logarithms 4 ms
  • When I calculate "sin(0)"
  • Then the result is "0"
Exponentials and logarithms 4 ms
  • When I calculate "cos(0)"
  • Then the result is "1"
exp inverts ln to within the Double seam 4 ms
  • When I calculate "exp(ln(7))"
  • Then the result is within "0.000000000000002" of "7"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "avg(2, 4, 9)"
  • Then the result is "5"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "median(1, 9, 5)"
  • Then the result is "5"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "median(1, 2, 3, 4)"
  • Then the result is "2.5"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "count(1, 2, 3)"
  • Then the result is "3"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "product(2, 3, 4)"
  • Then the result is "24"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "stdev(2, 4, 4, 4, 5, 5, 7, 9)"
  • Then the result is "2.1380899352993950774764278470380281724320113187307"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "avg([2, 4, 9])"
  • Then the result is "5"
Statistics over lists, arrays, and ranges 4 ms
  • When I calculate "stdev(1, 3) - sqrt(2)"
  • Then the result is "0"
Products mirror summation 11 ms
  • 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 4 ms
  • 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 4 ms
  • 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 15 ms
  • 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 8 ms
  • 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 38 ms
  • 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) 18 ms
  • When I calculate "pmt(0.05/12, 360, 200000)"
  • Then the result is within "0.01" of "-1073.64"
Finance agrees with Excel (independent cross-validation) 4 ms
  • When I calculate "pmt(0, 12, 1200)"
  • Then the result is within "0.000001" of "-100"
Finance agrees with Excel (independent cross-validation) 7 ms
  • When I calculate "fv(0.06/12, 120, -100)"
  • Then the result is within "0.01" of "16387.93"
Finance agrees with Excel (independent cross-validation) 3 ms
  • When I calculate "fv(0, 12, -100)"
  • Then the result is within "0.000001" of "1200"
Finance agrees with Excel (independent cross-validation) 5 ms
  • When I calculate "pv(0.04/12, 60, -500)"
  • Then the result is within "0.01" of "27149.53"
Finance agrees with Excel (independent cross-validation) 4 ms
  • When I calculate "nper(0.05/12, -1073.64, 200000)"
  • Then the result is within "0.01" of "360"
Finance agrees with Excel (independent cross-validation) 5 ms
  • When I calculate "nper(0, -100, 1200)"
  • Then the result is within "0.000001" of "12"
Finance agrees with Excel (independent cross-validation) 5 ms
  • When I calculate "rate(360, -1073.64, 200000) * 12"
  • Then the result is within "0.0001" of "0.05"
Finance agrees with Excel (independent cross-validation) 4 ms
  • When I calculate "npv(0.1, 3000, 4200, 6800)"
  • Then the result is within "0.01" of "11307.29"
Finance agrees with Excel (independent cross-validation) 5 ms
  • When I calculate "irr(-70000, 12000, 15000, 18000, 21000, 26000)"
  • Then the result is within "0.0001" of "0.0866"
Finance agrees with Excel (independent cross-validation) 5 ms
  • When I calculate "effectiveRate(0.06, 12)"
  • Then the result is within "0.000001" of "0.061678"
Finance agrees with Excel (independent cross-validation) 4 ms
  • When I calculate "markup(80, 25)"
  • Then the result is within "0.000001" of "100"
Finance agrees with Excel (independent cross-validation) 5 ms
  • When I calculate "percentOf(30, 120)"
  • Then the result is within "0.000001" of "25"
Finance agrees with Excel (independent cross-validation) 4 ms
  • When I calculate "percentChange(80, 100)"
  • Then the result is within "0.000001" of "25"
A mortgage's lifetime cost, built up across lines 27 ms
  • 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 4 ms
  • When I calculate "irr(1000, 2000)"
  • Then the calculation fails mentioning "both positive and negative"
Finance (spreadsheet sign convention) 4 ms
  • When I calculate "fv(0.1, 2, -100)"
  • Then the result is "210"
Finance (spreadsheet sign convention) 5 ms
  • When I calculate "npv(0, 10, 20, 30)"
  • Then the result is "60"
Finance (spreadsheet sign convention) 4 ms
  • When I calculate "npv(0.1, 110)"
  • Then the result is "100"
Finance (spreadsheet sign convention) 4 ms
  • When I calculate "nper(0, -10, 100)"
  • Then the result is "10"
Finance (spreadsheet sign convention) 4 ms
  • When I calculate "irr(-100, 110)"
  • Then the result is "0.1"
Finance (spreadsheet sign convention) 4 ms
  • When I calculate "effectiveRate(0.12, 12)"
  • Then the result is "0.126825030131969720661201"
Accounting shorthands 4 ms
  • When I calculate "margin(100, 80)"
  • Then the result is "20"
Accounting shorthands 4 ms
  • When I calculate "markup(80, 25)"
  • Then the result is "100"
Accounting shorthands 4 ms
  • When I calculate "percentOf(50, 200)"
  • Then the result is "25"
Accounting shorthands 9 ms
  • When I calculate "percentChange(100, 150)"
  • Then the result is "50"
Date arithmetic on exact day serials 16 ms
  • When I calculate "weekday(date(2026, 6, 6))"
  • Then the result is "6"
Date arithmetic on exact day serials 7 ms
  • When I calculate "weekday(date(2026, 6, 8))"
  • Then the result is "1"
Date arithmetic on exact day serials 4 ms
  • When I calculate "year(date(2026, 6, 6))"
  • Then the result is "2026"
Date arithmetic on exact day serials 4 ms
  • When I calculate "month(date(2026, 6, 6))"
  • Then the result is "6"
Date arithmetic on exact day serials 4 ms
  • When I calculate "day(date(2026, 6, 6))"
  • Then the result is "6"
Date arithmetic on exact day serials 4 ms
  • When I calculate "days(date(2026, 3, 1), date(2026, 2, 1))"
  • Then the result is "28"
Date arithmetic on exact day serials 4 ms
  • When I calculate "edate(date(2026, 1, 31), 1) == date(2026, 2, 28)"
  • Then the result is "1"
Date arithmetic on exact day serials 4 ms
  • When I calculate "eomonth(date(2024, 2, 1), 0) == date(2024, 2, 29)"
  • Then the result is "1"
Date arithmetic on exact day serials 4 ms
  • When I calculate "day(eomonth(date(1900, 2, 1), 0))"
  • Then the result is "28"
Date arithmetic on exact day serials 4 ms
  • When I calculate "day(eomonth(date(2000, 2, 1), 0))"
  • Then the result is "29"
Date arithmetic on exact day serials 4 ms
  • When I calculate "days(date(2001, 1, 1), date(2000, 1, 1))"
  • Then the result is "366"
Logic composes with everything 4 ms
  • 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 scenarios
Programmer mode reads glyphs as bitwise and modulo 17 ms
  • Given the calculator is in programmer mode
  • When I calculate "5 ^ 3"
  • Then the result is "6"
Programmer mode reads glyphs as bitwise and modulo 5 ms
  • Given the calculator is in programmer mode
  • When I calculate "12 & 10"
  • Then the result is "8"
Programmer mode reads glyphs as bitwise and modulo 4 ms
  • Given the calculator is in programmer mode
  • When I calculate "1 << 4"
  • Then the result is "16"
Programmer mode reads glyphs as bitwise and modulo 5 ms
  • Given the calculator is in programmer mode
  • When I calculate "8 >> 2"
  • Then the result is "2"
Programmer mode reads glyphs as bitwise and modulo 3 ms
  • Given the calculator is in programmer mode
  • When I calculate "17 % 5"
  • Then the result is "2"
Programmer mode reads glyphs as bitwise and modulo 4 ms
  • 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 4 ms
  • Given the calculator is in programmer mode
  • When I calculate "12 | 3"
  • Then the result is "15"
Programmer-mode bitwise precedence follows Python 4 ms
  • Given the calculator is in programmer mode
  • When I calculate "1 & 1 == 1"
  • Then the result is "1"
Bitwise glyphs are Programmer-mode only 2 ms
  • When I calculate "5 & 3"
  • Then the calculation fails mentioning "Programmer-mode operator"
Bitwise glyphs are Programmer-mode only 2 ms
  • When I calculate "1 << 2"
  • Then the calculation fails mentioning "Programmer-mode operator"
Bitwise glyphs are Programmer-mode only 3 ms
  • When I calculate "8 >> 1"
  • Then the calculation fails mentioning "Programmer-mode operator"
Scientific mode keeps the Normal arithmetic core 3 ms
  • Given the calculator is in scientific mode
  • When I calculate "2 ^ 3"
  • Then the result is "8"
Scientific mode keeps the Normal arithmetic core 3 ms
  • Given the calculator is in scientific mode
  • When I calculate "3%"
  • Then the result is "0.03"
Scientific mode keeps the Normal arithmetic core 3 ms
  • 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 3 ms
  • 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 notation 4 ms
  • Given the calculator is in scientific mode
  • When I calculate "123456 * 2"
  • Then the result is "246912"
  • And the log echoes "2.46912e5"
Scientific mode echoes plain numbers in scientific notation 4 ms
  • Given the calculator is in scientific mode
  • When I calculate "5"
  • Then the result is "5"
  • And the log echoes "5e0"
Scientific mode echoes plain numbers in scientific notation 5 ms
  • 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"
Scientific mode echoes plain numbers in scientific notation 5 ms
  • 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 3 4 ms
  • Given the calculator is in scientific mode
  • And the scientific style is "eng"
  • When I calculate "123456 * 2"
  • Then the log echoes "246.912e3"
The engineering style snaps the exponent to a multiple of 3 5 ms
  • Given the calculator is in scientific mode
  • And the scientific style is "eng"
  • When I calculate "0.05"
  • Then the log echoes "50e-3"
The engineering style snaps the exponent to a multiple of 3 5 ms
  • 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 3 ms
  • 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 3 ms
  • 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 4 ms
  • 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 2 ms
  • When I calculate ":mode finance"
  • Then the calculation fails mentioning "currency now works"
Currency carries through arithmetic 3 ms
  • When I calculate "$10"
  • Then the result is "Money(10, "USD")"
  • And the log echoes "$10.00"
Currency carries through arithmetic 3 ms
  • When I calculate "$10 * 5%"
  • Then the result is "Money(0.5, "USD")"
  • And the log echoes "$0.50"
Currency carries through arithmetic 3 ms
  • When I calculate "€10 * .4"
  • Then the result is "Money(4, "EUR")"
  • And the log echoes "€4.00"
Currency carries through arithmetic 3 ms
  • When I calculate "$10 + $5"
  • Then the result is "Money(15, "USD")"
  • And the log echoes "$15.00"
Currency carries through arithmetic 3 ms
  • When I calculate "$10 - $2.50"
  • Then the result is "Money(7.5, "USD")"
  • And the log echoes "$7.50"
Currency carries through arithmetic 9 ms
  • When I calculate "$100 / 4"
  • Then the result is "Money(25, "USD")"
  • And the log echoes "$25.00"
Currency carries through arithmetic 3 ms
  • When I calculate "£1234.567"
  • Then the result is "Money(1234.567, "GBP")"
  • And the log echoes "£1,234.57"
Currency carries through arithmetic 3 ms
  • 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 4 ms
  • 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 2 ms
  • When I calculate "Money(10, "XYZ")"
  • Then the calculation fails mentioning "unknown currency"
An unsupported currency glyph is refused 2 ms
  • When I calculate "₫100"
  • Then the calculation fails mentioning "unsupported currency"
Percent on a currency amount is refused 2 ms
  • When I calculate "$9%"
  • Then the calculation fails mentioning "can't apply % to a currency amount"
A plain number absorbs into the currency 2 ms
  • When I calculate "$10 + 5"
  • Then the log echoes "$15.00"
A plain number absorbs into the currency 2 ms
  • When I calculate "5 + $10"
  • Then the log echoes "$15.00"
A plain number absorbs into the currency 2 ms
  • When I calculate "$10 * 3"
  • Then the log echoes "$30.00"
Mixing currencies is refused 2 ms
  • When I calculate "$10 + €5"
  • Then the calculation fails mentioning "can't mix currencies"
Mixing currencies is refused 2 ms
  • When I calculate "€10 - $5"
  • Then the calculation fails mentioning "can't mix currencies"
Mixing currencies is refused 2 ms
  • When I calculate "$10 * £2"
  • Then the calculation fails mentioning "can't mix currencies"
The currency survives every arithmetic operator 2 ms
  • When I calculate "$10 * $2"
  • Then the log echoes "$20.00"
The currency survives every arithmetic operator 2 ms
  • When I calculate "$10 / $2"
  • Then the log echoes "$5.00"
The currency survives every arithmetic operator 2 ms
  • When I calculate "$10 + $2"
  • Then the log echoes "$12.00"
The currency survives every arithmetic operator 1 ms
  • When I calculate "$10 - $2"
  • Then the log echoes "$8.00"
Negation keeps the currency and puts the sign outside the symbol 3 ms
  • When I calculate "-$1234.5"
  • Then the result is "Money(-1234.5, "USD")"
  • And the log echoes "-$1,234.50"
Grouped numbers echo their grouping 3 ms
  • When I calculate "138,561"
  • Then the result is "138561"
  • And the log echoes "138,561"
Grouped numbers echo their grouping 3 ms
  • When I calculate "138,561 * 9%"
  • Then the result is "12470.49"
  • And the log echoes "12,470.49"
Grouped numbers echo their grouping 3 ms
  • When I calculate "1,000 + 1"
  • Then the result is "1001"
  • And the log echoes "1,001"
Grouped numbers echo their grouping 4 ms
  • When I calculate "1,234,567"
  • Then the result is "1234567"
  • And the log echoes "1,234,567"
Grouped numbers echo their grouping 3 ms
  • When I calculate "12,470.49 * 2"
  • Then the result is "24940.98"
  • And the log echoes "24,940.98"
Grouped numbers echo their grouping 3 ms
  • When I calculate "-138,561"
  • Then the result is "-138561"
  • And the log echoes "-138,561"
Grouped currency composes through a subexpression 3 ms
  • 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 2 ms
  • When I calculate "max(138,561)"
  • Then the result is "561"
Grouping is suppressed inside an array literal 2 ms
  • When I calculate "sum([1,500])"
  • Then the result is "501"
Malformed thousands groups are refused 2 ms
  • When I calculate "1,23"
  • Then the calculation fails mentioning "thousands group"
Malformed thousands groups are refused 2 ms
  • When I calculate "1,2345"
  • Then the calculation fails mentioning "thousands group"
Malformed thousands groups are refused 2 ms
  • When I calculate "1234,567"
  • Then the calculation fails mentioning "thousands group"

Namespaces group declarations under a qualified name

33 scenarios
A namespace exposes its data types by qualified name 5 ms
  • 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 2 ms
  • 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 3 ms
  • 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 5 ms
  • 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 3 ms
  • 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 2 ms
  • 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 2 ms
  • 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 loudly 2 ms
  • 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"
Qualified construction validates and resolves loudly 2 ms
  • 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 1 ms
  • When I calculate "namespace Empty { }"
  • Then the calculation fails mentioning "at least one declaration"
Namespace members are separated by a semicolon 1.0 ms
  • 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 2 ms
  • 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 3 ms
  • 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 3 ms
  • 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 2 ms
  • 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 2 ms
  • 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 1 ms
  • 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 2 ms
  • 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 2 ms
  • 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 4 ms
  • 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 0.8 ms
  • When I calculate "import Nope"
  • Then the calculation fails mentioning "no namespace 'Nope'"
Importing a name that collides with a global is loud 2 ms
  • 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 1 ms
  • 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 3 ms
  • 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 3 ms
  • 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 0.9 ms
  • When I calculate "Finance::sqrt(16)"
  • Then the calculation fails mentioning "unknown function 'Finance::sqrt'"
A qualified builtin works as a value 0.8 ms
  • 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 1 ms
  • 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 5 ms
  • 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 4 ms
  • 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 9 ms
  • 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 7 ms
  • 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 3 ms
  • 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 scenarios
Workbook reports its sheets 2 ms
  • Given a sheet named "Budget"
  • When I calculate "Workbook.count"
  • Then the result is "2"
Worksheet names are readable 0.7 ms
  • Given a sheet named "Budget"
  • When I calculate "Workbook.worksheets[1].name == "Budget""
  • Then the result is "1"
A worksheet is reachable by name 0.6 ms
  • 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 0.5 ms
  • 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 1.0 ms
  • 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 0.6 ms
  • 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 0.5 ms
  • 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 0.8 ms
  • 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 0.5 ms
  • Given a sheet named "Budget"
  • When I calculate "len(sheetNames())"
  • Then the result is "2"
rowCount() reports the grid size 0.3 ms
  • When I calculate "rowCount()"
  • Then the result is "1000"
A formula reading a cell through reflection recomputes live 0.6 ms
  • 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 0.5 ms
  • When I calculate "cell(x) = x * 10"
  • And I calculate "cell(5)"
  • Then the result is "50"
An unknown sheet is reported clearly 0.3 ms
  • When I calculate "cell("Nope", "A", 1).value"
  • Then the calculation fails mentioning "unknown sheet"

Scripts — multi-line sources split into logical statements

8 scenarios
A pretty multi-line namespace block runs as one statement 0.8 ms
  • When I run the script:
  • Then the result is "3"
Parentheses continue a statement across lines 0.3 ms
  • When I run the script:
  • Then the result is "10"
Brackets continue a statement across lines 0.3 ms
  • When I run the script:
  • Then the result is "3"
Shebang and comment lines pass between statements 0.2 ms
  • When I run the script:
  • Then the result is "2"
A first-line comment documents a multi-line definition 0.5 ms
  • 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 0.2 ms
  • When I run the script:
  • Then the result is "13"
An unclosed block at end of input is a loud error 0.2 ms
  • When I run the script:
  • Then the calculation fails mentioning "unterminated"
Thousands grouping works inside a continued statement 0.4 ms
  • When I run the script:
  • Then the result is "12470.49"
  • And the log echoes "12,470.49"

Working in the grid

21 scenarios
Cells reference each other 2 ms
  • Given cell B:1 contains "1200"
  • And cell B:2 contains "=B:1 * 2"
  • Then cell B:2 shows "2400"
Labels never become errors 0.8 ms
  • Given cell A:1 contains "Q1 revenue"
  • Then cell A:1 shows "Q1 revenue"
A comment cell is a note that holds no value 0.5 ms
  • 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 2 ms
  • 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 0.9 ms
  • 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 1.0 ms
  • 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 1 ms
  • 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 2 ms
  • 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 1 ms
  • 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 1 ms
  • 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 1.0 ms
  • Given cell B:1 contains "1200"
  • When I calculate "B:1 * 2"
  • Then the result is "2400"
Explicit markers override auto-detection 1 ms
  • 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 1.0 ms
  • 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 0.5 ms
  • Given cell A:1 contains "=B:9 + 5"
  • Then cell A:1 shows "5"
Circular references are caught, not infinite 1 ms
  • 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 1 ms
  • 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 4 ms
  • 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 1 ms
  • 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 3 ms
  • 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 0.6 ms
  • 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 8 ms
  • 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 scenarios
Text and structure functions 6 ms
  • When I calculate "len([1, 2, 3])"
  • Then the result is "3"
Text and structure functions 4 ms
  • When I calculate "len("hello")"
  • Then the result is "5"
Text and structure functions 3 ms
  • When I calculate "len({a: 1, b: 2})"
  • Then the result is "2"
Text and structure functions 2 ms
  • When I calculate "first([5, 6, 7])"
  • Then the result is "5"
Text and structure functions 3 ms
  • When I calculate "last([5, 6, 7])"
  • Then the result is "7"
Text and structure functions 3 ms
  • When I calculate "keys({name: "Ada", age: 36})"
  • Then the result is "["name", "age"]"
Text and structure functions 2 ms
  • When I calculate "values({a: 1, b: 2})"
  • Then the result is "[1, 2]"
Text and structure functions 2 ms
  • When I calculate "sum(values({a: 1, b: 2}))"
  • Then the result is "3"
Text and structure functions 2 ms
  • When I calculate "concat("Q", 1, "-", 2026)"
  • Then the result is ""Q1-2026""
Text and structure functions 2 ms
  • When I calculate "concat([1, 2], [3])"
  • Then the result is "[1, 2, 3]"
Text and structure functions 2 ms
  • When I calculate ""Q" + 1"
  • Then the result is ""Q1""
Text and structure functions 2 ms
  • When I calculate ""a" + "b" + "c""
  • Then the result is ""abc""
Text and structure functions 2 ms
  • When I calculate ""abc"[0]"
  • Then the result is ""a""
Text and structure functions 2 ms
  • When I calculate "slice("hello", 0, 2)"
  • Then the result is ""he""
Text and structure functions 2 ms
  • When I calculate "slice("hello", 2)"
  • Then the result is ""llo""
Text and structure functions 3 ms
  • When I calculate "slice("hello", 1, 4)"
  • Then the result is ""ell""
Text and structure functions 2 ms
  • When I calculate "slice("hello", 0, len("hello"))"
  • Then the result is ""hello""
Text and structure functions 2 ms
  • When I calculate "slice([1, 2, 3, 4], 1, 3)"
  • Then the result is "[2, 3]"
Text and structure functions 2 ms
  • When I calculate "slice([1, 2, 3, 4], 2)"
  • Then the result is "[3, 4]"
Text and structure functions 2 ms
  • When I calculate "split("a,b,c", ",")"
  • Then the result is "["a", "b", "c"]"
Text and structure functions 3 ms
  • When I calculate "split("nosep", ",")"
  • Then the result is "["nosep"]"
Text and structure functions 3 ms
  • When I calculate "len(split("a,b,c", ","))"
  • Then the result is "3"
Text and structure functions 2 ms
  • When I calculate "charCode("A")"
  • Then the result is "65"
Text and structure functions 2 ms
  • When I calculate "fromCharCode(65)"
  • Then the result is ""A""
Text and structure functions 2 ms
  • When I calculate "fromCharCode(charCode("z"))"
  • Then the result is ""z""
Text and structure functions 2 ms
  • When I calculate "[[1, 2], [3, 4]][1][0]"
  • Then the result is "3"
Text and structure functions 2 ms
  • When I calculate "{a: [1, 2]}.a[1]"
  • Then the result is "2"
Text and structure functions 2 ms
  • When I calculate "[1, 2] == [1, 2]"
  • Then the result is "1"
Text and structure functions 2 ms
  • When I calculate "{a: 1, b: 2} == {b: 2, a: 1}"
  • Then the result is "1"
Text and structure functions 2 ms
  • When I calculate ""x" != 5"
  • Then the result is "1"
Structure mistakes explain themselves 2 ms
  • When I calculate "[1, 2][5]"
  • Then the calculation fails mentioning "out of range"
Structure mistakes explain themselves 2 ms
  • When I calculate "{a: 1}.b"
  • Then the calculation fails mentioning "no key 'b'"
Structure mistakes explain themselves 2 ms
  • When I calculate "first([])"
  • Then the calculation fails mentioning "empty array"
Structure mistakes explain themselves 2 ms
  • When I calculate "sum(["a"])"
  • Then the calculation fails mentioning "works on numbers"
Structure mistakes explain themselves 2 ms
  • When I calculate ""a" * 2"
  • Then the calculation fails mentioning "expected a number"
Structure mistakes explain themselves 3 ms
  • When I calculate "{a: 1, a: 2}"
  • Then the calculation fails mentioning "duplicate key"
Structure mistakes explain themselves 3 ms
  • When I calculate "slice("abc", 0, 9)"
  • Then the calculation fails mentioning "out of range"
Structure mistakes explain themselves 3 ms
  • When I calculate "slice("abc", -1, 2)"
  • Then the calculation fails mentioning "out of range"
Structure mistakes explain themselves 2 ms
  • When I calculate "split("abc", "")"
  • Then the calculation fails mentioning "non-empty separator"
Structure mistakes explain themselves 5 ms
  • When I calculate "charCode("ab")"
  • Then the calculation fails mentioning "single character"
Structure mistakes explain themselves 4 ms
  • When I calculate "fromCharCode(-1)"
  • Then the calculation fails mentioning "valid character"
charCode enables ordered character-class tests 7 ms
  • 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 7 ms
  • 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 12 ms
  • 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 10 ms
  • 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"