Skip to content

Core: Fix Z-order byte encoding for floating-point values - #17071

Merged
RussellSpitzer merged 2 commits into
apache:mainfrom
eye-gu:fix-17070-zorder-float-ordering
Aug 13, 2026
Merged

Core: Fix Z-order byte encoding for floating-point values#17071
RussellSpitzer merged 2 commits into
apache:mainfrom
eye-gu:fix-17070-zorder-float-ordering

Conversation

@eye-gu

@eye-gu eye-gu commented Jul 3, 2026

Copy link
Copy Markdown
Member

Fixes #17070

@github-actions github-actions Bot added the core label Jul 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions.

@github-actions github-actions Bot added the stale label Aug 3, 2026
@github-actions

Copy link
Copy Markdown

This pull request has been closed due to lack of activity. This is not a judgement on the merit of the PR in any way. It is just a way of keeping the PR queue manageable. If you think that is incorrect, or the pull request requires review, you can revive the PR at any time.

@github-actions github-actions Bot closed this Aug 11, 2026
vbhanuchander-lang added a commit to vbhanuchander-lang/iceberg that referenced this pull request Aug 13, 2026
floatingPointOrderedBytes shifts a long by Integer.SIZE - 1 (31) instead
of Long.SIZE - 1 (63) when building the sign mask. For a positive value
the shift is meant to produce 0 so that only the sign bit is flipped, but
shifting by 31 leaves exponent bits in the low 32 bits of the mask, which
then corrupt the low bytes of the encoding.

Ordering is still decided correctly by the high bytes, so the bug only
surfaces when two values agree on their top 31 bits and the corrupted low
bytes decide the comparison. There the order is inverted: every one of
the 2016 pairs drawn from 1.0d + i * 2^-30 encodes backwards, as do 48 of
the 496 pairs among 32 consecutive float bit patterns above 1.0f, the
smallest being 1.0f and Math.nextUp(1.0f).

The existing random ordering tests draw from nextFloat()/nextDouble(),
whose values essentially never agree on their high bits: with the fixed
seed of 42, none of the 100000 pairs in either test share bits 63..33, so
neither test reaches the corrupted low bytes.

The encoding is not persisted -- SparkZOrderFileRewriteRunner adds it as
the temporary ICEZVALUE column, sorts on it and drops it -- so this only
changes the clustering produced by future z-order rewrites.

The diagnosis and the one-character fix are eye-gu's, from issue apache#17070.
The boundary pairs in testDoubleOrderingForBoundaryPairs are taken from
their PR apache#17071, which was closed by the stale bot without review.
@vbhanuchander-lang

Copy link
Copy Markdown

@eye-gu this was closed by the stale bot, not on merit — your fix is correct and your test is good.
I independently verified the whole thing before finding your PR, and I want to record that here so
it is easier for a committer to act on:

  • lval >> (Integer.SIZE - 1) leaves exponent bits in the low 32 bits of the mask. For 1.0d the
    mask is 0x800000007fe00000 instead of 0x8000000000000000, so the low bytes get corrupted.
  • Ordering is still correct on the high bytes, so it only breaks when two values agree on their top
    31 bits. There it inverts: all 2016 pairs from 1.0d + i * 2^-30 encode backwards, and 48 of the
    496 pairs among 32 consecutive float bit patterns above 1.0f — the smallest being 1.0f and
    Math.nextUp(1.0f).
  • Why CI never caught it: testFloatOrdering/testDoubleOrdering draw from
    nextFloat()/nextDouble(). I replayed the exact Random(42) sequence — none of the 100 000
    pairs in either test share bits 63..33
    , so neither test ever reaches the corrupted low bytes.
  • No compatibility concern: the encoding is never persisted. SparkZOrderFileRewriteRunner adds it
    as the temporary ICEZVALUE column, sorts on it, and drops it.

I opened #17628 before I spotted this PR — that was my mistake, and I have credited you in its
commit message and borrowed your boundary pairs with attribution. I would rather yours merged
than mine.
The bot's message says you can revive it at any time by commenting; if you do, I will
close #17628 immediately and you are welcome to any of my extra tests.

Also worth flagging for whoever picks this up: #17130 is an auto-generated PR for the same issue
that is still open and contains the production fix with no test.

@RussellSpitzer

Copy link
Copy Markdown
Member

Bots talking to each other aside, I think this is fine. Sorry I missed at let it go stale. I'd rather merge this than the new PR because the new one goes a bit overboard on testing.

@RussellSpitzer
RussellSpitzer merged commit dcd9fee into apache:main Aug 13, 2026
90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Core: Z-order byte encoding for floating-point values is not order-preserving

3 participants