[ 
https://issues.apache.org/jira/browse/FLINK-40693?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated FLINK-40693:
-----------------------------------
    Labels: pull-request-available  (was: )

> FLOAT/DOUBLE IN and NOT IN disagree with equality for signed zero
> -----------------------------------------------------------------
>
>                 Key: FLINK-40693
>                 URL: https://issues.apache.org/jira/browse/FLINK-40693
>             Project: Flink
>          Issue Type: Bug
>          Components: Table SQL / Planner
>    Affects Versions: 1.20.1, 2.3.0, 2.2.1, 2.1.3
>         Environment: OpenJDK 21.0.12, Linux amd64, Maven 3.9.16. Batch and 
> streaming execution, parallelism 1
>            Reporter: Sergey Paryshev
>            Priority: Major
>              Labels: pull-request-available
>
> SQL equality and constant-list IN predicates produce different results for 
> FLOAT and DOUBLE signed zero. A singleton IN predicate behaves like equality, 
> whereas an IN predicate using the generated hash-set implementation can 
> reject an
>   equal value with the opposite zero sign.
>   This reproduces with a typed in-memory DataStream, without external 
> connectors or file formats.
>   Steps to reproduce
>   Run the following as a Flink Table/DataStream application with the planner 
> available:
> {code:java}
>   package org.apache.flink.examples.table;
>   import org.apache.flink.api.common.RuntimeExecutionMode;
>   import org.apache.flink.api.common.typeinfo.Types;
>   import 
> org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
>   import org.apache.flink.table.api.EnvironmentSettings;
>   import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
>   import org.apache.flink.types.Row;
>   import org.apache.flink.util.CloseableIterator;
>   import java.util.ArrayList;
>   import java.util.Arrays;
>   import java.util.Collections;
>   import java.util.List;
>   public class SignedZeroIn {
>       public static void main(String[] args) throws Exception {
>           final List<Row> rows = Arrays.asList(
>                   Row.of(1, -0.0f, -0.0d),
>                   Row.of(1, -0.0f, -0.0d),
>                   Row.of(2, 0.0f, 0.0d),
>                   Row.of(3, 1.0f, 1.0d),
>                   Row.of(4, null, null),
>                   Row.of(5, 22.0f, 22.0d),
>                   Row.of(6, Float.NaN, Double.NaN),
>                   Row.of(7, Float.POSITIVE_INFINITY, 
> Double.POSITIVE_INFINITY),
>                   Row.of(8, Float.NEGATIVE_INFINITY, 
> Double.NEGATIVE_INFINITY));
>           if (Float.floatToRawIntBits((Float) rows.get(0).getField(1)) != 
> 0x80000000
>                   || Double.doubleToRawLongBits((Double) 
> rows.get(0).getField(2))
>                           != 0x8000000000000000L) {
>               throw new AssertionError("The source must contain negative 
> zero");
>           }
>           final StreamExecutionEnvironment env =
>                   StreamExecutionEnvironment.getExecutionEnvironment();
>           env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
>           env.setParallelism(1);
>           final StreamTableEnvironment tables =
>                   StreamTableEnvironment.create(env, 
> EnvironmentSettings.inStreamingMode());
>           tables.createTemporaryView(
>                   "T",
>                   tables.fromDataStream(
>                           env.fromCollection(
>                                   rows,
>                                   Types.ROW_NAMED(
>                                           new String[] {"id", "f", "d"},
>                                           Types.INT, Types.FLOAT, 
> Types.DOUBLE))));
>           for (String type : Arrays.asList("FLOAT", "DOUBLE")) {
>               final String field = type.equals("FLOAT") ? "f" : "d";
>               final String query = "SELECT id FROM T WHERE " + field;
>               final String zero = "CAST(0 AS " + type + ")";
>               System.out.println(type + " equality: "
>                       + ids(tables, query + " = " + zero));
>               System.out.println(type + " singleton IN: "
>                       + ids(tables, query + " IN (" + zero + ")"));
>               System.out.println(type + " two-element IN: "
>                       + ids(tables, query + " IN (" + zero
>                               + ", CAST(2 AS " + type + "))"));
>           }
>       }
>       private static List<Integer> ids(StreamTableEnvironment tables, String 
> sql)
>               throws Exception {
>           final List<Integer> result = new ArrayList<>();
>           try (CloseableIterator<Row> rows = 
> tables.executeSql(sql).collect()) {
>               rows.forEachRemaining(row -> result.add((Integer) 
> row.getField(0)));
>           }
>           Collections.sort(result);
>           return result;
>       }
>   }
>  {code}
>   Expected result
>   All three predicates return the ID multiset [1, 1, 2] for both types.
>   Actual result
>   Equality and singleton IN return [1, 1, 2].
>   The two-element IN returns [2].
>   Execution-path verification
>   The regression tests inspect generated code and verify 
> FloatHashSet/DoubleHashSet membership for the two-element list and a 
> 21-element distinct list. Singleton IN uses the equality path.
>   SearchOperatorGen selects hash sets for point or complemented-point Sargs, 
> without a list-size threshold at that stage. The separate threshold of 20 in 
> ConvertToNotInOrInRule is not a general SQL IN threshold.
>   Root cause
>   Generated numeric equality uses primitive ==. FloatHashSet and 
> DoubleHashSet intentionally implement boxed equality, which distinguishes 
> positive and negative zero. SQL SEARCH currently uses these sets without 
> adapting their zero
>   equivalence.
>   Complemented SEARCH also affects NOT IN. NULL handling must remain 
> three-valued: NULL input yields UNKNOWN, and a nonmatching value with NULL in 
> the list yields UNKNOWN.
>   The public documentation does not explicitly specify signed-zero equality. 
> This fix follows Flink's existing generated numeric equality and preserves 
> equivalence across these execution paths.
>   Proposed fix
>   When preparing a floating-point SEARCH constant set, include both zero 
> representations whenever either zero is present, after casting to the common 
> type. Preserve both representations during code generation.
>   Related work
>   FLINK-35276 and PR #28383 address negative-zero sorting and remain open; 
> they do not fix SQL membership. Searches did not identify an exact duplicate.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to