Coercion

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:

  1. Operators — arithmetic, comparison, concatenation, logical.
  2. Function arguments — a function that expects a number coerces what it can and errors on what it can't.
  3. Type-tagged assignmentsA1 is currency = "100" coerces the string to a number before the tag check.
  4. Rule-action read-modify-writeadd/sub/mul/div coerce to number; concat/prepend coerce 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 TRUE1, FALSE0
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 text

For 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  <  boolean

So 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 happened

Only 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-tagged

The check is part of the coercion axisloose (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")  # 18

The 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 errors

See 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 masks

10.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