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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5af0f51cfe4 [FLINK-40693][table] Align floating-point `IN` predicates 
with equality for signed zero
5af0f51cfe4 is described below

commit 5af0f51cfe4ba089f4df28852d30b67e0ca2127d
Author: ParyshevSergey <[email protected]>
AuthorDate: Mon Sep 28 13:48:56 2026 +0700

    [FLINK-40693][table] Align floating-point `IN` predicates with equality for 
signed zero
---
 .../planner/expressions/ScalarOperatorsTest.scala  | 35 +++++++++++++
 .../planner/runtime/batch/sql/CalcITCase.scala     | 46 ++++++++++++++++
 .../planner/runtime/stream/sql/CalcITCase.scala    | 61 +++++++++++++++++++++-
 .../runtime/util/collections/DoubleHashSet.java    |  8 +--
 .../runtime/util/collections/FloatHashSet.java     |  8 +--
 5 files changed, 149 insertions(+), 9 deletions(-)

diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarOperatorsTest.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarOperatorsTest.scala
index 44fac362f8f..5d22b4e757c 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarOperatorsTest.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarOperatorsTest.scala
@@ -21,6 +21,8 @@ import org.apache.flink.table.api.{DataTypes, 
LiteralStringExpression, Unresolve
 import org.apache.flink.table.planner.expressions.utils.ScalarOperatorsTestBase
 
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
 
 class ScalarOperatorsTest extends ScalarOperatorsTestBase {
 
@@ -62,6 +64,39 @@ class ScalarOperatorsTest extends ScalarOperatorsTestBase {
     )
   }
 
+  @ParameterizedTest(name = "{0}")
+  @ValueSource(strings = Array("FLOAT", "DOUBLE"))
+  def testInWithSignedZero(numericType: String): Unit = {
+    // Negate the runtime zero so constant folding cannot erase its sign.
+    val zero = s"CAST(f7 AS $numericType)"
+    val negativeZero = s"CAST(-f7 AS $numericType)"
+    val pair = s"CAST(0 AS $numericType), CAST(2 AS $numericType)"
+    val negativePair = s"CAST('-0.0' AS $numericType), CAST(2 AS $numericType)"
+    val longList = (Seq(0) ++ (2 to 21)).mkString(", ")
+
+    testSqlApi(s"$negativeZero = $zero", "TRUE")
+    testSqlApi(s"$negativeZero IN (0)", "TRUE")
+    testSqlApi(s"$negativeZero IN ($pair)", "TRUE")
+    testSqlApi(s"$negativeZero NOT IN ($pair)", "FALSE")
+    testSqlApi(s"$zero IN ($negativePair)", "TRUE")
+    testSqlApi(s"$zero NOT IN ($negativePair)", "FALSE")
+    testSqlApi(s"$negativeZero IN (CAST(0 AS $numericType), $negativePair)", 
"TRUE")
+    testSqlApi(s"$negativeZero IN ($longList)", "TRUE")
+    testSqlApi(s"$negativeZero NOT IN ($longList)", "FALSE")
+    testSqlApi(s"$negativeZero IN ($pair, NULL)", "TRUE")
+    testSqlApi(s"$negativeZero NOT IN ($pair, NULL)", "FALSE")
+    testSqlApi(s"$negativeZero IN (1, 2)", "FALSE")
+    testSqlApi(s"$negativeZero NOT IN (1, 2)", "TRUE")
+    testSqlApi(s"$negativeZero IN (1, 2, NULL)", "NULL")
+    testSqlApi(s"$negativeZero NOT IN (1, 2, NULL)", "NULL")
+    testSqlApi(s"CAST(f4 AS $numericType) IN ($pair)", "FALSE")
+    testSqlApi(s"CAST(f4 AS $numericType) NOT IN ($pair)", "TRUE")
+    testSqlApi(s"CAST(f14 AS $numericType) IN ($pair)", "NULL")
+    testSqlApi(s"CAST(f14 AS $numericType) NOT IN ($pair)", "NULL")
+    testSqlApi(s"(CAST(f14 AS $numericType) IN ($pair)) IS TRUE", "FALSE")
+    testSqlApi(s"(CAST(f14 AS $numericType) IN ($pair)) IS NOT FALSE", "TRUE")
+  }
+
   @Test
   def testCompareOperator(): Unit = {
 
diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
index 1ffedf2b0ae..909a66ea90a 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
@@ -1706,6 +1706,52 @@ class CalcITCase extends BatchTestBase {
     )
   }
 
+  @Test
+  def testFloatingPointInWithSignedZero(): Unit = {
+    registerCollection(
+      "SignedZeros",
+      Seq(
+        row(1, -0.0f, -0.0d),
+        row(1, -0.0f, -0.0d),
+        row(2, 0.0f, 0.0d),
+        row(3, 1.0f, 1.0d),
+        row(4, null, null),
+        row(5, 2.0f, 2.0d),
+        row(6, Float.NaN, Double.NaN),
+        row(7, Float.PositiveInfinity, Double.PositiveInfinity),
+        row(8, Float.NegativeInfinity, Double.NegativeInfinity)
+      ),
+      new RowTypeInfo(Types.INT, Types.FLOAT, Types.DOUBLE),
+      "id, f, d"
+    )
+
+    checkResult(
+      "SELECT id FROM SignedZeros WHERE f IN (0, 2) AND d IN (0, 2)",
+      Seq(row(1), row(1), row(2), row(5)))
+
+    for (field <- Seq("f", "d")) {
+      checkResult(
+        s"""
+           |SELECT id, $field = 0,
+           |  $field IN (0, 2), $field NOT IN (0, 2),
+           |  $field IN (0, 2, NULL), $field NOT IN (0, 2, NULL)
+           |FROM SignedZeros
+           |""".stripMargin,
+        Seq(
+          row(1, true, true, false, true, false),
+          row(1, true, true, false, true, false),
+          row(2, true, true, false, true, false),
+          row(3, false, false, true, null, null),
+          row(4, null, null, null, null, null),
+          row(5, false, true, false, true, false),
+          row(6, false, false, true, null, null),
+          row(7, false, false, true, null, null),
+          row(8, false, false, true, null, null)
+        )
+      )
+    }
+  }
+
   @Test
   def testSearch(): Unit = {
     val myTableDataId = TestValuesTableFactory.registerData(
diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/CalcITCase.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/CalcITCase.scala
index a8c90fe77b8..f885f582a28 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/CalcITCase.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/runtime/stream/sql/CalcITCase.scala
@@ -39,7 +39,7 @@ import org.apache.flink.test.util.TestBaseUtils
 import org.apache.flink.types.Row
 import org.apache.flink.util.CollectionUtil
 
-import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
+import org.assertj.core.api.Assertions.{assertThat, assertThatList, 
assertThatThrownBy}
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.RegisterExtension
 
@@ -348,6 +348,65 @@ class CalcITCase extends StreamingTestBase {
     assertThat(sink.getAppendResults.sorted).isEqualTo(expected.sorted)
   }
 
+  @Test
+  def testFloatingPointInWithSignedZero(): Unit = {
+    val dataId = TestValuesTableFactory.registerData(
+      Seq(
+        row(1, -0.0f, -0.0d),
+        row(1, -0.0f, -0.0d),
+        row(2, 0.0f, 0.0d),
+        row(3, 1.0f, 1.0d),
+        row(4, null, null),
+        row(5, 2.0f, 2.0d),
+        row(6, Float.NaN, Double.NaN),
+        row(7, Float.PositiveInfinity, Double.PositiveInfinity),
+        row(8, Float.NegativeInfinity, Double.NegativeInfinity)
+      ))
+    tEnv.executeSql(s"""
+                       |CREATE TABLE SignedZeros (id INT, f FLOAT, d DOUBLE) 
WITH (
+                       |  'connector' = 'values',
+                       |  'data-id' = '$dataId',
+                       |  'bounded' = 'true'
+                       |)
+                       |""".stripMargin)
+
+    val filtered = tEnv
+      .executeSql("SELECT id FROM SignedZeros WHERE f IN (0, 2) AND d IN (0, 
2)")
+      .collect()
+    try {
+      assertThatList(CollectionUtil.iteratorToList(filtered))
+        .containsExactlyInAnyOrder(row(1), row(1), row(2), row(5))
+    } finally {
+      filtered.close()
+    }
+
+    for (field <- Seq("f", "d")) {
+      val projected = tEnv
+        .executeSql(s"""
+                       |SELECT id, $field = 0,
+                       |  $field IN (0, 2), $field NOT IN (0, 2),
+                       |  $field IN (0, 2, NULL), $field NOT IN (0, 2, NULL)
+                       |FROM SignedZeros
+                       |""".stripMargin)
+        .collect()
+      try {
+        
assertThatList(CollectionUtil.iteratorToList(projected)).containsExactlyInAnyOrder(
+          row(1, true, true, false, true, false),
+          row(1, true, true, false, true, false),
+          row(2, true, true, false, true, false),
+          row(3, false, false, true, null, null),
+          row(4, null, null, null, null, null),
+          row(5, false, true, false, true, false),
+          row(6, false, false, true, null, null),
+          row(7, false, false, true, null, null),
+          row(8, false, false, true, null, null)
+        )
+      } finally {
+        projected.close()
+      }
+    }
+  }
+
   @Test
   def testLongProjectionList(): Unit = {
 
diff --git 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/DoubleHashSet.java
 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/DoubleHashSet.java
index bce2d931b5f..d32931a0bc9 100644
--- 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/DoubleHashSet.java
+++ 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/DoubleHashSet.java
@@ -37,9 +37,9 @@ public class DoubleHashSet extends OptimizableHashSet {
         this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR);
     }
 
-    /** See {@link Double#equals(Object)}. */
+    /** See {@link Double#equals(Object)}, except that both signs of zero are 
considered equal. */
     public boolean add(final double k) {
-        long longKey = Double.doubleToLongBits(k);
+        final long longKey = k == 0.0d ? 0L : Double.doubleToLongBits(k);
         if (longKey == 0L) {
             if (this.containsZero) {
                 return false;
@@ -75,9 +75,9 @@ public class DoubleHashSet extends OptimizableHashSet {
         return true;
     }
 
-    /** See {@link Double#equals(Object)}. */
+    /** See {@link Double#equals(Object)}, except that both signs of zero are 
considered equal. */
     public boolean contains(final double k) {
-        long longKey = Double.doubleToLongBits(k);
+        final long longKey = k == 0.0d ? 0L : Double.doubleToLongBits(k);
         if (longKey == 0L) {
             return this.containsZero;
         } else {
diff --git 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/FloatHashSet.java
 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/FloatHashSet.java
index 4f21c5859d8..b5634abc1ed 100644
--- 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/FloatHashSet.java
+++ 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/util/collections/FloatHashSet.java
@@ -37,9 +37,9 @@ public class FloatHashSet extends OptimizableHashSet {
         this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR);
     }
 
-    /** See {@link Float#equals(Object)}. */
+    /** See {@link Float#equals(Object)}, except that both signs of zero are 
considered equal. */
     public boolean add(final float k) {
-        int intKey = Float.floatToIntBits(k);
+        final int intKey = k == 0.0f ? 0 : Float.floatToIntBits(k);
         if (intKey == 0) {
             if (this.containsZero) {
                 return false;
@@ -73,9 +73,9 @@ public class FloatHashSet extends OptimizableHashSet {
         return true;
     }
 
-    /** See {@link Float#equals(Object)}. */
+    /** See {@link Float#equals(Object)}, except that both signs of zero are 
considered equal. */
     public boolean contains(final float k) {
-        int intKey = Float.floatToIntBits(k);
+        final int intKey = k == 0.0f ? 0 : Float.floatToIntBits(k);
         if (intKey == 0) {
             return this.containsZero;
         } else {

Reply via email to