The Anzan language itself
✓ 48 examplesAs a reader of docs/ANZAN.md
I want the grammar's promises to be executable
So that the spec and the engine can never quietly disagree
Operator precedence and associativityoutline · 6
✓2 + 3 * 4, 14, # multiplicative over additiverun ↗
When I calculate "2 + 3 * 4"
Then the result is "14"
✓2^3^2, 512, # ^ is right-associativerun ↗
When I calculate "2^3^2"
Then the result is "512"
✓-2^2, -4, # unary minus binds looser than ^run ↗
When I calculate "-2^2"
Then the result is "-4"
✓2^-2, 0.25, # the exponent may carry its own signrun ↗
When I calculate "2^-2"
Then the result is "0.25"
✓[10, 2][0]^2, 100, # postfix indexing binds tighter than ^run ↗
When I calculate "[10, 2][0]^2"
Then the result is "100"
✓√4 + 5, 7, # prefix √ is unary, not greedyrun ↗
When I calculate "√4 + 5"
Then the result is "7"
Number literalsoutline · 6
✓1_000 + 0, 1000run ↗
When I calculate "1_000 + 0"
Then the result is "1000"
✓2.5e-3, 0.0025run ↗
When I calculate "2.5e-3"
Then the result is "0.0025"
✓.5 + .5, 1run ↗
When I calculate ".5 + .5"
Then the result is "1"
✓0xFF, 255run ↗
When I calculate "0xFF"
Then the result is "255"
✓0b1010, 10run ↗
When I calculate "0b1010"
Then the result is "10"
✓0xDEAD_BEEF, 3735928559run ↗
When I calculate "0xDEAD_BEEF"
Then the result is "3735928559"
Pinned cell references evaluate like plain onesoutline · 3
✓$A:$1 * 2run ↗
Given cell A:1 contains "21"
When I calculate "$A:$1 * 2"
Then the result is "42"
✓$A:1 + A:1run ↗
Given cell A:1 contains "21"
When I calculate "$A:1 + A:1"
Then the result is "42"
✓A:$1 * 2run ↗
Given cell A:1 contains "21"
When I calculate "A:$1 * 2"
Then the result is "42"
A dangling $ is a loud lex erroroutline · 3
✓$run ↗
When I calculate "$"
Then the calculation fails mentioning "pins a cell reference"
✓$xrun ↗
When I calculate "$x"
Then the calculation fails mentioning "pins a cell reference"
✓2 + $raterun ↗
When I calculate "2 + $rate"
Then the calculation fails mentioning "pins a cell reference"
✓A comment-only line is a note, not an errorrun ↗
When I calculate "# the calc below confirms our test"
Then the result is "# the calc below confirms our test"
✓A standalone note never touches ansrun ↗
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 evaluationrun ↗
When I calculate "5 + 3 # adds them"
Then the result is "8"
✓A hash inside a string is not a commentrun ↗
When I calculate "len("a # b")"
Then the result is "5"
Malformed programmer literals fail loudlyoutline · 4
✓0xFG, malformed hexrun ↗
When I calculate "0xFG"
Then the calculation fails mentioning "malformed hex"
✓0x1.5, malformed hexrun ↗
When I calculate "0x1.5"
Then the calculation fails mentioning "malformed hex"
✓0x, needs digitsrun ↗
When I calculate "0x"
Then the calculation fails mentioning "needs digits"
✓0b12, malformed binaryrun ↗
When I calculate "0b12"
Then the calculation fails mentioning "malformed binary"
✓Implicit multiplication covers parens, names, and constantsrun ↗
When I calculate "x9 = 4"
And I calculate "(2)(3) + 2x9"
Then the result is "14"
✓Modulo is exactrun ↗
When I calculate "mod(0.3, 0.1)"
Then the result is "0"
Percent literalsoutline · 5
✓3%, 0.03run ↗
When I calculate "3%"
Then the result is "0.03"
✓1 * 3%, 0.03run ↗
When I calculate "1 * 3%"
Then the result is "0.03"
✓100 + 5%, 100.05run ↗
When I calculate "100 + 5%"
Then the result is "100.05"
✓50% * 2, 1run ↗
When I calculate "50% * 2"
Then the result is "1"
✓(2 + 3)%, 0.05run ↗
When I calculate "(2 + 3)%"
Then the result is "0.05"
A number can't directly follow another valueoutline · 3
✓3 4run ↗
When I calculate "3 4"
Then the calculation fails mentioning "operator"
✓3 % 4run ↗
When I calculate "3 % 4"
Then the calculation fails mentioning "operator"
✓(1) 2run ↗
When I calculate "(1) 2"
Then the calculation fails mentioning "operator"
✓A string is not a conditionrun ↗
When I calculate "if("a", 1, 2)"
Then the calculation fails mentioning "number"
✓Empty reductions yield identitiesrun ↗
When I calculate "∑_i=1^0(i) + ∏_i=1^0(i)"
Then the result is "1"
✓A reduction spanning too many terms is refusedrun ↗
When I calculate "∑_i=1^200000(i)"
Then the calculation fails mentioning "100,000"
Reserved words refuse assignmentoutline · 3
✓ans = 5run ↗
When I calculate "ans = 5"
Then the calculation fails mentioning "cannot assign"
✓sigma = 1run ↗
When I calculate "sigma = 1"
Then the calculation fails mentioning "cannot assign"
✓true = 0run ↗
When I calculate "true = 0"
Then the calculation fails mentioning "cannot assign"
✓Parameters shadow globals without clobbering themrun ↗
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 timerun ↗
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 manualrun ↗
When I calculate "man(if)"
Then documentation is shown mentioning "taken branch"
✓String escapes are honoredrun ↗
When I calculate "len("a\tb")"
Then the result is "3"
✓The word data is still an ordinary namerun ↗
When I calculate "data = 5"
And I calculate "data * 2"
Then the result is "10"
✓Compact named arguments versus cell referencesrun ↗
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 examplesAs someone doing money math
I want exact decimal answers from typed expressions
So that floating-point drift never lies to me
Everyday calculations are exactoutline · 10
✓0.1 + 0.2, 0.3run ↗
When I calculate "0.1 + 0.2"
Then the result is "0.3"
✓2(3 + 4), 14run ↗
When I calculate "2(3 + 4)"
Then the result is "14"
✓2^10, 1024run ↗
When I calculate "2^10"
Then the result is "1024"
✓∑(1, 2, 3), 6run ↗
When I calculate "∑(1, 2, 3)"
Then the result is "6"
✓∑_i=1^10(i^2), 385run ↗
When I calculate "∑_i=1^10(i^2)"
Then the result is "385"
✓pmt(0.05/12, 360, 200000), -1073.643246024277969656985158225109053609679713701run ↗
When I calculate "pmt(0.05/12, 360, 200000)"
Then the result is "-1073.643246024277969656985158225109053609679713701"
✓margin(100, 80), 20run ↗
When I calculate "margin(100, 80)"
Then the result is "20"
✓date(2026, 6, 6) - date(2026, 1, 1), 156run ↗
When I calculate "date(2026, 6, 6) - date(2026, 1, 1)"
Then the result is "156"
✓if(2 > 1, 10, 20), 10run ↗
When I calculate "if(2 > 1, 10, 20)"
Then the result is "10"
✓gcd(48, 36), 12run ↗
When I calculate "gcd(48, 36)"
Then the result is "12"
✓A hundred dimes make exactly a dollar bagrun ↗
When I calculate "∑_i=1^100(0.1)"
Then the result is "10"
✓The previous answer carries forwardrun ↗
When I calculate "6 * 7"
And I calculate "ans + 8"
Then the result is "50"
✓Variables persist across calculationsrun ↗
When I calculate "rate2026 = 0.0825"
And I calculate "1200 * rate2026"
Then the result is "99"
✓A leading equals sign is toleratedrun ↗
When I calculate "= 1 + 2"
Then the result is "3"
✓A failed calculation never clobbers the previous answerrun ↗
When I calculate "6 * 7"
And I calculate "1 / 0"
And I calculate "ans + 0"
Then the result is "42"
✓Dividing by zero explains itselfrun ↗
When I calculate "1 / 0"
Then the calculation fails mentioning "division by zero"
✓Typos are caught, not guessedrun ↗
When I calculate "12 * rte"
Then the calculation fails mentioning "unknown variable 'rte'"
Comparisons answer 1 or 0outline · 7
✓2 < 3, 1run ↗
When I calculate "2 < 3"
Then the result is "1"
✓3 < 2, 0run ↗
When I calculate "3 < 2"
Then the result is "0"
✓3 > 2, 1run ↗
When I calculate "3 > 2"
Then the result is "1"
✓2 <= 2, 1run ↗
When I calculate "2 <= 2"
Then the result is "1"
✓2 >= 3, 0run ↗
When I calculate "2 >= 3"
Then the result is "0"
✓2 == 2, 1run ↗
When I calculate "2 == 2"
Then the result is "1"
✓2 != 2, 0run ↗
When I calculate "2 != 2"
Then the result is "0"
✓Comparison chains are rejected, not misreadrun ↗
When I calculate "1 < 2 < 3"
Then the calculation fails mentioning "can't be chained"
Data types — declared records with named construction
✓ 47 examplesAs a user modeling real things
I want to declare a typed record once and build instances by field name
So that my collections stay consistent and serialize honestly
✓Declaring a type registers its constructorrun ↗
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 positionalrun ↗
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/falserun ↗
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 keyrun ↗
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 functionsrun ↗
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 deeplyrun ↗
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 Booleansrun ↗
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, exactlyrun ↗
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 recordrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
And I calculate "p = Person(name: "Ada", age: 36, active: true)"
When I calculate "Person(fromJson(toJson(p))) == p"
Then the result is "1"
When I calculate "map(Person, fromJson(toJson([p, p])))[1].age"
Then the result is "36"
fromJson mistakes explain themselvesoutline · 5
✓fromJson("null"), has no Anzan valuerun ↗
When I calculate "fromJson("null")"
Then the calculation fails mentioning "has no Anzan value"
✓fromJson("[1,"), unexpected endrun ↗
When I calculate "fromJson("[1,")"
Then the calculation fails mentioning "unexpected end"
✓fromJson("[1] junk"), trailing contentrun ↗
When I calculate "fromJson("[1] junk")"
Then the calculation fails mentioning "trailing content"
✓fromJson("{\"a\":1,\"a\":2}"), duplicate keyrun ↗
When I calculate "fromJson("{\"a\":1,\"a\":2}")"
Then the calculation fails mentioning "duplicate key"
✓fromJson(5), wants JSON textrun ↗
When I calculate "fromJson(5)"
Then the calculation fails mentioning "wants JSON text"
✓Json options are named constants, not magic flagsrun ↗
When I calculate "Json.Pretty"
Then the result is ""pretty""
When I calculate "toJson([1, 2], Json.Compact)"
Then the result is ""[1,2]""
When I calculate "toJson([1, 2], "compact")"
Then the result is ""[1,2]""
When I calculate "Json = 5"
Then the calculation fails mentioning "cannot assign"
When I calculate "man(Json)"
Then documentation is shown mentioning "Formatting options"
Construction mistakes explain themselvesoutline · 9
✓Person(name: "Ada", age: 36), missing 'active'run ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "Ada", age: 36)"
Then the calculation fails mentioning "missing 'active'"
✓Person(name: 7, age: 36, active: true), 'name' of Person is a Stringrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: 7, age: 36, active: true)"
Then the calculation fails mentioning "'name' of Person is a String"
✓Person(name: "A", age: "x", active: true), 'age' of Person is a Numberrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", age: "x", active: true)"
Then the calculation fails mentioning "'age' of Person is a Number"
✓Person(name: "A", age: 36, active: 7), use true or falserun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", age: 36, active: 7)"
Then the calculation fails mentioning "use true or false"
✓Person(name: "A", age: 36, active: true, pet: 1), no field 'pet'run ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", age: 36, active: true, pet: 1)"
Then the calculation fails mentioning "no field 'pet'"
✓Person(name: "A", name: "B", age: 1, active: 1), duplicate fieldrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "Person(name: "A", name: "B", age: 1, active: 1)"
Then the calculation fails mentioning "duplicate field"
✓toJson(sqrt), can't serialize a functionrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "toJson(sqrt)"
Then the calculation fails mentioning "can't serialize a function"
✓toJson(1, "wat"), unknown toJson optionrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "toJson(1, "wat")"
Then the calculation fails mentioning "unknown toJson option"
✓toJson(1, true), Json.Pretty or Json.Compactrun ↗
Given I calculate "data Person { name: String, age: Number, active: Boolean }"
When I calculate "toJson(1, true)"
Then the calculation fails mentioning "Json.Pretty or Json.Compact"
Declaration mistakes explain themselvesoutline · 5
✓data person { a: Number }, capital letterrun ↗
When I calculate "data person { a: Number }"
Then the calculation fails mentioning "capital letter"
✓data Bad { a: truthy }, declared data typerun ↗
When I calculate "data Bad { a: truthy }"
Then the calculation fails mentioning "declared data type"
✓data Empty {}, at least one fieldrun ↗
When I calculate "data Empty {}"
Then the calculation fails mentioning "at least one field"
✓data Dup { a: Number, A: Number }, duplicate fieldrun ↗
When I calculate "data Dup { a: Number, A: Number }"
Then the calculation fails mentioning "duplicate field"
✓data Abs { a: Number }, built-inrun ↗
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 worksrun ↗
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 commentrun ↗
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 containersrun ↗
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 functionrun ↗
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 cellsrun ↗
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 𝑫 typerun ↗
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 reopenrun ↗
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 typerun ↗
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 typerun ↗
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 typerun ↗
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 scalarrun ↗
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 overloadrun ↗
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 typerun ↗
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 staterun ↗
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 reopenrun ↗
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 typerun ↗
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 typerun ↗
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 reopenrun ↗
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"
Number formats are presentation, never value
✓ 15 examplesAs someone formatting a model for reading
I want currency, percent, and date displays
So that cells read naturally while the math stays exact
A formatted cell displays its value in costumeoutline · 13
✓1234567.5, number, 1,234,567.50run ↗
Given cell B:1 contains "1234567.5"
And cell B:1 is formatted as "number"
Then cell B:1 displays "1,234,567.50"
✓1234.5, dollars, $1,234.50run ↗
Given cell B:1 contains "1234.5"
And cell B:1 is formatted as "dollars"
Then cell B:1 displays "$1,234.50"
✓-1234.5, dollars, -$1,234.50run ↗
Given cell B:1 contains "-1234.5"
And cell B:1 is formatted as "dollars"
Then cell B:1 displays "-$1,234.50"
✓-2, euros, -€2.00run ↗
Given cell B:1 contains "-2"
And cell B:1 is formatted as "euros"
Then cell B:1 displays "-€2.00"
✓0.0825, percent, 8.25%run ↗
Given cell B:1 contains "0.0825"
And cell B:1 is formatted as "percent"
Then cell B:1 displays "8.25%"
✓1, percent, 100.00%run ↗
Given cell B:1 contains "1"
And cell B:1 is formatted as "percent"
Then cell B:1 displays "100.00%"
✓20610, a date, 2026-06-06run ↗
Given cell B:1 contains "20610"
And cell B:1 is formatted as "a date"
Then cell B:1 displays "2026-06-06"
✓0, a date, 1970-01-01run ↗
Given cell B:1 contains "0"
And cell B:1 is formatted as "a date"
Then cell B:1 displays "1970-01-01"
✓195, hex, 0xC3run ↗
Given cell B:1 contains "195"
And cell B:1 is formatted as "hex"
Then cell B:1 displays "0xC3"
✓-255, hex, -0xFFrun ↗
Given cell B:1 contains "-255"
And cell B:1 is formatted as "hex"
Then cell B:1 displays "-0xFF"
✓195, binary, 0b1100_0011run ↗
Given cell B:1 contains "195"
And cell B:1 is formatted as "binary"
Then cell B:1 displays "0b1100_0011"
✓5, binary, 0b101run ↗
Given cell B:1 contains "5"
And cell B:1 is formatted as "binary"
Then cell B:1 displays "0b101"
✓1.5, hex, 1.5run ↗
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 mathrun ↗
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 formatrun ↗
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 examplesAs a user with my own formulas
I want to define, compose, and document functions
So that the calculator speaks my domain
✓Define a function and use itrun ↗
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 orderrun ↗
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 worksrun ↗
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 valuesrun ↗
When I calculate "map(x -> x * 2, [1, 2, 3])"
Then the result is "[2, 4, 6]"
✓Structures carry datarun ↗
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 pirun ↗
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 mathrun ↗
When I calculate "6 * 7 # the answer"
Then the result is "42"
✓A trailing comment becomes the function's documentationrun ↗
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 manualrun ↗
When I calculate "man(pmt)"
Then documentation is shown mentioning "payment"
✓Only the taken branch of if() runsrun ↗
When I calculate "if(1, 2, 1/0)"
Then the result is "2"
✓Deep recursion is bounded by memory, not a counterrun ↗
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 depthrun ↗
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 hintrun ↗
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 crashrun ↗
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 protectedrun ↗
When I calculate "abs(x) = x"
Then the calculation fails mentioning "built-in"
✓Function calls are case-insensitiverun ↗
When I calculate "MIN(1, 2) + Sqrt(16)"
Then the result is "5"
The expanded function library
✓ 114 examplesAs a spreadsheet refugee and a terminal dweller
I want combinatorics, deeper statistics, amortization, business days, and bit math
So that one exact engine covers what I'd otherwise scatter across tools
Exact combinatorics — BigInt keeps every digitoutline · 7
✓choose(5, 2), 10run ↗
When I calculate "choose(5, 2)"
Then the result is "10"
✓choose(52, 5), 2598960run ↗
When I calculate "choose(52, 5)"
Then the result is "2598960"
✓choose(100, 50), 100891344545564193334812497256run ↗
When I calculate "choose(100, 50)"
Then the result is "100891344545564193334812497256"
✓choose(2, 5), 0run ↗
When I calculate "choose(2, 5)"
Then the result is "0"
✓perm(5, 2), 20run ↗
When I calculate "perm(5, 2)"
Then the result is "20"
✓perm(10, 3), 720run ↗
When I calculate "perm(10, 3)"
Then the result is "720"
✓perm(10, 10), 3628800run ↗
When I calculate "perm(10, 10)"
Then the result is "3628800"
Array plumbingoutline · 11
✓sort([3, 1, 2]), [1, 2, 3]run ↗
When I calculate "sort([3, 1, 2])"
Then the result is "[1, 2, 3]"
✓sort(["pear", "fig", "kiwi"]), ["fig", "kiwi", "pear"]run ↗
When I calculate "sort(["pear", "fig", "kiwi"])"
Then the result is "["fig", "kiwi", "pear"]"
✓unique([3, 1, 3, 2, 1]), [3, 1, 2]run ↗
When I calculate "unique([3, 1, 3, 2, 1])"
Then the result is "[3, 1, 2]"
✓reverse([1, 2, 3]), [3, 2, 1]run ↗
When I calculate "reverse([1, 2, 3])"
Then the result is "[3, 2, 1]"
✓reverse("abc"), "cba"run ↗
When I calculate "reverse("abc")"
Then the result is ""cba""
✓seq(1, 5), [1, 2, 3, 4, 5]run ↗
When I calculate "seq(1, 5)"
Then the result is "[1, 2, 3, 4, 5]"
✓seq(10, 0, -2), [10, 8, 6, 4, 2, 0]run ↗
When I calculate "seq(10, 0, -2)"
Then the result is "[10, 8, 6, 4, 2, 0]"
✓sum(map(x -> x^2, seq(1, 10))), 385run ↗
When I calculate "sum(map(x -> x^2, seq(1, 10)))"
Then the result is "385"
✓list(1, 2, 3), [1, 2, 3]run ↗
When I calculate "list(1, 2, 3)"
Then the result is "[1, 2, 3]"
✓sumproduct([2, 3], [10, 100]), 320run ↗
When I calculate "sumproduct([2, 3], [10, 100])"
Then the result is "320"
✓sumproduct(1, 2, 3, 4, 5, 6), 32run ↗
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 cellsrun ↗
Given the sheet contains:
When I calculate "sum(filter(x -> x > 10, list(A:1..A:4)))"
Then the result is "42"
When I calculate "map(x -> x * 2, list(A:1..A:2))"
Then the result is "[10, 24]"
Statistics depth (sample conventions, full precision)outline · 11
✓variance(2, 4, 4, 4, 5, 5, 7, 9), 4.5714285714285714285714285714285714285714285714286run ↗
When I calculate "variance(2, 4, 4, 4, 5, 5, 7, 9)"
Then the result is "4.5714285714285714285714285714285714285714285714286"
✓mode(1, 2, 2, 3, 3, 3), 3run ↗
When I calculate "mode(1, 2, 2, 3, 3, 3)"
Then the result is "3"
✓mode(4, 9, 4, 9), 4run ↗
When I calculate "mode(4, 9, 4, 9)"
Then the result is "4"
✓percentile(15, 20, 35, 40, 50, 0.4), 29run ↗
When I calculate "percentile(15, 20, 35, 40, 50, 0.4)"
Then the result is "29"
✓percentile(1, 2, 3, 4, 0.75), 3.25run ↗
When I calculate "percentile(1, 2, 3, 4, 0.75)"
Then the result is "3.25"
✓percentile(1, 2, 3, 1), 3run ↗
When I calculate "percentile(1, 2, 3, 1)"
Then the result is "3"
✓geomean(4, 9), 6run ↗
When I calculate "geomean(4, 9)"
Then the result is "6"
✓correl(1, 2, 3, 2, 4, 6), 1run ↗
When I calculate "correl(1, 2, 3, 2, 4, 6)"
Then the result is "1"
✓slope(3, 5, 7, 1, 2, 3), 2run ↗
When I calculate "slope(3, 5, 7, 1, 2, 3)"
Then the result is "2"
✓intercept(3, 5, 7, 1, 2, 3), 1run ↗
When I calculate "intercept(3, 5, 7, 1, 2, 3)"
Then the result is "1"
✓forecast(4, 3, 5, 7, 1, 2, 3), 9run ↗
When I calculate "forecast(4, 3, 5, 7, 1, 2, 3)"
Then the result is "9"
Amortization and depreciationoutline · 10
✓round(ipmt(0.05/12, 1, 360, 200000), 10), -833.3333333333run ↗
When I calculate "round(ipmt(0.05/12, 1, 360, 200000), 10)"
Then the result is "-833.3333333333"
✓round(ipmt(0.05/12, 360, 360, 200000), 10), -4.4549512283run ↗
When I calculate "round(ipmt(0.05/12, 360, 360, 200000), 10)"
Then the result is "-4.4549512283"
✓round(cumipmt(0.05/12, 360, 200000, 1, 12), 2), -9932.99run ↗
When I calculate "round(cumipmt(0.05/12, 360, 200000, 1, 12), 2)"
Then the result is "-9932.99"
✓round(cumprinc(0.05/12, 360, 200000, 1, 12), 2), -2950.73run ↗
When I calculate "round(cumprinc(0.05/12, 360, 200000, 1, 12), 2)"
Then the result is "-2950.73"
✓ipmt(0.05/12, 7, 360, 200000) + ppmt(0.05/12, 7, 360, 200000) == pmt(0.05/12, 360, 200000), 1run ↗
When I calculate "ipmt(0.05/12, 7, 360, 200000) + ppmt(0.05/12, 7, 360, 200000) == pmt(0.05/12, 360, 200000)"
Then the result is "1"
✓sln(30000, 7500, 10), 2250run ↗
When I calculate "sln(30000, 7500, 10)"
Then the result is "2250"
✓round(syd(30000, 7500, 10, 1), 2), 4090.91run ↗
When I calculate "round(syd(30000, 7500, 10, 1), 2)"
Then the result is "4090.91"
✓ddb(30000, 7500, 10, 1), 6000run ↗
When I calculate "ddb(30000, 7500, 10, 1)"
Then the result is "6000"
✓ddb(30000, 7500, 10, 10), 0run ↗
When I calculate "ddb(30000, 7500, 10, 10)"
Then the result is "0"
✓round(nominal(effectiveRate(0.06, 12), 12), 10), 0.06run ↗
When I calculate "round(nominal(effectiveRate(0.06, 12), 12), 10)"
Then the result is "0.06"
Business days and calendar positionsoutline · 9
✓quarter(date(2026, 6, 6)), 2run ↗
When I calculate "quarter(date(2026, 6, 6))"
Then the result is "2"
✓quarter(date(2026, 11, 1)), 4run ↗
When I calculate "quarter(date(2026, 11, 1))"
Then the result is "4"
✓weeknum(date(2026, 1, 1)), 1run ↗
When I calculate "weeknum(date(2026, 1, 1))"
Then the result is "1"
✓weeknum(date(2026, 1, 4)), 2run ↗
When I calculate "weeknum(date(2026, 1, 4))"
Then the result is "2"
✓workday(date(2026, 6, 5), 1) == date(2026, 6, 8), 1run ↗
When I calculate "workday(date(2026, 6, 5), 1) == date(2026, 6, 8)"
Then the result is "1"
✓workday(date(2026, 6, 8), -1) == date(2026, 6, 5), 1run ↗
When I calculate "workday(date(2026, 6, 8), -1) == date(2026, 6, 5)"
Then the result is "1"
✓networkdays(date(2026, 6, 1), date(2026, 6, 30)), 22run ↗
When I calculate "networkdays(date(2026, 6, 1), date(2026, 6, 30))"
Then the result is "22"
✓networkdays(date(2026, 6, 1), date(2026, 6, 30), date(2026, 6, 19)), 21run ↗
When I calculate "networkdays(date(2026, 6, 1), date(2026, 6, 30), date(2026, 6, 19))"
Then the result is "21"
✓networkdays(date(2026, 6, 5), date(2026, 6, 1)), -5run ↗
When I calculate "networkdays(date(2026, 6, 5), date(2026, 6, 1))"
Then the result is "-5"
Scientific completionsoutline · 9
✓deg(pi), 180run ↗
When I calculate "deg(pi)"
Then the result is "180"
✓deg(pi / 4), 45run ↗
When I calculate "deg(pi / 4)"
Then the result is "45"
✓sin(rad(90)), 1run ↗
When I calculate "sin(rad(90))"
Then the result is "1"
✓sinh(0), 0run ↗
When I calculate "sinh(0)"
Then the result is "0"
✓cosh(0), 1run ↗
When I calculate "cosh(0)"
Then the result is "1"
✓tanh(0), 0run ↗
When I calculate "tanh(0)"
Then the result is "0"
✓asinh(0), 0run ↗
When I calculate "asinh(0)"
Then the result is "0"
✓acosh(1), 0run ↗
When I calculate "acosh(1)"
Then the result is "0"
✓atanh(0), 0run ↗
When I calculate "atanh(0)"
Then the result is "0"
✓atan2 knows its quadrantrun ↗
When I calculate "atan2(1, 1) - pi/4"
Then the result is within "1e-15" of zero
When I calculate "atan2(-1, -1) + 3 * pi/4"
Then the result is within "1e-15" of zero
Programmer tools — exact at any widthoutline · 14
✓toBase(255, 16), "FF"run ↗
When I calculate "toBase(255, 16)"
Then the result is ""FF""
✓toBase(10, 2), "1010"run ↗
When I calculate "toBase(10, 2)"
Then the result is ""1010""
✓fromBase("ff", 16), 255run ↗
When I calculate "fromBase("ff", 16)"
Then the result is "255"
✓fromBase("1010", 2), 10run ↗
When I calculate "fromBase("1010", 2)"
Then the result is "10"
✓toBase(-255, 16), "-FF"run ↗
When I calculate "toBase(-255, 16)"
Then the result is ""-FF""
✓fromBase("-ff", 16), -255run ↗
When I calculate "fromBase("-ff", 16)"
Then the result is "-255"
✓fromBase(toBase(123456789012345678901234567890, 36), 36), 123456789012345678901234567890run ↗
When I calculate "fromBase(toBase(123456789012345678901234567890, 36), 36)"
Then the result is "123456789012345678901234567890"
✓bitAnd(12, 10), 8run ↗
When I calculate "bitAnd(12, 10)"
Then the result is "8"
✓bitOr(12, 10), 14run ↗
When I calculate "bitOr(12, 10)"
Then the result is "14"
✓bitXor(12, 10), 6run ↗
When I calculate "bitXor(12, 10)"
Then the result is "6"
✓bitShift(1, 100), 1.267650600228229401496703205376e+30run ↗
When I calculate "bitShift(1, 100)"
Then the result is "1.267650600228229401496703205376e+30"
✓bitShift(256, -4), 16run ↗
When I calculate "bitShift(256, -4)"
Then the result is "16"
✓bitAnd(0xFF, 0x0F), 15run ↗
When I calculate "bitAnd(0xFF, 0x0F)"
Then the result is "15"
✓toBase(0b1111, 16), "F"run ↗
When I calculate "toBase(0b1111, 16)"
Then the result is ""F""
✓solve() is goal seek in a formularun ↗
When I calculate "solve(x -> x^2, 2) - sqrt(2)"
Then the result is within "1e-12" of zero
When I calculate "solve(cos, 0, 1) - pi/2"
Then the result is within "1e-12" of zero
When I calculate "solve(x -> if(x < 1, -1, 1), 0) - 1"
Then the result is within "1e-12" of zero
When I calculate "solve(r -> fv(r, 120, -100), 20000, 0.01) * 12"
Then the result is within "1e-9" of "0.0958092381724"
Derived builtins equal their definitionsoutline · 16
✓avg(2, 4, 9) == sum(2, 4, 9) / count(2, 4, 9)run ↗
When I calculate "avg(2, 4, 9) == sum(2, 4, 9) / count(2, 4, 9)"
Then the result is "1"
✓stdev(2, 4, 4, 7) == sqrt(variance(2, 4, 4, 7))run ↗
When I calculate "stdev(2, 4, 4, 7) == sqrt(variance(2, 4, 4, 7))"
Then the result is "1"
✓variance(2, 4) == ((2 - 3)^2 + (4 - 3)^2) / 1run ↗
When I calculate "variance(2, 4) == ((2 - 3)^2 + (4 - 3)^2) / 1"
Then the result is "1"
✓cbrt(27) == root(27, 3)run ↗
When I calculate "cbrt(27) == root(27, 3)"
Then the result is "1"
✓geomean(2, 4, 8) == root(product(2, 4, 8), 3)run ↗
When I calculate "geomean(2, 4, 8) == root(product(2, 4, 8), 3)"
Then the result is "1"
✓percent(8.25) == 8.25 / 100run ↗
When I calculate "percent(8.25) == 8.25 / 100"
Then the result is "1"
✓deg(2) == 2 * 180 / pirun ↗
When I calculate "deg(2) == 2 * 180 / pi"
Then the result is "1"
✓choose(10, 4) == fact(10) / (fact(4) * fact(6))run ↗
When I calculate "choose(10, 4) == fact(10) / (fact(4) * fact(6))"
Then the result is "1"
✓perm(10, 4) == fact(10) / fact(6)run ↗
When I calculate "perm(10, 4) == fact(10) / fact(6)"
Then the result is "1"
✓lcm(12, 18) == 12 * 18 / gcd(12, 18)run ↗
When I calculate "lcm(12, 18) == 12 * 18 / gcd(12, 18)"
Then the result is "1"
✓median(4, 1, 3) == sort([4, 1, 3])[1]run ↗
When I calculate "median(4, 1, 3) == sort([4, 1, 3])[1]"
Then the result is "1"
✓sumproduct([2, 3], [10, 100]) == 2 * 10 + 3 * 100run ↗
When I calculate "sumproduct([2, 3], [10, 100]) == 2 * 10 + 3 * 100"
Then the result is "1"
✓npv(0.1, 300, 420) == 300 / 1.1 + 420 / 1.1^2run ↗
When I calculate "npv(0.1, 300, 420) == 300 / 1.1 + 420 / 1.1^2"
Then the result is "1"
✓forecast(4, 3, 5, 7, 1, 2, 3) == intercept(3, 5, 7, 1, 2, 3) + slope(3, 5, 7, 1, 2, 3) * 4run ↗
When I calculate "forecast(4, 3, 5, 7, 1, 2, 3) == intercept(3, 5, 7, 1, 2, 3) + slope(3, 5, 7, 1, 2, 3) * 4"
Then the result is "1"
✓quarter(date(2026, 11, 1)) == trunc((month(date(2026, 11, 1)) - 1) / 3) + 1run ↗
When I calculate "quarter(date(2026, 11, 1)) == trunc((month(date(2026, 11, 1)) - 1) / 3) + 1"
Then the result is "1"
✓bitShift(5, 3) == 5 * 2^3run ↗
When I calculate "bitShift(5, 3) == 5 * 2^3"
Then the result is "1"
The new functions explain their mistakesoutline · 24
✓choose(-1, 2), non-negativerun ↗
When I calculate "choose(-1, 2)"
Then the calculation fails mentioning "non-negative"
✓sort([1, "a"]), all numbers or all stringsrun ↗
When I calculate "sort([1, "a"])"
Then the calculation fails mentioning "all numbers or all strings"
✓seq(1, 10, 0), can't be 0run ↗
When I calculate "seq(1, 10, 0)"
Then the calculation fails mentioning "can't be 0"
✓mode(1, 2, 3), no value repeatsrun ↗
When I calculate "mode(1, 2, 3)"
Then the calculation fails mentioning "no value repeats"
✓percentile(1, 2, 3, 1.5), between 0 and 1run ↗
When I calculate "percentile(1, 2, 3, 1.5)"
Then the calculation fails mentioning "between 0 and 1"
✓geomean(4, -9), positiverun ↗
When I calculate "geomean(4, -9)"
Then the calculation fails mentioning "positive"
✓correl(1, 2, 3, 4, 5), equal-lengthrun ↗
When I calculate "correl(1, 2, 3, 4, 5)"
Then the calculation fails mentioning "equal-length"
✓ipmt(0.05, 0, 12, 1000), 1 ≤ per ≤ nperrun ↗
When I calculate "ipmt(0.05, 0, 12, 1000)"
Then the calculation fails mentioning "1 ≤ per ≤ nper"
✓syd(30000, 7500, 10, 11), 1 ≤ per ≤ liferun ↗
When I calculate "syd(30000, 7500, 10, 11)"
Then the calculation fails mentioning "1 ≤ per ≤ life"
✓toBase(1.5, 16), integerrun ↗
When I calculate "toBase(1.5, 16)"
Then the calculation fails mentioning "integer"
✓fromBase("xyz", 16), not a base-16run ↗
When I calculate "fromBase("xyz", 16)"
Then the calculation fails mentioning "not a base-16"
✓bitAnd(-1, 2), non-negativerun ↗
When I calculate "bitAnd(-1, 2)"
Then the calculation fails mentioning "non-negative"
✓solve(x -> x^2 + 1, 0), did not convergerun ↗
When I calculate "solve(x -> x^2 + 1, 0)"
Then the calculation fails mentioning "did not converge"
✓ipmt(0.05, 1, 0, 1000), 1 ≤ per ≤ nperrun ↗
When I calculate "ipmt(0.05, 1, 0, 1000)"
Then the calculation fails mentioning "1 ≤ per ≤ nper"
✓cumipmt(0.05/12, 12, 1000, 5, 2), 1 ≤ start ≤ endrun ↗
When I calculate "cumipmt(0.05/12, 12, 1000, 5, 2)"
Then the calculation fails mentioning "1 ≤ start ≤ end"
✓nominal(-2, 12), above -100%run ↗
When I calculate "nominal(-2, 12)"
Then the calculation fails mentioning "above -100%"
✓ddb(30000, 7500, 10, 11), 1 ≤ per ≤ liferun ↗
When I calculate "ddb(30000, 7500, 10, 11)"
Then the calculation fails mentioning "1 ≤ per ≤ life"
✓sln(1, 1, 0), can't be 0run ↗
When I calculate "sln(1, 1, 0)"
Then the calculation fails mentioning "can't be 0"
✓toBase(255, 50), 2–36run ↗
When I calculate "toBase(255, 50)"
Then the calculation fails mentioning "2–36"
✓fromBase("", 16), at least one digitrun ↗
When I calculate "fromBase("", 16)"
Then the calculation fails mentioning "at least one digit"
✓bitShift(1, 1.5), integerrun ↗
When I calculate "bitShift(1, 1.5)"
Then the calculation fails mentioning "integer"
✓seq(1, 1000000), 100,000run ↗
When I calculate "seq(1, 1000000)"
Then the calculation fails mentioning "100,000"
✓workday(date(2026, 1, 1), 200001), too manyrun ↗
When I calculate "workday(date(2026, 1, 1), 200001)"
Then the calculation fails mentioning "too many"
✓networkdays(date(1, 1, 1), date(9999, 1, 1)), too manyrun ↗
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
✓ 105 examplesAs a user of one calculator for everything
I want every domain — algebra, trig, stats, finance, dates — to answer correctly
So that I never need a second tool
Core arithmetic and algebraoutline · 28
✓1 + 2 * 3, 7run ↗
When I calculate "1 + 2 * 3"
Then the result is "7"
✓(1 + 2) * 3, 9run ↗
When I calculate "(1 + 2) * 3"
Then the result is "9"
✓10 / 4, 2.5run ↗
When I calculate "10 / 4"
Then the result is "2.5"
✓mod(7, 3), 1run ↗
When I calculate "mod(7, 3)"
Then the result is "1"
✓-2^2, -4run ↗
When I calculate "-2^2"
Then the result is "-4"
✓2^-1, 0.5run ↗
When I calculate "2^-1"
Then the result is "0.5"
✓abs(-5), 5run ↗
When I calculate "abs(-5)"
Then the result is "5"
✓min(3, 1, 2), 1run ↗
When I calculate "min(3, 1, 2)"
Then the result is "1"
✓max(3, 1, 2), 3run ↗
When I calculate "max(3, 1, 2)"
Then the result is "3"
✓floor(2.7), 2run ↗
When I calculate "floor(2.7)"
Then the result is "2"
✓floor(-1.5), -2run ↗
When I calculate "floor(-1.5)"
Then the result is "-2"
✓ceil(2.1), 3run ↗
When I calculate "ceil(2.1)"
Then the result is "3"
✓ceil(-1.5), -1run ↗
When I calculate "ceil(-1.5)"
Then the result is "-1"
✓trunc(-2.7), -2run ↗
When I calculate "trunc(-2.7)"
Then the result is "-2"
✓round(2.345, 2), 2.34run ↗
When I calculate "round(2.345, 2)"
Then the result is "2.34"
✓round(2.567, 2), 2.57run ↗
When I calculate "round(2.567, 2)"
Then the result is "2.57"
✓round(2.5), 2run ↗
When I calculate "round(2.5)"
Then the result is "2"
✓mod(7, 3), 1run ↗
When I calculate "mod(7, 3)"
Then the result is "1"
✓fact(5), 120run ↗
When I calculate "fact(5)"
Then the result is "120"
✓fact(20), 2432902008176640000run ↗
When I calculate "fact(20)"
Then the result is "2432902008176640000"
✓gcd(12, 18), 6run ↗
When I calculate "gcd(12, 18)"
Then the result is "6"
✓lcm(4, 6), 12run ↗
When I calculate "lcm(4, 6)"
Then the result is "12"
✓percent(8.25), 0.0825run ↗
When I calculate "percent(8.25)"
Then the result is "0.0825"
✓sqrt(144), 12run ↗
When I calculate "sqrt(144)"
Then the result is "12"
✓cbrt(27), 3run ↗
When I calculate "cbrt(27)"
Then the result is "3"
✓cbrt(-27), -3run ↗
When I calculate "cbrt(-27)"
Then the result is "-3"
✓root(32, 5), 2run ↗
When I calculate "root(32, 5)"
Then the result is "2"
✓pow(4, 0.5), 2run ↗
When I calculate "pow(4, 0.5)"
Then the result is "2"
Mathematical symbols are first-classoutline · 11
✓√16, 4run ↗
When I calculate "√16"
Then the result is "4"
✓√(2 + 2), 2run ↗
When I calculate "√(2 + 2)"
Then the result is "2"
✓√2^2, 2run ↗
When I calculate "√2^2"
Then the result is "2"
✓6 × 7 ÷ 2, 21run ↗
When I calculate "6 × 7 ÷ 2"
Then the result is "21"
✓6 ÷ 2 − 1, 2run ↗
When I calculate "6 ÷ 2 − 1"
Then the result is "2"
✓3 · 4, 12run ↗
When I calculate "3 · 4"
Then the result is "12"
✓π − pi, 0run ↗
When I calculate "π − pi"
Then the result is "0"
✓τ ÷ π, 2run ↗
When I calculate "τ ÷ π"
Then the result is "2"
✓pi, 3.14159265358979323846264338327950288419716939937510582097494run ↗
When I calculate "pi"
Then the result is "3.14159265358979323846264338327950288419716939937510582097494"
✓3 ≠ 2, 1run ↗
When I calculate "3 ≠ 2"
Then the result is "1"
✓2 ≤ 2, 1run ↗
When I calculate "2 ≤ 2"
Then the result is "1"
✓Constants are protectedrun ↗
When I calculate "π = 3"
Then the calculation fails mentioning "cannot assign to 'π'"
Trigonometry (radians)outline · 6
✓sin(pi / 2), 1run ↗
When I calculate "sin(pi / 2)"
Then the result is "1"
✓cos(pi), -1run ↗
When I calculate "cos(pi)"
Then the result is "-1"
✓tan(pi / 4), 0.9999999999999999run ↗
When I calculate "tan(pi / 4)"
Then the result is "0.9999999999999999"
✓asin(1) * 2, 3.1415926535897932run ↗
When I calculate "asin(1) * 2"
Then the result is "3.1415926535897932"
✓acos(0) * 2, 3.1415926535897932run ↗
When I calculate "acos(0) * 2"
Then the result is "3.1415926535897932"
✓atan(1) * 4, 3.1415926535897932run ↗
When I calculate "atan(1) * 4"
Then the result is "3.1415926535897932"
Exponentials and logarithmsoutline · 7
✓exp(0), 1run ↗
When I calculate "exp(0)"
Then the result is "1"
✓ln(e), 1run ↗
When I calculate "ln(e)"
Then the result is "1"
✓exp(ln(7)), 6.999999999999999run ↗
When I calculate "exp(ln(7))"
Then the result is "6.999999999999999"
✓log10(1000), 3run ↗
When I calculate "log10(1000)"
Then the result is "3"
✓log(2, 8), 3run ↗
When I calculate "log(2, 8)"
Then the result is "3"
✓sin(0), 0run ↗
When I calculate "sin(0)"
Then the result is "0"
✓cos(0), 1run ↗
When I calculate "cos(0)"
Then the result is "1"
Statistics over lists, arrays, and rangesoutline · 8
✓avg(2, 4, 9), 5run ↗
When I calculate "avg(2, 4, 9)"
Then the result is "5"
✓median(1, 9, 5), 5run ↗
When I calculate "median(1, 9, 5)"
Then the result is "5"
✓median(1, 2, 3, 4), 2.5run ↗
When I calculate "median(1, 2, 3, 4)"
Then the result is "2.5"
✓count(1, 2, 3), 3run ↗
When I calculate "count(1, 2, 3)"
Then the result is "3"
✓product(2, 3, 4), 24run ↗
When I calculate "product(2, 3, 4)"
Then the result is "24"
✓stdev(2, 4, 4, 4, 5, 5, 7, 9), 2.1380899352993950774764278470380281724320113187307run ↗
When I calculate "stdev(2, 4, 4, 4, 5, 5, 7, 9)"
Then the result is "2.1380899352993950774764278470380281724320113187307"
✓avg([2, 4, 9]), 5run ↗
When I calculate "avg([2, 4, 9])"
Then the result is "5"
✓stdev(1, 3) - sqrt(2), 0run ↗
When I calculate "stdev(1, 3) - sqrt(2)"
Then the result is "0"
✓Products mirror summationrun ↗
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 seriesrun ↗
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 sumrun ↗
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 exactlyrun ↗
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, exactlyrun ↗
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 placesrun ↗
When I calculate "pv(0.05/12, 360, pmt(0.05/12, 360, 200000)) - 200000"
Then the result is within "1e-30" of zero
Finance agrees with Excel (independent cross-validation)outline · 14
✓pmt(0.05/12, 360, 200000), -1073.64, 0.01run ↗
When I calculate "pmt(0.05/12, 360, 200000)"
Then the result is within "0.01" of "-1073.64"
✓pmt(0, 12, 1200), -100, 0.000001run ↗
When I calculate "pmt(0, 12, 1200)"
Then the result is within "0.000001" of "-100"
✓fv(0.06/12, 120, -100), 16387.93, 0.01run ↗
When I calculate "fv(0.06/12, 120, -100)"
Then the result is within "0.01" of "16387.93"
✓fv(0, 12, -100), 1200, 0.000001run ↗
When I calculate "fv(0, 12, -100)"
Then the result is within "0.000001" of "1200"
✓pv(0.04/12, 60, -500), 27149.53, 0.01run ↗
When I calculate "pv(0.04/12, 60, -500)"
Then the result is within "0.01" of "27149.53"
✓nper(0.05/12, -1073.64, 200000), 360, 0.01run ↗
When I calculate "nper(0.05/12, -1073.64, 200000)"
Then the result is within "0.01" of "360"
✓nper(0, -100, 1200), 12, 0.000001run ↗
When I calculate "nper(0, -100, 1200)"
Then the result is within "0.000001" of "12"
✓rate(360, -1073.64, 200000) * 12, 0.05, 0.0001run ↗
When I calculate "rate(360, -1073.64, 200000) * 12"
Then the result is within "0.0001" of "0.05"
✓npv(0.1, 3000, 4200, 6800), 11307.29, 0.01run ↗
When I calculate "npv(0.1, 3000, 4200, 6800)"
Then the result is within "0.01" of "11307.29"
✓irr(-70000, 12000, 15000, 18000, 21000, 26000), 0.0866, 0.0001run ↗
When I calculate "irr(-70000, 12000, 15000, 18000, 21000, 26000)"
Then the result is within "0.0001" of "0.0866"
✓effectiveRate(0.06, 12), 0.061678, 0.000001run ↗
When I calculate "effectiveRate(0.06, 12)"
Then the result is within "0.000001" of "0.061678"
✓markup(80, 25), 100, 0.000001run ↗
When I calculate "markup(80, 25)"
Then the result is within "0.000001" of "100"
✓percentOf(30, 120), 25, 0.000001run ↗
When I calculate "percentOf(30, 120)"
Then the result is within "0.000001" of "25"
✓percentChange(80, 100), 25, 0.000001run ↗
When I calculate "percentChange(80, 100)"
Then the result is within "0.000001" of "25"
✓A mortgage's lifetime cost, built up across linesrun ↗
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 streamrun ↗
When I calculate "irr(1000, 2000)"
Then the calculation fails mentioning "both positive and negative"
Finance (spreadsheet sign convention)outline · 6
✓fv(0.1, 2, -100), 210run ↗
When I calculate "fv(0.1, 2, -100)"
Then the result is "210"
✓npv(0, 10, 20, 30), 60run ↗
When I calculate "npv(0, 10, 20, 30)"
Then the result is "60"
✓npv(0.1, 110), 100run ↗
When I calculate "npv(0.1, 110)"
Then the result is "100"
✓nper(0, -10, 100), 10run ↗
When I calculate "nper(0, -10, 100)"
Then the result is "10"
✓irr(-100, 110), 0.1run ↗
When I calculate "irr(-100, 110)"
Then the result is "0.1"
✓effectiveRate(0.12, 12), 0.126825030131969720661201run ↗
When I calculate "effectiveRate(0.12, 12)"
Then the result is "0.126825030131969720661201"
Accounting shorthandsoutline · 4
✓margin(100, 80), 20run ↗
When I calculate "margin(100, 80)"
Then the result is "20"
✓markup(80, 25), 100run ↗
When I calculate "markup(80, 25)"
Then the result is "100"
✓percentOf(50, 200), 25run ↗
When I calculate "percentOf(50, 200)"
Then the result is "25"
✓percentChange(100, 150), 50run ↗
When I calculate "percentChange(100, 150)"
Then the result is "50"
Date arithmetic on exact day serialsoutline · 11
✓weekday(date(2026, 6, 6)), 6run ↗
When I calculate "weekday(date(2026, 6, 6))"
Then the result is "6"
✓weekday(date(2026, 6, 8)), 1run ↗
When I calculate "weekday(date(2026, 6, 8))"
Then the result is "1"
✓year(date(2026, 6, 6)), 2026run ↗
When I calculate "year(date(2026, 6, 6))"
Then the result is "2026"
✓month(date(2026, 6, 6)), 6run ↗
When I calculate "month(date(2026, 6, 6))"
Then the result is "6"
✓day(date(2026, 6, 6)), 6run ↗
When I calculate "day(date(2026, 6, 6))"
Then the result is "6"
✓days(date(2026, 3, 1), date(2026, 2, 1)), 28run ↗
When I calculate "days(date(2026, 3, 1), date(2026, 2, 1))"
Then the result is "28"
✓edate(date(2026, 1, 31), 1) == date(2026, 2, 28), 1run ↗
When I calculate "edate(date(2026, 1, 31), 1) == date(2026, 2, 28)"
Then the result is "1"
✓eomonth(date(2024, 2, 1), 0) == date(2024, 2, 29), 1run ↗
When I calculate "eomonth(date(2024, 2, 1), 0) == date(2024, 2, 29)"
Then the result is "1"
✓day(eomonth(date(1900, 2, 1), 0)), 28run ↗
When I calculate "day(eomonth(date(1900, 2, 1), 0))"
Then the result is "28"
✓day(eomonth(date(2000, 2, 1), 0)), 29run ↗
When I calculate "day(eomonth(date(2000, 2, 1), 0))"
Then the result is "29"
✓days(date(2001, 1, 1), date(2000, 1, 1)), 366run ↗
When I calculate "days(date(2001, 1, 1), date(2000, 1, 1))"
Then the result is "366"
✓Logic composes with everythingrun ↗
When I calculate "and(1 < 2, or(0, not(0)))"
Then the result is "1"
Inspecting the workbook
✓ 13 examplesAs someone modelling in the grid
I want to read the workbook's own structure from a formula
So that a calculation can adapt to its sheets and cells
✓Workbook reports its sheetsrun ↗
Given a sheet named "Budget"
When I calculate "Workbook.count"
Then the result is "2"
✓Worksheet names are readablerun ↗
Given a sheet named "Budget"
When I calculate "Workbook.worksheets[1].name == "Budget""
Then the result is "1"
✓A worksheet is reachable by namerun ↗
Given a sheet named "Budget"
When I calculate "Workbook.worksheets["Budget"].name == "Budget""
Then the result is "1"
✓A negative index counts from the endrun ↗
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 graphrun ↗
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 positionrun ↗
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 sheetrun ↗
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 namerun ↗
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 sheetrun ↗
Given a sheet named "Budget"
When I calculate "len(sheetNames())"
Then the result is "2"
✓rowCount() reports the grid sizerun ↗
When I calculate "rowCount()"
Then the result is "1000"
✓A formula reading a cell through reflection recomputes liverun ↗
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 accessorrun ↗
When I calculate "cell(x) = x * 10"
And I calculate "cell(5)"
Then the result is "50"
✓An unknown sheet is reported clearlyrun ↗
When I calculate "cell("Nope", "A", 1).value"
Then the calculation fails mentioning "unknown sheet"
Working in the grid
✓ 21 examplesAs a spreadsheet user
I want cells, names, definitions, and controls to cooperate
So that my model reads like what I mean
✓Cells reference each otherrun ↗
Given cell B:1 contains "1200"
And cell B:2 contains "=B:1 * 2"
Then cell B:2 shows "2400"
✓Labels never become errorsrun ↗
Given cell A:1 contains "Q1 revenue"
Then cell A:1 shows "Q1 revenue"
✓A comment cell is a note that holds no valuerun ↗
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 referencedrun ↗
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 evaluationrun ↗
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 columnrun ↗
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 proserun ↗
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 cellsrun ↗
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 callablerun ↗
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 rangerun ↗
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 sheetrun ↗
Given cell B:1 contains "1200"
When I calculate "B:1 * 2"
Then the result is "2400"
✓Explicit markers override auto-detectionrun ↗
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 guessrun ↗
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 zerorun ↗
Given cell A:1 contains "=B:9 + 5"
Then cell A:1 shows "5"
✓Circular references are caught, not infiniterun ↗
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 errorrun ↗
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 valuesrun ↗
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 onesrun ↗
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 namerun ↗
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 silentlyrun ↗
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 formatrun ↗
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
✓ 27 examplesAs a user with more than numbers
I want text and structured data to be first-class
So that records and lists live next to my math
Text and structure functionsoutline · 18
✓len([1, 2, 3]), 3run ↗
When I calculate "len([1, 2, 3])"
Then the result is "3"
✓len("hello"), 5run ↗
When I calculate "len("hello")"
Then the result is "5"
✓len({a: 1, b: 2}), 2run ↗
When I calculate "len({a: 1, b: 2})"
Then the result is "2"
✓first([5, 6, 7]), 5run ↗
When I calculate "first([5, 6, 7])"
Then the result is "5"
✓last([5, 6, 7]), 7run ↗
When I calculate "last([5, 6, 7])"
Then the result is "7"
✓keys({name: "Ada", age: 36}), ["name", "age"]run ↗
When I calculate "keys({name: "Ada", age: 36})"
Then the result is "["name", "age"]"
✓values({a: 1, b: 2}), [1, 2]run ↗
When I calculate "values({a: 1, b: 2})"
Then the result is "[1, 2]"
✓sum(values({a: 1, b: 2})), 3run ↗
When I calculate "sum(values({a: 1, b: 2}))"
Then the result is "3"
✓concat("Q", 1, "-", 2026), "Q1-2026"run ↗
When I calculate "concat("Q", 1, "-", 2026)"
Then the result is ""Q1-2026""
✓concat([1, 2], [3]), [1, 2, 3]run ↗
When I calculate "concat([1, 2], [3])"
Then the result is "[1, 2, 3]"
✓"Q" + 1, "Q1"run ↗
When I calculate ""Q" + 1"
Then the result is ""Q1""
✓"a" + "b" + "c", "abc"run ↗
When I calculate ""a" + "b" + "c""
Then the result is ""abc""
✓"abc"[0], "a"run ↗
When I calculate ""abc"[0]"
Then the result is ""a""
✓[[1, 2], [3, 4]][1][0], 3run ↗
When I calculate "[[1, 2], [3, 4]][1][0]"
Then the result is "3"
✓{a: [1, 2]}.a[1], 2run ↗
When I calculate "{a: [1, 2]}.a[1]"
Then the result is "2"
✓[1, 2] == [1, 2], 1run ↗
When I calculate "[1, 2] == [1, 2]"
Then the result is "1"
✓{a: 1, b: 2} == {b: 2, a: 1}, 1run ↗
When I calculate "{a: 1, b: 2} == {b: 2, a: 1}"
Then the result is "1"
✓"x" != 5, 1run ↗
When I calculate ""x" != 5"
Then the result is "1"
Structure mistakes explain themselvesoutline · 6
✓[1, 2][5], out of rangerun ↗
When I calculate "[1, 2][5]"
Then the calculation fails mentioning "out of range"
✓{a: 1}.b, no key 'b'run ↗
When I calculate "{a: 1}.b"
Then the calculation fails mentioning "no key 'b'"
✓first([]), empty arrayrun ↗
When I calculate "first([])"
Then the calculation fails mentioning "empty array"
✓sum(["a"]), works on numbersrun ↗
When I calculate "sum(["a"])"
Then the calculation fails mentioning "works on numbers"
✓"a" * 2, expected a numberrun ↗
When I calculate ""a" * 2"
Then the calculation fails mentioning "expected a number"
✓{a: 1, a: 2}, duplicate keyrun ↗
When I calculate "{a: 1, a: 2}"
Then the calculation fails mentioning "duplicate key"
✓Filter and reduce shape data like a user thinksrun ↗
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 parametersrun ↗
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 redefinitionrun ↗
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"