This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new bc7ad717be [common] Run the z-order sign flip at the width of the bit 
pattern (#9628)
bc7ad717be is described below

commit bc7ad717be22862c480adb3be5665840cd143f3c
Author: YangJie <[email protected]>
AuthorDate: Thu Sep 10 03:00:07 2026 -0400

    [common] Run the z-order sign flip at the width of the bit pattern (#9628)
---
 .../apache/paimon/sort/zorder/ZOrderByteUtils.java |  6 +-
 .../paimon/sort/zorder/TestZOrderByteUtil.java     | 76 ++++++++++++++++++++++
 2 files changed, 80 insertions(+), 2 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/sort/zorder/ZOrderByteUtils.java
 
b/paimon-common/src/main/java/org/apache/paimon/sort/zorder/ZOrderByteUtils.java
index 04bf863a3f..05cb42122e 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/sort/zorder/ZOrderByteUtils.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/sort/zorder/ZOrderByteUtils.java
@@ -108,8 +108,10 @@ public class ZOrderByteUtils {
      */
     public static ByteBuffer floatToOrderedBytes(float val, ByteBuffer reuse) {
         ByteBuffer bytes = reuse(reuse, PRIMITIVE_BUFFER_SIZE);
+        // Widening to double is exact and order-preserving, so the double bit 
pattern can
+        // carry the float: flip the sign bit, and invert the rest for 
negatives.
         long lval = Double.doubleToLongBits(val);
-        lval ^= ((lval >> (Integer.SIZE - 1)) | Long.MIN_VALUE);
+        lval ^= ((lval >> (Long.SIZE - 1)) | Long.MIN_VALUE);
         bytes.putLong(lval);
         return bytes;
     }
@@ -120,7 +122,7 @@ public class ZOrderByteUtils {
     public static ByteBuffer doubleToOrderedBytes(double val, ByteBuffer 
reuse) {
         ByteBuffer bytes = reuse(reuse, PRIMITIVE_BUFFER_SIZE);
         long lval = Double.doubleToLongBits(val);
-        lval ^= ((lval >> (Integer.SIZE - 1)) | Long.MIN_VALUE);
+        lval ^= ((lval >> (Long.SIZE - 1)) | Long.MIN_VALUE);
         bytes.putLong(lval);
         return bytes;
     }
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/sort/zorder/TestZOrderByteUtil.java
 
b/paimon-common/src/test/java/org/apache/paimon/sort/zorder/TestZOrderByteUtil.java
index cfab2a7b7d..48e4a87467 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/sort/zorder/TestZOrderByteUtil.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/sort/zorder/TestZOrderByteUtil.java
@@ -94,6 +94,82 @@ public class TestZOrderByteUtil {
         return result.toString();
     }
 
+    /**
+     * Ordered-bytes transforms must preserve value order across negatives for 
floats and doubles.
+     */
+    @Test
+    public void testFloatDoubleNegativeOrdering() {
+        float[] floats = {
+            -Float.MAX_VALUE,
+            -2.5f,
+            -1f,
+            -Float.MIN_VALUE,
+            0f,
+            Float.MIN_VALUE,
+            1f,
+            2.5f,
+            Float.MAX_VALUE
+        };
+        long prevF =
+                ZOrderByteUtils.floatToOrderedBytes(floats[0], 
ByteBuffer.allocate(8)).getLong(0);
+        for (int i = 1; i < floats.length; i++) {
+            long cur =
+                    ZOrderByteUtils.floatToOrderedBytes(floats[i], 
ByteBuffer.allocate(8))
+                            .getLong(0);
+            assertThat(Long.compareUnsigned(prevF, cur)).isLessThan(0);
+            prevF = cur;
+        }
+
+        double[] doubles = {
+            -Double.MAX_VALUE,
+            -2.5d,
+            -1d,
+            -Double.MIN_VALUE,
+            0d,
+            Double.MIN_VALUE,
+            1d,
+            2.5d,
+            Double.MAX_VALUE
+        };
+        long prevD =
+                ZOrderByteUtils.doubleToOrderedBytes(doubles[0], 
ByteBuffer.allocate(8)).getLong(0);
+        for (int i = 1; i < doubles.length; i++) {
+            long cur =
+                    ZOrderByteUtils.doubleToOrderedBytes(doubles[i], 
ByteBuffer.allocate(8))
+                            .getLong(0);
+            assertThat(Long.compareUnsigned(prevD, cur)).isLessThan(0);
+            prevD = cur;
+        }
+
+        // Dense walk of adjacent negative bit patterns: value strictly 
decreases, so
+        // the transformed unsigned value must strictly decrease too. The old 
shift-31
+        // flip inverts order for a fraction of adjacent negative pairs.
+        for (int i = 1; i < 1000; i++) {
+            int bits = 0xC0400000 + i; // starting at -3.0f, descending values
+            long prev =
+                    ZOrderByteUtils.floatToOrderedBytes(
+                                    Float.intBitsToFloat(bits - 1), 
ByteBuffer.allocate(8))
+                            .getLong(0);
+            long cur =
+                    ZOrderByteUtils.floatToOrderedBytes(
+                                    Float.intBitsToFloat(bits), 
ByteBuffer.allocate(8))
+                            .getLong(0);
+            assertThat(Long.compareUnsigned(prev, cur)).isGreaterThan(0);
+        }
+        for (int i = 0; i < 1000; i++) {
+            long bits = 0xC004000000000000L + i; // just below -2.5d, 
descending values
+            double v = Double.longBitsToDouble(bits);
+            long cur = ZOrderByteUtils.doubleToOrderedBytes(v, 
ByteBuffer.allocate(8)).getLong(0);
+            if (i > 0) {
+                long prev =
+                        ZOrderByteUtils.doubleToOrderedBytes(
+                                        Double.longBitsToDouble(bits - 1), 
ByteBuffer.allocate(8))
+                                .getLong(0);
+                assertThat(Long.compareUnsigned(prev, cur)).isGreaterThan(0);
+            }
+        }
+    }
+
     /**
      * Compares the result of a string based interleaving algorithm 
implemented above versus the
      * binary bit-shifting algorithm used in ZOrderByteUtils. Either both 
algorithms are identically

Reply via email to