015. Exact PostgreSQL Float Arithmetic

  • Status: Superseded
  • Author: @ornew
  • Date: 2026-10-13

Superseded for Float-specific parsing, arithmetic, rounding, and result expectations by 021. Backend-Native Approximate Float Semantics. This record remains as the history and rationale for the prior exact SQL lowering. Its Int behavior and exact mixed Int/Float comparison requirements remain in force.

Summary

The PostgreSQL backend supports HFF float and mixed int/float +, -, *, and / while preserving HFF's binary64 round-to-nearest, ties-to-even result. It computes with exact PostgreSQL numeric intermediates and applies an explicit binary64 rounding step instead of relying on PostgreSQL float operators to produce the final result.

Motivation

HFF specifies IEEE 754 binary64 arithmetic, including midpoint ties, subnormal results, signed zero, and null on overflow or non-finite results. PostgreSQL float arithmetic is not used as a proof of those language-level edge cases. The SQL backend needs a lowering that agrees with local evaluation and promotes an integer operand to float64 before the mixed operation, as HFF specifies.

Goals

  • Preserve HFF's numeric result kind: integer-only +, -, *, and % remain int64; division and mixed int/float arithmetic return float64.
  • Match binary64 round-to-nearest, ties-to-even, including exact ties, subnormal results, and signed zero.
  • Return null for integer overflow, division by zero, float overflow, and non-finite results.
  • Keep query operands parameterized and preserve HFF null/no-value propagation.

Non-goals

  • Float remainder; HFF remainder is int64-only.
  • A performance guarantee for the exact arithmetic SQL expressions.
  • Designing canonical JSON serialization. That representation contract and its PostgreSQL lowering are recorded separately.

Design

Public behavior and contracts

Scalar Float arithmetic supports +, -, *, and /; mixed Int and Float operands promote to float64. Integer-only operations retain the int64 contract, and % remains integer-only. Dynamic JSON arithmetic supports +, -, *, /, %, and unary negation when runtime JSON numbers can be recovered from native JSON spelling or JSONB type metadata. An incompatible runtime value, integer overflow, division by zero, or non-finite result yields ordinary null. HFF's operand presence and no-value rules remain in force.

Implementation and invariants

Each finite binary64 operand is reconstructed as an exact decimal rational from its IEEE sign, significand, and exponent. Addition, subtraction, and multiplication then produce an exact numeric value. The result is rounded to binary64 and checked against explicit boundaries: overflow begins at the rounding midpoint 2^1024 - 2^970; values at or below half the minimum subnormal, 2^-1075, round to signed zero as required.

Division requires a rational result that may not have a finite decimal expansion. The lowering computes a quotient candidate at 1000 decimal places, then uses exact cross-products to detect whether the true quotient is exactly on a binary64 midpoint. If a candidate lies on such a midpoint and its significand is odd, it is adjusted to the even adjacent value. For a ratio of finite binary64 operands, the reduced denominator is at most 2^2098; binary64 midpoints have denominators at most 2^1075. A non-tie quotient is therefore separated from a midpoint by at least 2^-3173. Rounding the candidate to 1000 decimal places introduces error no greater than 5e-1001, so it cannot cross a non-tie midpoint. Exact cross-products handle equality rather than inferring a tie from the approximation.

JSON arithmetic first resolves integer and float candidates using the schema, native JSON number spelling, and, for JSONB, the configured numeric metadata. Integer-only dynamic operands use the checked int64 lowering; other supported numeric combinations use the exact float path. JSONB metadata is needed when storage has erased an integer/float or value distinction that the operation observes. Dynamic serialization follows the separate canonical JSON serialization record and requires a recoverable JSON representation.

Compatibility and backend support

The change adds PostgreSQL support without changing the language contract in spec/types.md or spec/evaluation.md. Other SQL dialects do not inherit this implementation. Float remainder, non-finite stored inputs, and JSONB distinctions not recoverable from schema or metadata remain unsupported or outside the caller's storage contract. See the PostgreSQL numeric guide for user-facing examples and metadata requirements.

Alternatives considered

Use PostgreSQL double precision operators directly

This is concise, but it does not establish HFF's exact midpoint and boundary behavior across all binary64 values. It is not used for the final rounding step.

Use a fixed decimal approximation without a tie check

A fixed precision separates non-ties when its error is below the minimum distance to a rounding midpoint, but an exact midpoint still needs explicit ties-to-even handling. The selected division path combines a high-precision candidate with exact cross-products and even-neighbor correction.

Testing

The SQL arithmetic tests compare completed PostgreSQL results with local HFF results at ordinary values, exact midpoint cases, adjacent values, overflow, the half-minimum-subnormal boundary, subnormals, and signed zero. Dynamic JSON tests also cover numeric-kind guards, values, and null/no-value propagation for native JSON and metadata-restored JSONB. JSON decoding cases include decimal spellings below the float64 range, which must become signed zero instead of a database cast error. The current validation set includes 300 typed-float comparisons and 1,440 dynamic JSON/restored-JSONB operator, type, value, and guard comparisons; these are regression evidence, not an exhaustive enumeration of binary64 inputs.

Limitations and open questions

The SQL expression is a correctness-first lowering, not a latency or SQL-size guarantee. JSONB cannot recover distinctions that neither its stored value nor configured metadata records. Canonical serialization has its own supported types and representation limits, described in the separate design record.

Explore HFF
Search documentation

Search the language specification and guides.