Future work: switch Float SMT encoding from Real to FPSort
Today Float is encoded as z3 Real. This keeps SMT reasoning decidable, sort-uniform and rounding-mode-free, but it does not match runtime IEEE-754 semantics (rounding, overflow, NaN, infinities). This issue tracks what it would take to move to a true floating-point encoding (FPSort), and what we would gain. It is intentionally left for the future — it is a design change, not a one-line sort swap. See the analysis comments below for the empirical evidence (verified against z3 4.15.3) of why a naive swap fails.
Current state (entry points)
get_sort: Float -> z3.RealSort() — aeon/verification/smt.py:554-555
make_variable: Float vars -> Real(name) — aeon/verification/smt.py:631
base_functions: all numeric operators (+ - * / == != < <= > >=) are generic Python operators shared between Int and Float — aeon/verification/smt.py:116-138
The Real encoding works precisely because that one generic, sort-agnostic operator table can serve both Int and Float.
What needs to be done
- Introduce explicit float widths. There is no single "Float" in FP: results depend on precision (
0.1 + 0.2 == 0.3 is unsat at FP64, sat over Real). A fixed FPSort(8, 24) bakes a representation choice into the type system, so this likely means a Float32/Float64 type family rather than one Float, with FPSort(8, 24) / FPSort(11, 53) respectively.
- Make operator translation sort-aware. FP sorts do not mix with the
Int/Real literals we emit (FP + IntVal(1) and FP + RealVal(1.5) both raise sort mismatch). translate_app / base_functions can no longer be type-agnostic: each operator must dispatch on operand sort and insert coercions (fpToReal, fpToFP, ToReal, fpToInt, …) for mixed Int/Float refinements.
- Thread a rounding mode through arithmetic. FP
+ - * / require an explicit rounding mode (fpAdd(RNE(), x, y), not fpAdd(x, y)). Pick and thread a policy (default roundNearestTiesToEven), and decide whether it is user-controllable.
- Handle NaN / infinities in comparisons. Over FP,
x == x is not a tautology (NaN) and x + 1.0 > x can fail (infinities/saturation). Equality and ordering need FP-aware predicates (fpEQ, fpLT, …) and the type system must decide how NaN/Inf interact with refinements (e.g. exclude NaN by default, or expose isNaN/isInfinite).
- Float literals. Emit
FPVal(v, sort) instead of RealVal, and route literal typing through the chosen width.
- Migrate / re-validate existing specs. Algebraic specs that verify today under
Real (e.g. def half (x : Float) : {y:Float | y * 2.0 == x} = x / 2.0;) may no longer be valid under FP. Existing Float refinements and tests in tests/smt_test.py must be reviewed.
What would be gained
- Faithful runtime semantics: verification would reason about the same rounding/overflow/NaN behaviour that the evaluator actually produces, closing the gap where a refinement is proven over
Real but violated at runtime by rounding.
- Ability to express and check FP-specific properties: NaN-freedom, absence of overflow/underflow, bounds that account for representable values, etc.
- Correctness for numerically sensitive code (the kind where
0.1 + 0.2 != 0.3 matters).
Trade-off to keep in mind
We would lose the exact, decidable, sort-uniform, rounding-free arithmetic that makes the current generic translation simple and makes clean algebraic refinements valid. FP solving is also generally harder for the SMT backend. A reasonable path is to keep Real as the default and add FP behind an explicit opt-in fixed-width float type, only where IEEE-754-accurate refinements are actually needed.
Note: the previous inline TODO/commented-out FPSort(8, 24) stub in make_variable was removed in #314; this issue is now the canonical record of the plan.
Future work: switch
FloatSMT encoding fromRealtoFPSortToday
Floatis encoded as z3Real. This keeps SMT reasoning decidable, sort-uniform and rounding-mode-free, but it does not match runtime IEEE-754 semantics (rounding, overflow, NaN, infinities). This issue tracks what it would take to move to a true floating-point encoding (FPSort), and what we would gain. It is intentionally left for the future — it is a design change, not a one-line sort swap. See the analysis comments below for the empirical evidence (verified against z3 4.15.3) of why a naive swap fails.Current state (entry points)
get_sort:Float->z3.RealSort()—aeon/verification/smt.py:554-555make_variable:Floatvars ->Real(name)—aeon/verification/smt.py:631base_functions: all numeric operators (+ - * / == != < <= > >=) are generic Python operators shared betweenIntandFloat—aeon/verification/smt.py:116-138The Real encoding works precisely because that one generic, sort-agnostic operator table can serve both
IntandFloat.What needs to be done
0.1 + 0.2 == 0.3isunsatat FP64,satoverReal). A fixedFPSort(8, 24)bakes a representation choice into the type system, so this likely means aFloat32/Float64type family rather than oneFloat, withFPSort(8, 24)/FPSort(11, 53)respectively.Int/Realliterals we emit (FP + IntVal(1)andFP + RealVal(1.5)both raisesort mismatch).translate_app/base_functionscan no longer be type-agnostic: each operator must dispatch on operand sort and insert coercions (fpToReal,fpToFP,ToReal,fpToInt, …) for mixedInt/Floatrefinements.+ - * /require an explicit rounding mode (fpAdd(RNE(), x, y), notfpAdd(x, y)). Pick and thread a policy (defaultroundNearestTiesToEven), and decide whether it is user-controllable.x == xis not a tautology (NaN) andx + 1.0 > xcan fail (infinities/saturation). Equality and ordering need FP-aware predicates (fpEQ,fpLT, …) and the type system must decide how NaN/Inf interact with refinements (e.g. exclude NaN by default, or exposeisNaN/isInfinite).FPVal(v, sort)instead ofRealVal, and route literal typing through the chosen width.Real(e.g.def half (x : Float) : {y:Float | y * 2.0 == x} = x / 2.0;) may no longer be valid under FP. Existing Float refinements and tests intests/smt_test.pymust be reviewed.What would be gained
Realbut violated at runtime by rounding.0.1 + 0.2 != 0.3matters).Trade-off to keep in mind
We would lose the exact, decidable, sort-uniform, rounding-free arithmetic that makes the current generic translation simple and makes clean algebraic refinements valid. FP solving is also generally harder for the SMT backend. A reasonable path is to keep
Realas the default and add FP behind an explicit opt-in fixed-width float type, only where IEEE-754-accurate refinements are actually needed.Note: the previous inline
TODO/commented-outFPSort(8, 24)stub inmake_variablewas removed in #314; this issue is now the canonical record of the plan.