Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


id: ADR-0030 title: Canonicalize NaN and preserve -0.0 on the hashing path; never use Float64 as a distinct or join key status: accepted date: 2026-09-13 deciders: [paul-heyse] level: decision principles: [DM-40, DM-42, DM-08, DM-15] blueprint: [§5.3, §14.2, §19.2] review: docs/design_review/reviews/design_review_arrow-native-idaes-core-blueprint-rev2_2026-09-13.md#7-principle-findings evidence: Interface-checked supersedes: [] superseded-by: null revisit: DataFusion changes its grouping or join equality for floats, or a rule genuinely needs a float-valued key verification: tests/engine rule-compiler rejection test (rule.float_key); the canonicalization property tests in tests/conformance


ADR-0030: Canonicalize NaN and preserve -0.0 on the hashing path; never use Float64 as a distinct or join key

Context

Blueprint §5.3 step 5 hashes floats as IEEE-754 bits after mapping every NaN to the quiet NaN with zero payload and positive sign, preserving -0.0. The second review’s R2-2 measured that the preservation claim fails under GROUP BY, DISTINCT and hash joins, which merge -0.0 with +0.0 and treat NaN as self-equal.

Scope

Binds float canonicalization on the hashing path and the key discipline in rule plans. It does not change what reaches a solver: a NaN’s provenance matters for diagnostics (§18.2).

Drivers

An unqualified “-0.0 is preserved” is a claim the engine contradicts (charter DM-59); a float join key silently merges rows a reader believes are distinct.

Options

Canonicalize -0.0 to +0.0 too — rejected: the sign is meaningful in Arrow’s totalOrder and in stored data. Allow float keys with a tolerance — rejected: tolerance is a physical decision, not a key semantics decision.

Outcome

canonical_f64_bits maps every NaN to the quiet NaN with zero payload and positive sign and preserves -0.0, applied on the hashing path only. Preservation holds for the content hash and for Arrow ordering (null, -1, -0, 0, NaN, nulls first); it does not hold under engine grouping or joining, and §5.3 now says so. Rule plans never use a Float64 column as a distinct or join key.

Consequences

Analytics that group on floats collapse -0.0 with +0.0; §19.2 records the collapse rather than hiding it. Null-bearing key comparisons use distinct/not_distinct semantics and Kleene booleans.

Compensating controls

The rule compiler rejects a violating rule_plan_nodes row with rule.float_key, so the discipline is enforced at compile time rather than reviewed by eye.

Confirmation

tests/engine asserts the rejection; the canonicalization property tests assert the hash is invariant under re-batching and stable across NaN payloads.

Pros and cons

Refusing float keys costs expressiveness in rules; the measured engine behaviour is why it costs less than allowing them.

More information

Blueprint §5.3 step 5, §14.2 rule 7, §19.2; review finding R2-2; DataFusion capability map PROBE D (measured).

Status history

  • 2026-09-13 — accepted with the repository-seeding pull request (backfilled from blueprint revision 3).