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