Coercion
Coercion
Grid is loosely typed at the value level, like a spreadsheet: a formula may receive a number where it expected text, a blank where it expected a number, or a boolean in an arithmetic expression. Coercion is the set of rules that decide what happens.
The rules below describe Grid's value conversions.
For the kinds and tags these rules operate on, see
reference.md. For how errors flow
(they do not coerce — they propagate), see errors.md.
1. Where Coercion Happens
There are four places a value may be coerced:
- Operators — arithmetic, comparison, concatenation, logical.
- Function arguments — a function that expects a number coerces what it can and errors on what it can't.
- Type-tagged assignments —
A1 is currency = "100"coerces the string to a number before the tag check. - Rule-action read-modify-write —
add/sub/mul/divcoerce to number;concat/prependcoerce to text.
Coercion never mutates a stored value: it produces a converted value for the operation at hand. The binding keeps whatever kind its formula produced.
2. Coercion To Number
Used by arithmetic (+ - * / // ^ %% MOD), numeric comparison, and any
function that wants a number.
| From | Result |
|---|---|
| number | the number |
| boolean | TRUE → 1, FALSE → 0 |
| blank | 0 |
empty string "" |
0 |
| numeric string | parsed (whitespace trimmed): "5" → 5, " 3.14 " → 3.14 |
| non-numeric string | #VALUE! (e.g. "abc") |
| complex with zero imaginary part | the real part |
| anything else (array, object, date) | not a scalar number — function-specific |
"5" + 3 # 8 (numeric string coerces)
TRUE + 1 # 2 (TRUE = 1)
5 + BLANK # 5 (blank = 0)
"abc" + 1 # #VALUE! (non-numeric string)Blank as zero is what makes self-referential accumulators settle:
E1 = E1 + 1 resolves to 1 on first fire because E1 is blank
(→ 0) before it exists.
3. Coercion To Boolean
Used by IF, AND/OR/XOR/NOT, conditions in WHEN, and the
THEN … ELSE guard.
| From | Truthiness |
|---|---|
| boolean | itself |
| number | TRUE iff non-zero |
| string | TRUE iff non-empty |
| blank | FALSE |
| error | FALSE |
| complex | TRUE iff real or imaginary part is non-zero |
| array / date / object | TRUE |
IF(0, "y", "n") # "n" (0 is falsy)
IF("", "y", "n") # "n" (empty string is falsy)
IF("text", "y", "n") # "y" (non-empty string is truthy)
IF(BLANK, "y", "n") # "n"4. Coercion To Text
Used by concatenation (&), string functions, and interpolation.
| From | Text |
|---|---|
| string | itself |
| number | its decimal form (42 → "42", 3.5 → "3.5") |
| boolean | its text form |
| blank | "" (empty string) |
| date | its ISO form |
| error | the error literal (e.g. "#DIV/0!") |
| complex | "re+imi" |
| array / object | not a scalar — use the relevant function |
"foo" & "bar" # "foobar"
"count: " & 42 # "count: 42" (number coerces to text)
`net={A1}` # interpolation coerces A1 to textFor controlled formatting (currency, thousands separators, fixed
decimals), use TEXT(value, "pattern") or an interpolation format spec
(`{A1:"$#,##0.00"}`) rather than relying on default text coercion.
5. Comparison Across Kinds
Numeric comparison coerces both sides to number where possible. When the two sides are different kinds and cannot be compared numerically, Grid uses Excel's cross-kind ordering:
number < text < booleanSo any text sorts greater than any number, and any boolean greater than any text:
5 = 5 # TRUE
"5" = 5 # comparison coerces — equal by value
"a" > 1 # TRUE (text outranks number)Equality (=, <>/!=) compares by value after coercion; ordering
(<, <=, >, >=) uses the rules above. For case-insensitive text
comparison use EXACT, ILIKE, or fold case with LOWER/UPPER
first.
6. Operator Coercion Summary
| Operator family | Coerces operands to | On bad input |
|---|---|---|
Arithmetic + - * / // ^ |
number | #VALUE! for non-numeric text |
Modulo %% MOD |
number | #VALUE! / #DIV/0! |
Numeric comparison < <= > >= |
number (else cross-kind order) | cross-kind ordering |
Equality = <> != |
compare by value after coercion | — |
Concatenation & |
text | — (everything has a text form) |
Logical AND OR XOR NOT && || |
boolean (truthiness) | — |
Bitwise BITAND … |
integer (truncates) | #VALUE! |
6.1 Strict Mode Rejects Silent Coercion
The table above describes loose, the default. A model headed with
strict (see reference.md)
turns a lossy coercion in an arithmetic or comparison operand into a
TYPE_ERROR instead of converting silently:
strict
A2 = A1 + 1 # #TYPE! when A1 is blank or "5"
A2 = 40 + 2 # 42 — no coercion happenedOnly the silent conversion is rejected. A genuine number flows through
untouched, so strict never changes the result of arithmetic that had no
coercion to begin with. strict except coercion keeps this axis loose
while still tightening dimensions.
7. Type-Tag Coercion On Assignment
A type tag declares a semantic meaning over a kind. Under
strict, on assignment the
produced value is checked for kind compatibility with the tag's
representation root, after the same value coercions above:
strict
A1 is currency = 100 # OK — number is currency-compatible
A1 is currency = "100" # OK — string coerces to a number first
A1 is currency = "hello" # #TYPE! — not coercible to a number
A1 is percentage = 21pct # OK — 0.21, percentage-taggedThe check is part of the coercion axis — loose (the default) applies
the tag as an overlay without checking, and strict except coercion also
skips it. Either way, tags do not change arithmetic: a currency-tagged
1000 is still the number 1000 in A1 * 1.1. See
reference.md and
assignments.md.
7.1 Unit Conversion
INTO / TYPE_TAG overlays a semantic tag and participates in dimensional
checking, but it does not rescale a number. Use CONVERT(value, from, to) for numeric unit conversion:
CONVERT(3, "m", "ft") # 9.842519685...
CONVERT(10, "m/s", "km/h") # 36
CONVERT(0, "C", "K") # 273.15
CONVERT(10, "delta_C", "delta_F") # 18The conversion catalog understands ISO currency unit tags, SI/derived units,
rational exponents, absolute temperature units (C, F, K), and explicit
temperature delta units (delta_C, delta_F, delta_K). Absolute temperatures
convert with offsets; deltas convert by scale only. Converting between absolute
and delta temperature units returns #VALUE!.
Model-scoped custom units declared with unit <name> = ... participate in the
same catalog for compiled models, as long as they are scale-only aliases over
linear units.
Currency conversion never uses ambient FX. CONVERT(10, "USD", "EUR") returns
#VALUE!; currency conversion must be explicit with FX_RATE or with a
model-owned rate value declared by fx_rate <target> = BASE/QUOTE and populated
from model data, a table, or an explicit connector input.
8. Function Argument Coercion
Most built-ins coerce each argument to the kind they need, using the rules in §2–§4, and aggregate functions are lenient about non-numeric cells in a range:
SUM(1, "2", TRUE, BLANK) # 4 (numeric strings, TRUE=1, blank=0)
SUM([1, "x", 3]) # 4 (range aggregation skips non-numeric)
ROUND("3.14159", 2) # 3.14 (numeric string coerces)A scalar function argument that cannot be coerced returns #VALUE!,
while range aggregations (SUM, AVERAGE, COUNT, …) generally
skip cells that aren't numeric rather than erroring. Use AGGREGATE
with an error-ignoring mode when a range may contain error values:
AGGREGATE(9, 6, B1:B10) # SUM, ignoring errorsSee functions.md for each function's contract.
9. Errors Do Not Coerce
Error values are never coerced into a "normal" value by an operator or function — they propagate. The first error in a positional argument list wins:
A1 = #N/A
A2 = A1 + 5 # #N/A
A3 = SUM(A1, 1, 2) # #N/A
A4 = IFERROR(A1, 0) # 0 (explicit error-handling function)Only dedicated error-handling forms (IFERROR, IFNA, TRY,
DEFAULT/??, ?=, ISERROR, …) consume an error. Structural collection
predicates are a narrow inspection exception: IN/HAS, SUBSET/SUPERSET,
OVERLAPS, and ordered CONTAINS compare an error member by code without
coercing it; diagnostic message text is not identity. Ordinary arithmetic and
functions still pass errors through. Full rules: errors.md.
10. Gotchas
10.1 Blank is not empty string
A1 = "" # empty string
A2 = BLANK # blank
ISBLANK(A1) # FALSE
ISBLANK(A2) # TRUE
"" + 1 # 1 (empty string → 0 in arithmetic)Both coerce to 0 in arithmetic and to falsy in boolean context, but
ISBLANK distinguishes them.
10.2 Numeric strings coerce, words don't
"5" + 3 # 8
"5 apples" + 3 # #VALUE! (not a clean number)10.3 Boolean arithmetic is intentional
SUMPRODUCT((region = "EU") * amount) # TRUE/FALSE → 1/0 enables masks10.4 Don't rely on default number-to-text formatting
"Total: " & 1234.5 # "Total: 1234.5"
"Total: " & TEXT(1234.5, "$#,##0.00") # "Total: $1,234.50"11. See Also
reference.md— kinds and type tags.assignments.md— tag checks.errors.md— error propagation and#TYPE!.functions.md— per-function argument contracts.