You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] array_distinct / array_union / array_except do not canonicalize NaN like Spark #4481
Spark's array_distinct, array_union, and array_except use SQLOpenHashSet.withNaNCheckFunc to canonicalize NaN equality: every NaN bit pattern collapses to a single bucket, and +0.0 / -0.0 are treated as distinct. DataFusion's array_distinct, array_union, and array_except use hash-based set logic where NaN is not equal to NaN (IEEE semantics) and +0.0 == -0.0.
So for Float/Double element arrays containing NaN or signed zero, the results diverge silently:
array_distinct(array(double('NaN'), double('NaN'))) returns one element in Spark, two in Comet.
array_union(array(0.0), array(-0.0)) returns one element in Spark, two in Comet.
Surfaced by the array-expressions audit (collection PR queue).
Either canonicalize NaN/signed-zero on the Comet side before invoking the DataFusion set operations, or downgrade array_distinct / array_union / array_except to Incompatible(Some(...)) whenever the element type is FloatType or DoubleType. (array_intersect already has a related Incompatible flag for ordering; the NaN gap is separate.)
Additional context
Comet serdes: CometScalarFunction("array_distinct") (registered in QueryPlanSerde.scala), CometArrayUnion and CometArrayExcept in spark/src/main/scala/org/apache/comet/serde/arrays.scala.
Spark reference: SQLOpenHashSet.withNaNCheckFunc and ArraySetLike in collectionOperations.scala.
Related: array_intersect carries an ordering Incompatible flag but does not document the NaN gap either.
DF follows DuckDb concepts which returns NaN, NaN, however I would suppose this is a DuckDB bug
D select array_distinct(array['nan'::float, 'nan'::float]);
┌─────────────────────────────────────────────────────────────────────┐
│ array_distinct((ARRAY[CAST('nan' AS FLOAT), CAST('nan' AS FLOAT)])) │
│ float[] │
├─────────────────────────────────────────────────────────────────────┤
│ [nan, nan] │
└─────────────────────────────────────────────────────────────────────┘
However checking distincts without arrays give correct result, and arrays are not consistent, which IMO is a bug in duckDB and DF should follow more Spark semantics
D select 'nan'::float union select 'nan'::float;
┌──────────────────────┐
│ CAST('nan' AS FLOAT) │
│ float │
├──────────────────────┤
│ nan │
└──────────────────────┘
D select distinct a from (select 'nan'::float a union all select 'nan'::float);
┌───────┐
│ a │
│ float │
├───────┤
│ nan │
└───────┘
D
Activity
comphead commentedon Jun 3, 2026
DF follows DuckDb concepts which returns
NaN, NaN, however I would suppose this is a DuckDB bugHowever checking distincts without arrays give correct result, and arrays are not consistent, which IMO is a bug in duckDB and DF should follow more Spark semantics
comphead commentedon Jun 5, 2026
DF resolves NaN correctly so its more Comet trouble
comphead commentedon Jun 5, 2026
-0, +0 comparison is confirmed to be DF problem