Numerical semantics — MATHREAL = ℝ and the Representation Boundary¶
Audience: developer who wants to understand why zqel's MATHREAL is not a float
type, what the numerical assurance chain does, and when a mathematical proof is sufficient vs. when
a numerical error bound is needed.\
Language Reference: §13 MATHREAL semantics,
§17 Profiles.\
How-to: Configure MATHREAL representation.
The core choice: MATHREAL = ℝ¶
zqel's MATHREAL and LREAL are mathematical real numbers, not machine floats.
This is not a naming convention or a simplification. It is a deliberate proof-theoretic choice with consequences throughout the compiler:
- Z3 reasons in the ℝ sort: exact, without rounding, without NaN or Inf.
- Algebraic identities (
x/100 * 1000 = 10x) hold without qualification. - An
INVARIANT s >= -1.0is a theorem about ℝ, valid for everys ∈ ℝ. - The proof is independent of which machine type
swill eventually be stored in.
The alternative — equating MATHREAL = Float64 at the type level — would mean:
- Every proof must reason in IEEE 754 arithmetic: non-associative, with NaN/Inf,
with the identity
0.1 + 0.2 ≠ 0.3. - Z3's REAL sort (exact ℝ) cannot be used for
MATHREALvariables. - Simple expressions like
(a + b) * canda*c + b*cwould require a Gappa proof to be equated. - The invariant
s >= -1.0would be false for any computation that produces -∞ or NaN.
The mathematical semantics is more fundamental and more useful for the verification task. Machine arithmetic is a representation chosen for deployment, not the meaning of the programme.
Three levels that must not be confused¶
| Level | What lives here | zqel tool |
|---|---|---|
| A — Mathematical domain (ℝ) | Variables, invariants, contracts | Z3 (REAL sort) |
| B — Machine arithmetic | Float32, Float64, Fixed-point values | Gappa |
| C — Physical quantisation | Sensor noise, ADC resolution, timing jitter | Declared assumptions in .toml |
A Float64 rounding error is ≈ 2×10⁻¹⁶. A typical industrial sensor has ±0.1% error ≈ 10⁻³. An ADC quantisation step is 2⁻¹² ≈ 2.4×10⁻⁴. These are four orders of magnitude apart and come from completely different sources. Mixing them into a single number produces a meaningless bound. The numerical assurance chain keeps them separate and accounts for each contribution explicitly.
The Representation Boundary¶
A Representation Boundary is the explicit point where a mathematical MATHREAL value is assigned a machine type. It requires a proof obligation:
∀ x ∈ [lo, hi] : |repr(x) − x| ≤ ε ∧ finite(repr(x))
zqel places Representation Boundaries:
- At the output of the block (mathematical result → machine storage)
- At inputs (machine sensor value → MATHREAL variable; the gap is declared as a
[physical_errors] assumption)
Before the boundary, all algebraic identities of ℝ hold. After it, no simplification is valid without a new Gappa proof.
In the source language, the boundary is declared by profile or project file, not by
type annotation:
- --profile float64 or [representation] real = "float64" in a project TOML sets
Float64 as the representation for all MATHREAL/LREAL variables.
- Without a boundary, the compiler verifies the programme mathematically but refuses
to emit code — the gap between ℝ and any machine type is unaccounted for.
This fail-closed default prevents silent promotion of mathematical proofs as machine guarantees.
What the numerical assurance chain computes¶
The numerical assurance chain answers: given a declared deployment, how far can the machine implementation deviate from the mathematical specification over time?
The answer is a bound E_N (for N-step trajectories) and E_∞ (steady-state),
composed from three separate error sources:
Machine roundoff (ε_m)¶
The per-step error introduced by performing the mathematical computation in Float64 instead of ℝ. Computed (or bounded) by Gappa. For most process-control computations, ε_m ≈ 10⁻¹⁶ — negligible compared to other sources.
Physical input error (ε_p)¶
Declared in [physical_errors] as an engineering assumption. Represents the difference
between the value the sensor reports and the true physical quantity. Not a mathematical
theorem — a declared specification of the physical world.
Discretisation and jitter (ε_d, ε_j)¶
The error introduced by replacing a continuous ODE with a discrete update rule
(Forward Euler, Runge-Kutta, etc.) and by imperfect timing (the actual scan period
differs from the nominal). Requires a CONTINUOUS_REFERENCE block declaring the ODE
and [sampling.*] fields declaring the deployment parameters.
Trajectory composition¶
Per-step errors compose through the system's Lipschitz constant:
E_N = ε_combined · (L^N − 1) / (L − 1) [contractive: L < 1]
E_∞ = ε_combined / (1 − L) [steady-state bound]
For a contractive system (filter, controller with feedback), E_∞ is finite. For
a marginally stable system, the bound grows without limit — a correct warning that the
deployment is sensitive to numerical precision.
Provenance: what the bound actually guarantees¶
The E_∞ bound is proved-under-assumptions: it is mathematically derived given
the declared assumptions about the deployment. The assumptions are:
[physical_errors]values — sensor specifications, not measured facts[sampling.*] periodandjitter— deployment declarations, not hardware proofs[sampling.*] input_derivatives]— physical input trajectory assumptions
A bound with proved-under-assumptions provenance is only as strong as the declared
assumptions. If the sensor truly has ±1% error but you declared ±0.1%, the bound is
wrong. The Engineering Error Report makes this explicit: every declared assumption is
listed under "Declared assumptions", and the overall provenance ceiling is the weakest
link.
The correct reading of E_∞ = 0.27 is: "If the declared assumptions hold, then
with mathematical certainty, |s_machine − s_exact| ≤ 0.27 at all times."
Why Float64 is usually sufficient, but not always¶
Float64 machine epsilon ≈ 2.2×10⁻¹⁶ is typically negligible next to physical sensor errors and discretisation errors. For most process-automation tasks, Float64 roundoff is not the limiting factor in the error budget.
However, Float64 is not sufficient for: - Long accumulation chains (integrators running for millions of cycles) where roundoff accumulates - Cancellation-prone expressions (subtracting two nearly-equal large values) - Algorithms designed for higher precision
The Engineering Error Report makes this visible: if ε_m is the dominant term, the
representation needs attention. If ε_j (jitter) dominates, tighter timing hardware
or a different control design may be more effective than changing the numeric type.
LREAL vs. MATHREAL¶
Both denote mathematical ℝ, and neither has a machine representation at the
language level (Language Reference §5.5). The
difference is what each one signals about the Representation Boundary that a
deployment will later choose: LREAL says Float64-or-better is expected there,
MATHREAL says nothing about precision at all.
At the boundary there is today exactly one policy. RealRepresentation offers
none (fail-closed: the backends refuse) and float64; under float64 both
types lower to f64 (Rust) and LREAL (IEC 61131-3 ST). An external contract
accepts float64 and lreal64; float32 is refused explicitly — it is
structurally expressible, but the Gappa analysis for it does not exist, and a
representation without an error analysis would be a promise nobody can keep.
The distinction is therefore a statement of intent, not a semantic difference. A
programme proven over ℝ is equally correct for MATHREAL and LREAL.
See also¶
- Language Reference §13 — MATHREAL semantics
- Language Reference §17 — Profiles
- Project Configuration Reference
- How-to: Configure MATHREAL representation
- ADR: Numerical Semantics for the full decision record
- Introduction by Example §8 and §19 for worked examples