Browse documentation
Language

Approximate comparison

Compare binary numerical results with explicit relative and absolute tolerances, immutable local policies and recorded numerical boundaries.

API reference

a ~== b returns Bool using symmetric relative and absolute tolerances. The operator and local approx blocks belong to the scalar language and require no import. Exact == retains its existing behavior.

aner
print(0.1 + 0.2 == 0.3)  // false
print(0.1 + 0.2 ~== 0.3) // true

The fixed defaults are relative 1.7763568394002505e-15 (eight binary64 machine epsilons) and absolute 0.0. They cover small Float64 rounding differences; they do not establish an algorithm's error budget. Float32 computations generally need explicit tolerances appropriate to their precision.

Local immutable tolerances

Write both tolerance names at a block boundary:

aner
let relative_error = 1e-6
let absolute_error = 1e-10
approx(relative: relative_error, absolute: absolute_error) {
    print(1000000.0 ~== 1000000.5) // true
    print(1e-12 ~== 0.0)          // true
}
print(1e-12 ~== 0.0)              // false: fixed defaults

Both named arguments are required, in either source order. Positional, unknown, duplicate and missing arguments are rejected. Header expressions evaluate exactly once, in source order, each time the block is entered. Both values are validated before the body runs, including for an empty body. They are private immutable Float64 snapshots: reassigning a variable used by the header cannot change the active policy. A nested approx block creates a new policy; leaving it exposes the enclosing policy. Normal let/var lexical scope also applies to the body.

The block applies only to ~== expressions written inside it, including ordinary nested blocks and loops. It does not change comparisons inside called functions, methods or parallel tasks. A helper or task can accept tolerance numbers as explicit parameters and enter its own approx block. No global or thread local setting exists. Completed blocks cannot affect subsequent notebook cells, and retained functions keep the policy written in their defining source.

For a complete example showing snapshots, nested scopes and helper isolation, run approx_comparison.aner:

sh
aner check examples/approx_comparison.aner
aner run examples/approx_comparison.aner

Expected output:

text
false
true
false
true
true
false
false
false

Numerical contract

The mathematical rule is:

text
|a - b| <= absolute  OR  |a - b| <= relative * max(|a|, |b|)

Relative tolerance scales with the larger magnitude. Absolute tolerance handles near zero values and cancellation; it uses the operands' numerical scale, while relative tolerance is dimensionless. Choose both from the scientific question and numerical error budget. Convert physical values to the same unit before extracting raw numerical values.

Operands and tolerances use the existing checked Float64 conversion boundary. Integers must convert exactly; inexact automatic promotion fails with R1003. Float32 widens exactly but retains its earlier rounding. Decimal, Bool, Any, arrays and unit bearing scalar operands are not admitted directly. Cast Any to a concrete numeric type or extract an appropriate raw scalar before comparison.

For exact admitted decimal inputs, use Decimal and ordinary equality. Decimal does not enter this binary comparison implicitly; an explicit Float64(decimal) boundary permits rounding.

All inputs must be finite. NaN or infinity fails with source located R3601/E_MATH_NONFINITE. Relative tolerance must satisfy 0 <= relative < 1; absolute tolerance must be nonnegative. Invalid tolerances fail with R3601/E_MATH_DOMAIN on block entry. Signed zeros compare equal. Setting both tolerances to zero requests exact equality of the admitted Float64 values. The shared helper avoids overflowing differences and false matches caused by rounded subnormal relative thresholds.

~== has equality precedence. Comparison chaining is rejected: write (a ~== b) && (b ~== c) for two comparisons. Approximate comparison is symmetric but not transitive; it does not define identity, key equality, deduplication or sorting equivalence.

math.close remains available after import aner.math. It always uses its own supplied arguments or fixed defaults, including inside an approx block. The operator and function share the same numerical implementation.

Traceability

The editor highlights ~== and approx, offers an approx snippet and named argument help, and shows the selected policy when hovering over ~==. Go to Definition opens the nearest local policy header. Hover displays header expressions, without executing them; inspect the Development record for their actual runtime values.

Development inspection records effective relative/absolute values, the policy source range and SHA-256 identity, default/local origin and an exact activation count. Equal values and source locations are aggregated. Recording is bounded to 64 distinct policies; omitted activations are disclosed. Recording happens at policy activation, rather than on every comparison in a hot loop. These records describe policies, not a log of every comparison result. Development records cover the current interpreter; approximation activations inside parallel worker interpreters are not collected into the coordinator report. A default policy is recorded on entry to a script or function body containing default policy operators, even when a conditional branch prevents those comparisons from executing.

Native build receipts include the fixed defaults and a static comparison site to policy map under approximation, with schema aner.approximation/1. Generated C++ retains the policy snapshots. Actual dynamic policy values are available through interpreted Development reports; a native build receipt alone does not contain runtime dependent values.

Aner documentation · Reference and guides