Foreword
General
This document is the fourth document of the Plurimath specifications series. It specifies evaluation: the computation of a numeric value for a formula tree against variable bindings, under a strict error contract.
The evaluator described here is implemented in the plurimath gem of the 0.11 series (Plurimath::Math::Evaluation). It evaluates arithmetic over the tree exactly as written: it never rewrites the tree, never rounds behind the caller’s back, and never returns a value that is not a real, finite number — the alternative is one of the typed errors. Uncertainty propagation is not part of this edition: the model of PM 1002 carries uncertainty nodes, this document’s evaluator does not compute with them, and the propagation rules will arrive in this document when they arrive in the implementation.
Documents in the series
| Number | Title | Subject |
|---|---|---|
| 1001 | Math model | The formula tree, the node classes, the registry, the rendering surfaces |
| 1002 | Uncertainty model | The uncertainty node, its categories, its encodings |
| 1003 | Format interfaces | The readers and writers per format |
| 1004 | Evaluation | This document: numeric evaluation of trees |
Maintainers
This document is maintained by the Plurimath project at https://github.com/plurimath/plurimath.
Introduction
A formula tree is content until something computes with it. Evaluation is that step: given a tree and a set of bindings — names and their numeric values — the evaluator walks the tree, applies the operations its nodes denote, and yields one number, or refuses with a typed error naming exactly what stood in the way.
The design centre is the strict error contract. Arithmetic in general notation runs into division by zero, negative square roots, logarithms of non-positive numbers, overflowing magnitudes and unbound names. Many evaluators answer such cases with NaN, Infinity or a silent fallback; this one answers with a refusal. A result is always a real, finite number, and everything else is one of eight typed errors. The effect on callers is that a successful evaluation needs no hedging: the number returned is usable, and the errors can be handled each by name.
The evaluator is deliberately small. It evaluates arithmetic: addition, subtraction, multiplication, division, modulo, powers, negation, the function classes the model defines, and bounded iteration for the nary sum and product. It does not simplify, does not convert units, does not propagate uncertainty, and does not solve. Every one of those is a different contract with its own semantics; this document specifies the one the implementation carries.
EXAMPLE
The tree of x^2 + 1 with the binding x → 3. Expected outcome. the evaluation yields 10: the power evaluates to 9, the addition to 10, and the result is a real, finite number.
0.1 The design forces
One answer. Real finite number, or typed error — the evaluator never leaves the caller between the two.
As written. The tree is evaluated as the author wrote it: precedence, grouping and order respected, nothing rewritten.
Small surface. Arithmetic, functions, iteration, bindings — and nothing else. Every non-goal is stated, not implied.
Replayable. Determinism is the testability: the corpus freezes trees, bindings and outcomes, and drift is visible.
1 Scope
1.1 General
This document specifies evaluation of Plurimath formula trees:
the evaluator: its inputs, its contract, and the shape of its results;
the expression parser: the precedence that turns a tree’s linear reading into operations;
the arithmetic: every operation, its domain, and its exactness;
functions and their arguments;
bindings: keys, values, and the errors they raise;
bounded iteration for the nary sum and product;
the error contract: the eight typed errors and what each one means;
conformance of an implementation.
This document does not specify:
uncertainty propagation, combination or coverage computation — not implemented in this edition (see clause 12);
unit conversion or quantity arithmetic — the UnitsML series governs units, and evaluation works on numbers;
simplification, factorisation or any rewriting — the tree is evaluated as written;
the numeric contract of exact decimals in the data — CalConnect 36813 governs that, and this evaluator binds ordinary real numbers.
1.2 Relationship to the series
| Document | Relationship to this one |
|---|---|
| 1001 Math model | supplies the tree and the node classes this evaluator walks |
| 1002 Uncertainty model | carries uncertainty nodes; this evaluator does not compute with them in this edition |
| 1003 Format interfaces | delivers the trees; evaluation is a consumer, not a boundary of that document |
1.3 Audience
Implementers of Plurimath, applications embedding evaluation, and authors of the propagation rules to come.
2 Normative references
The following documents are referred to in the text in such a way that some or all of their content constitutes requirements of this document. For dated references, only the edition cited applies. For undated references, the latest edition of the referenced document (including any amendments) applies.
PM 1001 (math model), of this series — the tree, the node classes and their operand orders.
PM 1002 (uncertainty model), of this series — the nodes this edition does not compute with.
CalConnect 36813 (numeric contract), of the UnitsML series — the exact-decimal discipline of the data, distinct from this evaluator’s binding values.
3 Terms and definitions
For the purposes of this document, the following terms and definitions apply.
For the purposes of this document, the following terms and definitions apply.
3.1 evaluator
the component that computes a numeric value for a tree against bindings
3.2 binding
a name and the real numeric value bound to it for one evaluation
3.3 bindings
the set of bindings an evaluation uses; keys are names, values are real numbers
3.4 name
the identifier a tree’s symbols denote for evaluation purposes
3.5 evaluation
one walk of the tree under one binding set, yielding a number or raising a typed error
3.6 typed error
one of the eight error classes of clause 11; the only failures evaluation produces
3.7 real, finite
the only successful results: a real number with finite value — never NaN, never an infinity
3.8 strict error contract
the rule that evaluation either returns such a result or raises; there is no third answer
3.9 iteration
the bounded evaluation of a nary sum or product over an integer index range
3.10 index
the iteration variable, temporarily bound while the body folds
3.11 shadowing
an index temporarily replacing an outer binding of the same name for the body’s evaluation
3.12 fold
the left-to-right accumulation of the body’s values under the iteration’s operation
3.13 backend
the arithmetic engine the evaluator’s folds delegate to; its operations are clause 7’s
3.14 step limit
the maximum range width an iteration accepts; behaviour pinned by the corpus
3.15 normalisation
the deterministic token preparation that makes implicit products explicit before parsing
3.16 fold order
the left-to-right (or right, for powers) application order within one precedence level
4 Conventions
4.1 General
Evaluations are written as: tree, bindings, result. Errors are written as their class names of clause 11 with the identifying data the message carries.
4.2 Examples
Examples carry an Expected outcome paragraph stating the number yielded or the error raised. Numbers in examples are exact where they are exact: a rational binding such as one third behaves as its exact value in every example that uses it.
4.3 Names
Names in examples (x, n, i) are the plain identifiers the trees carry. The mapping from a tree’s symbols to names is pinned by the corpus; this document treats names as the keys of the binding set.
5 The evaluator
5.1 The model
lml "1.0"
models Evaluation {
class Evaluator {
attribute tree, Formula[1]
attribute bindings, Binding { cardinality 0..n }
}
class Binding {
attribute name, Name[1]
attribute value, RealNumber[1]
}
class Result {
attribute value, RealNumber[1]
}
enum EvaluationError {
value "division-by-zero"
value "invalid-binding"
value "invalid-binding-key"
value "math-domain"
value "missing-variable"
value "non-finite-result"
value "unsupported-expression"
}
}
An evaluation is one Evaluator over one tree with zero or more bindings. Its success is one Result; its failure is one EvaluationError, raised as the corresponding class of clause 11.
5.2 The contract
The strict error contract, stated once and honoured everywhere below: an evaluation returns a real, finite number, or raises one of the typed errors. There is no NaN result, no infinite result, no silently skipped subtree, no partial evaluation.
EXAMPLE
An evaluation whose arithmetic would produce a division by zero inside a larger expression. Expected outcome. the whole evaluation raises DivisionByZeroError. No subtree value, no partial result and no default leaks; the caller sees the refusal and nothing else.
5.3 Determinism
The same tree and the same bindings produce the same result or the same error, in the same order of internal operations. Evaluation has no randomness, no parallelism and no environment sensitivity; the corpus replays it identically.
5.4 The tree is not modified
Evaluation reads the tree. Nothing in the tree changes — no annotation of computed values, no caching inside nodes, no rewrite. A tree evaluates twice identically, and an evaluated tree writes exactly as an unevaluated one.
5.5 Foreign exceptions become typed errors
The underlying arithmetic platform’s own signals — domain errors from the math library, float-domain failures, platform division failures — are caught at the evaluation boundary and re-raised as the corresponding typed errors: MathDomainError, NonFiniteResultError, DivisionByZeroError. A caller never sees a foreign exception from evaluation; the message text of the platform error, where one exists, travels inside the typed error.
5.6 The walk, in order
An evaluation proceeds:
bindings validate at entry (clause 9) — nothing has run yet;
the tree walks in writing order through the expression parser’s chain (clause 6);
each node maps to an operation, a function call, an iteration, a name, or a leaf number;
names resolve against the bindings at first use;
folds apply clause 7’s arithmetic and clause 8’s functions, each result checked real-and-finite;
the final value returns; any refusal along the way aborts with its typed error.
The order is observable through the errors: an expression with both a missing name and a division by zero reports whichever the walk reaches first. The corpus includes order-sensitive vectors so the ordering is tested, not implied.
5.7 Zero bindings
An evaluation with no bindings evaluates the closed part of the tree: a tree without names evaluates completely (2 + 3 * 4 yields 14 with no bindings); a tree with any name refuses at that name. Both are ordinary evaluations under the one contract.
5.8 The evaluator is reentrant
An Evaluator instance holds its tree and bindings and nothing mutable; evaluating twice, or evaluating from two callers, shares no state. Iteration’s temporary bindings are scoped to their body and restored on every exit path, including the error paths — an aborted iteration leaves the binding set as it found it.
6 The expression parser
6.1 General
A tree denotes operations in writing order, not in a flat list: 2 + 3 * 4 multiplies before it adds. The expression parser turns the evaluator’s node walk into that precedence-correct sequence of folds. It is a precedence-climbing parser over the tree’s linear reading, with one level per binding strength.
6.2 The precedence chain
The parser descends one chain, strictest last:
| Level | Constructs | Folds to |
|---|---|---|
| relation | =, ≠, comparisons | the relation evaluation of clause 7 |
| additive | +, − | addition and subtraction |
| multiplicative | implicit products, /, mod | multiplication, division, modulo |
| unary | negation, positive sign | negation |
| power | x^y, right-associative | powers |
| operand | numbers, names, groups, functions | leaf evaluation or recursion |
| group | parenthesised content | recursion with saved state |
Each level parses the next as its operand and folds left-associatively within itself — powers excepted, which fold right-associatively: 232 is 2(32), the mathematical convention.
6.3 What the parser accepts
The parser accepts the arithmetic core the evaluator evaluates: numbers, bound names, the operations above, groups, and the function and iteration calls of clauses 8 and 10. A node outside this core — a Text node, a matrix, an uncertainty node — makes the whole evaluation raise UnsupportedExpressionError: the evaluator does not evaluate what it does not define, and never guesses.
EXAMPLE 1
The tree of 2 + 3 * 4. Expected outcome. the parser folds the multiplicative level first (yielding 12) and the additive level second (yielding 14), regardless of the walk order the tree’s linear reading might suggest.
EXAMPLE 2
The tree of 232. Expected outcome. 512 — the right-associative reading — not 64.
6.4 Groups
A group saves the parser’s pending state, evaluates its content to one value, and presents that value as an operand. Groups are the author’s precedence: 2 * (3 + 4) yields 14, not 10.
6.5 Relations
The relation level evaluates comparisons to truth values in the platform’s terms. A tree whose top level is a relation yields that relation’s result; relations compose left-associatively where the tree carries chains. The corpus pins the truth-value results alongside the numeric ones — both are results of the one contract.
6.6 Token normalisation
Before parsing, the tree’s linear tokens normalise: implicit products are marked (2x becomes 2 * x, a(x + 1) becomes multiplication, x sin y stays two operands), explicit multiplication symbols map to the same fold, and spacing is irrelevant. Normalisation is deterministic and pinned by the corpus; it changes no node, only the fold sequence.
6.7 Function calls in the chain
A function call parses as a prefix at the operand level: the function name, then its argument or arguments as the grammar of the construct gives them — parenthesised, braced, or by adjacency for the notation’s single-argument forms. The argument expressions parse at the full chain, so log_2 x^3 binds the power inside the argument where the notation’s grouping says so; the corpus carries the ambiguous-looking cases explicitly.
7 Arithmetic
7.1 The operations
The evaluator’s operations, their names, and their domains:
| Operation | Notation in the tree | Domain |
|---|---|---|
| addition | a + b | reals |
| subtraction | a − b | reals |
| multiplication | a * b, a b (implicit) | reals |
| division | a / b | reals, b ≠ 0 |
| modulo | a mod b | reals per the platform’s modulo |
| power | a^b | per clause 7’s power rules |
| negation | −a | reals |
| positive sign | +a | reals |
Every operation consumes real, finite values and produces one; the contract of clause 5 applies at each fold.
7.2 Division
Division by zero raises DivisionByZeroError — including division by an expression that evaluated to zero, however far away the zero came from. There is no limiting behaviour, no infinity: the contract has no place to put one.
7.3 Modulo
Modulo follows the platform’s modulo semantics for real operands; a zero divisor raises DivisionByZeroError. The sign behaviour is the platform’s and the corpus pins it.
7.4 Powers
The power operation evaluates a^b for the domains the real numbers define: a positive base with any real exponent, a zero base with a positive exponent, a negative base only where the exponent is an integer or a rational with an odd denominator in lowest terms. Outside those domains the platform’s domain error becomes MathDomainError. An exponent that would produce a non-finite magnitude raises NonFiniteResultError at the boundary.
EXAMPLE 1
2^10, 9^(1/2), (−8)^(1/3). Expected outcome. 1024; 3; −2 — the real cube root, in the odd-denominator domain.
EXAMPLE 2
(−2)^(1/2) and 0^(−1). Expected outcome. MathDomainError for the first; DivisionByZeroError for the second — the platform’s reading of a zero base with a negative exponent.
7.5 Negation and sign
Negation flips the sign of its operand; a unary plus is the identity. Neither has side effects, and chains (−−x) negate once per sign as written.
7.6 Results
Every fold’s result passes the real-and-finite check before it feeds the next. A non-finite intermediate — an overflow inside the platform’s numbers — aborts the whole evaluation with NonFiniteResultError rather than propagating a poisoned value.
7.7 Implicit products
Adjacency means multiplication where the notation puts values side by side: a number before a name, a name before a group, a group before a group. It does not mean multiplication between two numbers (2 3 is not a valid product — the corpus refuses it as a token fault) nor between a name and the keyword introducing an iteration. The normalisation of clause 6 makes every implicit product explicit before folding.
7.8 Accuracy classes
Every value an evaluation carries belongs to an accuracy class. Integers and rationals are exact: their add, subtract, multiply and rational division are exact operations, and an exact operand list yields an exact result. Floats are limited with undeclared accuracy (the platform’s bits); guard-digit decimals are limited to their declared precision. Limited operands contaminate exact ones: the accuracy of an operation’s result is the accuracy of its least precise operand. A five-digit value times a six-digit value is defined to five digits — the class algebra states this, the corpus vectors pin it.
7.9 Boundaries
A boundary is an operation that would replace exact values with platform approximations: a transcendental function, an irrational root, a non-exact power on exact operands. The evaluator processes until a boundary and never violates one silently — every crossing is reported.
7.10 Modes
The caller chooses the mode at the entry point; the default is approximate.
Approximate mode crosses every boundary: the computation runs on platform floats, and the result reports each boundary it crossed — the operation, the operands and their accuracy classes, and the produced value. This is the mode of the original contract: whatever the platform computes is delivered as computed, refused when not real-and-finite, never silently rounded. The corpus records its expected values, platform and version pinned.
Strict mode never crosses an exactness boundary: the first boundary defers the evaluation. The result then carries no value, the boundary record, and the unevaluated expression in source form — a partially evaluated result whose accuracy is undiminished, which the caller can re-run in approximate mode or with declared guard digits. Limited operands degrade as defined: a transcendental over a limited value computes without deferring, since there is no exactness left to lose.
Guard digits extend strict mode: with a declared digit count, a boundary is crossed by computing a decimal result to that declared precision (an exact rational square root stays exact without any declaration). Functions without a guard-digit implementation are refused with a typed error naming the function.
Exact vectors — whole expressions evaluated without a crossed boundary — are byte-identical across runtimes: their results are integers and rationals, not platform values. The exact-decimal discipline of the data layer (CalConnect 36813) is the same discipline strict mode evaluates under.
7.11 Results
Evaluation returns a result object, not a bare number: the value, the ordered boundary records (operation, operands, operand accuracy, action — approximated, guarded or deferred — and the produced value), and the unevaluated expressions when strict mode deferred. A result whose boundaries contain no approximation is exact; a deferred result carries the residual expression.
7.12 Order of folds
Left-associative levels fold left: a − b − c is (a − b) − c, and a / b / c is (a / b) / c. Right-associativity belongs to powers alone. The distinction is observable in floating point even when exact arithmetic would agree; the corpus pins the fold order with vectors whose inputs differ by association.
7.13 A precedence walk, concretely
The tree of −3 + 4 * 2^2 / (1 + 1):
the group folds first: 1 + 1 yields 2;
the power: 2^2 yields 4;
the multiplicative level folds left: 4 * 4 yields 16, then 16 / 2 yields 8;
the unary level negates 3 to −3 for its operand;
the additive level folds: −3 + 8 yields 5.
Expected outcome. 5 — each level once, in the chain’s order, with the group overriding the written order.
8 Functions
8.1 The function classes
The evaluator evaluates the function classes the model defines where a numeric semantics exists: the trigonometric and hyperbolic functions and their inverses, the logarithms (ln, lg, log with a base), the exponential, the absolute value, floor and ceiling, min and max, and the named constants of the registry where the platform defines their values.
| Family | Evaluation | Domain refusals |
|---|---|---|
| trigonometric | per the platform | none; results checked real and finite |
| inverse trigonometric | per the platform | argument outside [−1, 1] → MathDomainError |
| hyperbolic | per the platform | overflow → NonFiniteResultError |
| logarithms | ln, lg, log_b | non-positive argument → MathDomainError; base ≤ 0 or base 1 → MathDomainError |
| exponential | per the platform | overflow → NonFiniteResultError |
| abs | absolute value | none |
| floor, ceil | per the platform | none |
| min, max | over the arguments | none |
8.2 Arguments
A function’s arguments are its operands, evaluated left to right as ordinary sub-evaluations. The argument count is the class-defined arity of PM 1001; a tree that carries a different count is an UnsupportedExpressionError — the evaluator does not improvise signatures.
8.3 The logarithm base
log with a base evaluates to the logarithm in that base: log_2 8 yields 3 under exact arithmetic in the corpus. The one-argument form is the natural logarithm’s decadic sibling per the registry’s reading; the corpus pins which.
8.4 Constants
Registry constants with platform values (π, the exponential constant) evaluate to the platform’s value of that constant. Constants without one are names: they bind from the bindings or refuse with MissingVariableError, like any name.
EXAMPLE 1
The tree of log(x) with the binding x → 100 under the decadic reading. Expected outcome. 2 where the corpus fixes the decadic reading; the reading itself is pinned there, not negotiated per call.
EXAMPLE 2
The tree of sqrt(x) with x → −1. Expected outcome. MathDomainError — the square root of a negative number has no real value, and the contract has no complex numbers.
8.5 Nary calls and argument evaluation
The nary functions the evaluator defines (sum, prod with bounds) route to iteration (clause 10); the nary classes without bounds refuse as unsupported in this edition — a bare ∑ over a list has no evaluation semantics here. Argument sub-evaluations run through the same chain and contract; an error inside an argument aborts the whole call, and there is no argument-level recovery.
8.6 The log argument rule
The logarithm’s argument is the full expression the notation groups: log x + 1 is log(x) + 1 under the adjacency binding, and log(x + 1) is the logarithm of the sum. Where the writer’s shape and the reader’s grouping disagree, the tree is authority: the evaluator evaluates the tree it is given, and the corpus carries the disambiguated pairs.
8.7 Constants and names, precisely
A registry constant with a platform value evaluates without bindings; a name without one binds or refuses. The registry decides which is which, per PM 1001 — evaluation adds no constants of its own and treats an unknown constant exactly like any other name.
9 Bindings
9.1 The binding set
Bindings are the evaluation’s variables: a set of names, each with a real numeric value. Keys arrive as strings or symbols and normalise to strings; values must be real numbers.
9.2 Validation at entry
The binding set is validated before the tree is touched:
a key that is neither a string nor a symbol raises InvalidBindingKeyError, carrying the key;
a value that is not a real number raises InvalidBindingError, carrying the key and the value;
duplicate keys after normalisation — :x and "x" together — collapse to one binding, the last one given.
An evaluation with an invalid binding refuses before any evaluation happens: the tree is not walked with a bad environment.
EXAMPLE 1
Bindings { x: 3, "y" => 4.5 }. Expected outcome. accepted; the names x and y bind 3 and 4.5.
EXAMPLE 2
The binding key 2 — a number offered as a name. Expected outcome. InvalidBindingKeyError carrying 2, raised before evaluation.
EXAMPLE 3
The binding x → "three". Expected outcome. InvalidBindingError carrying the key and the string: values must be real numbers, not their spellings.
9.3 Missing names
A name the tree mentions and the bindings lack raises MissingVariableError, carrying the name. The refusal happens at first use — evaluation does not pre-scan the tree for names, so an evaluation that dies early reports the first missing name it needed, not all of them.
9.4 Shadowing
The iteration index temporarily shadows an outer binding of the same name for the body’s evaluation and restores the outer binding afterwards (clause 10). Shadowing is scoped to the body: outside the iteration, the outer binding is untouched.
EXAMPLE
The tree of i + sum_(i=1)^3 i with the binding i → 10. Expected outcome. the outer i contributes 10; the iteration binds its own i over 1..3 and contributes 6; the result is 16. The outer binding survives the iteration unchanged.
9.5 The numeric types accepted
A binding value may be any real numeric type the platform offers — integers, rationals, floating-point numbers. The evaluator normalises nothing: an integer binding stays an integer in the arithmetic where the platform keeps it one, a rational stays exact while its operations do, and the corpus pins the type-observable outcomes (sum_(i=1)^3 i with an integer index folds in integers). Values that are complex, non-real or not numbers at all refuse per clause 9.
9.6 Name shape
A name is the identifier text a tree’s symbols carry for evaluation. Names are case-sensitive and exact: X and x are different bindings, and a binding for one does not serve the other. There are no reserved names — the iteration indexes are ordinary names that shadow per clause 10.
9.7 Binding once, evaluating many
An evaluation binds its names once, at entry. There is no rebinding during evaluation, no environment mutation from inside the tree, and no callback through which the tree could observe or alter its own bindings.
10 Iteration
10.1 The construct
The nary sum and product with explicit bounds — sum_(i=1)^n body, prod_(i=1)^n body — evaluate as bounded iteration: the index runs the integer range and the body’s values fold under the operation, starting from the operation’s identity.
10.2 The bounds
The lower bound parses as index = start and the upper as the end expression. Both evaluate to integers: a bound that evaluates to a non-integer or an infinite value raises the typed error of its cause (MathDomainError for a non-integer bound where the platform names it so, NonFiniteResultError for an infinite one). An empty range — a start above the end — yields the identity: the empty sum is 0, the empty product is 1.
10.3 The step limit
A range wider than the implementation’s step limit refuses with UnsupportedExpressionError: iteration is bounded by construction, and an unbounded or enormous range is a caller error, not a long computation.
10.4 The fold
The body evaluates once per index value, left to right, with the index bound; the values fold under the operation — addition for the sum, multiplication for the product — from the identity. The index binding shadows per clause 9 and restores after.
EXAMPLE 1
sum_(i=1)^4 i^2. Expected outcome. 30 — 1 + 4 + 9 + 16, each square evaluated with the index bound, folded in order.
EXAMPLE 2
prod_(i=1)^4 i — the factorial of 4 written as a product. Expected outcome. 24.
EXAMPLE 3
sum_(i=5)^4 i — an empty range. Expected outcome. 0, the addition identity; no error, no evaluation of the body.
10.5 The index definition, precisely
The lower bound parses as the two-part form name = start. The name becomes the index; anything else in the lower-bound position is an UnsupportedExpressionError. The start evaluates once, to an integer; a non-integer start refuses with the typed error the platform names for it.
10.6 Bounds validation
After the start, the upper bound evaluates once, to an integer. Validation is order-fixed: start first, end second, both integers, then the step limit. The error each check raises names its stage, so a caller with a malformed bound learns which one it was.
10.7 The step limit’s value
The implementation carries a fixed maximum range width; the corpus pins it as behaviour (a range one past it refuses, one within it runs). The limit exists so that sum_(i=1)109 — an infinite work order in the caller’s clothing — is a refusal the caller handles, not a hang they kill.
10.8 Nested iteration
A body may itself carry an iteration: the inner index shadows the outer for the inner body, and the ranges compose. The corpus carries nested vectors to fixed depth; the step limits apply per range.
11 The error contract
11.1 The eight errors
| Error | Raised when | Carries |
|---|---|---|
| DivisionByZeroError | a division or modulo by an evaluated zero | nothing beyond the platform’s message where one exists |
| InvalidBindingError | a binding value is not a real number | the key and the value |
| InvalidBindingKeyError | a binding key is neither string nor symbol | the key |
| MathDomainError | an operation or function outside its real domain | the platform’s message |
| MissingVariableError | a name the tree needs is unbound | the name |
| NonFiniteResultError | a result or intermediate is not real-and-finite | nothing |
| UnsupportedExpressionError | the tree contains a node evaluation does not define | the node |
| Error | the base class | — |
Catching the base class catches them all; catching a subclass catches the specific refusal. The errors are the contract’s vocabulary: everything a caller can do about a refusal starts from one of these names.
11.2 Refusal points
Where each error can arise:
bindings — InvalidBindingKeyError, InvalidBindingError, at entry;
names — MissingVariableError, at first use;
unsupported content — UnsupportedExpressionError, at the node;
arithmetic and functions — DivisionByZeroError, MathDomainError, at the fold;
the boundary — NonFiniteResultError, at every fold and at the end.
11.3 What the contract forbids
No default values for missing names, no complex-number escape hatch, no unsigned infinity, no NaN, no evaluation of part of a tree, no second chance. Each of those would turn a refusal the caller can handle into a value the caller must guess about — the contract prefers the refusal.
11.4 Messages
Message shapes are stable within an edition. The identifying data travels in the message: the missing name, the offending binding, the node class refused. The corpus pins the shapes with one vector per error.
11.5 Causes, not substitutes
Where a typed error wraps a platform exception, the platform’s error is the cause — preserved for diagnostics, never raised in its place. Callers match on the typed name; the cause answers “what exactly did the platform say”.
11.6 Catching guidance
Handle the specific errors you can act on: retry with corrected bindings for MissingVariableError, report the input for UnsupportedExpressionError, treat DivisionByZeroError and MathDomainError as mathematical facts about the input. Catching the base class is for the boundary of a long pipeline; everywhere else, the specific name is the better match, and the corpus holds a vector for each name.
11.7 Errors are results too
A refusal is a well-defined outcome, deterministic as any number: the same tree and bindings produce the same error with the same message. The evaluation corpus treats errors exactly like results — frozen, replayed, drift-checked — because a caller’s error handling is only as reliable as the errors are repeatable.
12 What this edition does not do
12.1 Uncertainty propagation
The model carries uncertainty nodes (PM 1002); this edition’s evaluator does not compute with them. An evaluation whose tree contains an uncertainty node refuses with UnsupportedExpressionError — the honest answer, rather than an arithmetic that quietly drops the uncertainty. The propagation rules — sensitivities, the propagation law, second-order terms, coverage — enter this document in the edition whose implementation carries them.
12.2 Simplification
The evaluator never rewrites the tree: no folding of constants into nodes, no canonicalisation of equivalent forms, no algebra. Evaluation is a reading of the tree, and two equal trees evaluate identically while two different trees evaluate as written even when their values coincide.
12.3 Units and quantities
Evaluation works on numbers. Unit conversions, quantity arithmetic and the absolute/difference typing of the UnitsML series are that series’ contracts; an evaluator applied to unit-bearing trees evaluates the numeric content as the bindings supply it and the units ride with the tree, unconverted.
12.4 Solving
No root-finding, no inversion, no symbolic manipulation. The evaluator answers one tree with one number, given complete bindings; everything else is a different tool.
12.5 Why the limits are stated
Each non-goal is a contract someone will eventually ask for. Stating them keeps the boundary honest: an evaluator that quietly simplified would surprise every caller who diffed two trees; one that silently propagated uncertainty would make reported results and computed results behave differently in the same expression; one that converted units would need the whole UnitsML typing inside arithmetic. The limits are the design; the clauses above are the contract they protect.
12.6 The road in
When propagation arrives, it arrives in this document: its operations, its error extensions if any, its corpus, its conformance class — the same structure this edition uses for arithmetic. The uncertainty nodes of PM 1002 were built to carry everything propagation will need: category, fields, exact decimals. Nothing in the model blocks the road; the evaluator simply has not walked it yet.
13 Conformance
13.1 General
Conformance to this document is the implementation of the evaluator per clauses 5 to 11. One class covers it, with the corpus as its evidence; a partial implementation claims nothing.
13.2 Conformance classes
| Class | Licenses the implementation to claim | Replay requirements |
|---|---|---|
| evaluation | evaluates trees against bindings per this document | the evaluation corpus: results, errors, and message shapes, replayed identically |
The claim carries the model claim of PM 1001: an evaluator over trees is an implementation of trees first.
13.3 The evaluation corpus
The corpus freezes, per entry: the tree (in a canonical reading), the bindings, and the outcome — the exact result or the typed error with its message shape. Entries come from this document’s examples first and from defect fixes and community reports as they arrive; a generation is immutable once published.
13.4 Drift
An implementation whose result or error differs from the corpus for the same tree and bindings does not conform. Floating-point-order differences that change results are drift: the corpus, not the platform, defines the expected values, and an implementation adjusts or the document moves an edition.
14 Annex A: precedence and operations
14.1 The precedence chain in full
| Level | Constructs | Associativity | Folds to |
|---|---|---|---|
| relation | =, ≠, <, >, comparisons | left | relation evaluation |
| additive | +, − | left | add, subtract |
| multiplicative | implicit product, *, /, mod | left | multiply, divide, modulo |
| unary | −, + | prefix | negate, identity |
| power | ^ | right | power |
| operand | numbers, names, groups, calls | — | leaf or recursion |
| group | parentheses | — | recursion |
14.2 The operations in full
| Operation | Result | Refusals |
|---|---|---|
| add | a + b | non-finite intermediate |
| subtract | a − b | non-finite intermediate |
| multiply | a · b | non-finite intermediate |
| divide | a / b | zero divisor; non-finite |
| modulo | platform modulo | zero divisor |
| power | a^b in the real domains of clause 7 | domain; non-finite |
| negate | −a | none |
| identity | a | none |
14.3 Function domains, normative summary
| Function | Refusal domain |
|---|---|
| inverse trigonometric | argument outside the closed interval of arcsine and arccosine |
| logarithms | argument ≤ 0; base ≤ 0 or base = 1 |
| exponential, hyperbolic | overflow only |
| trigonometric, abs, floor, ceil, min, max | no domain refusals |
Every refusal raises MathDomainError with the platform’s message; every overflow raises NonFiniteResultError.
15 Annex B: the error messages
| Error | Message carries |
|---|---|
| DivisionByZeroError | the platform’s division message, where one exists |
| InvalidBindingError | the key, and the inspection of the rejected value |
| InvalidBindingKeyError | the inspection of the rejected key |
| MathDomainError | the platform’s domain message |
| MissingVariableError | the missing name, alone |
| NonFiniteResultError | nothing — the class is the message |
| UnsupportedExpressionError | the class of the refused node |
One vector per row exists in the corpus, so an implementation’s messages are checked against the shapes, not against prose in this document.
15.1 Refusal vectors in the corpus
The message shapes of this annex carry one corpus vector each, plus: a missing name deep inside an iteration body (the name reported is the body’s first use), an unsupported node inside a group (the group does not shield it), and a non-finite intermediate inside an argument (refused at the argument, before the call). The corpus, not this annex, is the checkable form; this annex is its key.
16 Annex C: test-suite examples
Vectors illustrative of the evaluation corpus. Each states the tree, the bindings, and the outcome.
16.1 Results
EXAMPLE 1
x^2 + 1 with x → 3. Expected outcome. 10.
EXAMPLE 2
232. Expected outcome. 512 — right-associative powers.
EXAMPLE 3
(a + b) * (a − b) with a → 5, b → 2. Expected outcome. 21 — groups first, then the product.
EXAMPLE 4
sum_(i=1)^4 i^2. Expected outcome. 30.
EXAMPLE 5
prod_(i=1)^4 i. Expected outcome. 24.
EXAMPLE 6
sum_(i=5)^4 i. Expected outcome. 0 — the empty range, no error.
EXAMPLE 7
i + sum_(i=1)^3 i with i → 10. Expected outcome. 16 — shadowing scoped to the body; the outer binding survives.
16.2 Refusals
EXAMPLE 1
1/0 and x/(y − y) with any x, y bound. Expected outcome. DivisionByZeroError — the second through an evaluated zero.
EXAMPLE 2
sqrt(−1) written as a power (−2)^(1/2). Expected outcome. MathDomainError.
EXAMPLE 3
log(0). Expected outcome. MathDomainError.
EXAMPLE 4
x + 1 with no bindings. Expected outcome. MissingVariableError carrying x.
EXAMPLE 5
The tree of a text node: text("hello"). Expected outcome. UnsupportedExpressionError naming the node class.
EXAMPLE 6
A tree containing an uncertainty node (PM 1002). Expected outcome. UnsupportedExpressionError — clause 12’s rule, tested.
EXAMPLE 7
The binding x → "three". Expected outcome. InvalidBindingError with the key and value.
EXAMPLE 8
The binding key 2. Expected outcome. InvalidBindingKeyError with the key.
EXAMPLE 9
An exponentiation whose magnitude overflows the platform’s numbers. Expected outcome. NonFiniteResultError at the fold, not at the end — intermediate checks refuse early.
16.3 Determinism
EXAMPLE
The same tree and bindings evaluated twice, in one process and across processes. Expected outcome. the identical result or the identical error, every time; the corpus replays the whole set.
16.4 Precedence vectors
EXAMPLE 1
−3 + 4 * 2^2 / (1 + 1). Expected outcome. 5, per the walk of Annex A.
EXAMPLE 2
8 / 4 / 2. Expected outcome. 1 — left-associative division.
EXAMPLE 3
2 − 3 − 4. Expected outcome. −5 — left-associative subtraction, not 3.
16.5 Binding vectors
EXAMPLE 1
:x and "x" given together with different values. Expected outcome. one binding, the last given; the corpus pins which spelling survives.
EXAMPLE 2
A rational binding 1/3 in x * 3. Expected outcome. 1 — exact under the platform’s rational arithmetic, and the corpus states the type-observable result.
16.6 Iteration vectors
EXAMPLE 1
sum_(i=1)^n i with n → 0. Expected outcome. 0 — the empty range for a bound that evaluated to zero.
EXAMPLE 2
A range past the step limit. Expected outcome. UnsupportedExpressionError naming the iteration; the limit’s edge is a corpus vector on both sides.
17 Annex D: worked evaluations
Two evaluations end to end, from tree to outcome.
17.1 The quadratic discriminant
The tree of b^2 − 4ac, bindings b → −3, a → 1, c → 2.
The power level: b^2 folds to 9.
The multiplicative level: 4, a, c fold to 8 — implicit products fold left: 4·1 = 4, 4·2 = 8.
The additive level: 9 − 8 folds to 1.
Expected outcome. 1. The discriminant is positive; nothing in this document says what that means — the caller reads their own mathematics into the number.
17.2 A series sum with shadowing
The tree of x_0 + sum_(n=1)^5 (n * x_0 / n) — a deliberately redundant form to exercise the machinery — bindings x_0 → 2.
The iteration’s lower bound reads n = 1; the upper bound evaluates to 5; the range is 1..5.
For each n in order, the body folds: the product n · 2 then the division by the bound n, yielding 2 each time — the index shadows nothing here (no outer n), but binds itself for the body.
The five values fold additively from 0: 10.
The additive level: 2 + 10.
Expected outcome. 12. The outer x_0 binding is untouched by the iteration; a second evaluation of the same tree with the same bindings yields 12 again, byte for byte.
17.3 What the walk-through shows
the precedence chain in action: powers, products, then the sum, then the addition;
the binding discipline: entry validation, use-time missing-name refusals, scoped index binding;
determinism as an observable property, not a promise.
Bibliography
[1] Plurimath repository, — https://github.com/plurimath/plurimath
[2] Plurimath::Math::Evaluation implementation, — the reference implementation of this document
[3] PM 1001, of this series
[4] PM 1002, of this series
[5] PM 1003, of this series
[6] JCGM 100:2008, INTERNATIONAL ORGANIZATION FOR STANDARDIZATION, ORGANISATION INTERNATIONALE DE NORMALISATION (ISO), INTERNATIONAL ELECTROTECHNICAL COMMISSION, COMMISSION ÉLECTROTECHNIQUE INTERNATIONALE (IEC), INTERNATIONAL ORGANIZATION OF LEGAL METROLOGY, ORGANISATION INTERNATIONALE DE MÉTROLOGIE LÉGALE (OIML), INTERNATIONAL UNION OF PURE AND APPLIED CHEMISTRY, UNION INTERNATIONALE DE CHIMIE PURE ET APPLIQUÉE (IUPAC), INTERNATIONAL UNION OF PURE AND APPLIED PHYSICS, UNION INTERNATIONALE DE PHYSIQUE PURE ET APPLIQUÉE (IUPAP), INTERNATIONAL FEDERATION OF CLINICAL CHEMISTRY AND LABORATORY MEDICINE, FÉDÉRATION INTERNATIONALE DE CHIMIE CLINIQUE ET DE BIOLOGIE MOLÉCULAIRE (IFCC), INTERNATIONAL LABORATORY ACCREDITATION COOPERATION, COOPÉRATION INTERNATIONALE D’ACCRÉDITATION DES LABORATOIRES (ILAC), and BUREAU INTERNATIONAL DES POIDS ET MESURES (BIPM). Guide to the expression of uncertainty in measurement. Report no. JCGM 100:2008. First edition. Paris: International Bureau of Weights and Measures, Bureau international des poids et mesures; International Organization for Standardization, Organisation internationale de normalisation; International Electrotechnical Commission, Commission électrotechnique internationale, 2008. Available from: https://www.bipm.org/documents/20126/2071204/JCGM_100_2008_E.pdf/cb0ef43f-baa5-11cf-3f85-4dcd86f77bd6.