Skip to content

bug: a NaN with its sign bit set sorts below every number in filters #9315

Description

@jonasdedden

Description

Float filters order values by IEEE 754 total order, where a NaN with its sign bit set comes before -inf. SQL engines (PostgreSQL treats every NaN as equal and greater than any number), IEEE 754 comparisons, and dataframe libraries such as Polars don't order NaN that way. A negative NaN is not unusual either: on x86 it is the NaN produced by 0.0 / 0.0, inf - inf, sqrt(-1) and inf * 0 in numpy, pyarrow and Polars, and by negating a NaN.

Steps to reproduce

So the same logical value answers differently depending on its sign bit:

import math
import lance
import pyarrow as pa

neg_nan = math.copysign(math.nan, -1.0)  # what 0.0/0.0 and inf-inf produce on x86
data = pa.table({"id": [0, 1, 2, 3], "x": [neg_nan, math.nan, 1.0, 2.0]})
ds = lance.write_dataset(data, "nan.lance", mode="overwrite")

def ids(filter):
    return sorted(ds.to_table(columns=["id"], filter=filter)["id"].to_pylist())

print("x > 1.0      ", ids("x > 1.0"))
print("x < 1.0      ", ids("x < 1.0"))
print("isnan(x)     ", ids("isnan(x)"))
ds.create_scalar_index("x", "BTREE")
ds = lance.dataset("nan.lance")
print("x > 1.0 btree", ids("x > 1.0"))

On pylance 13.0.0b4 (same on 11.0.0 and 9.0.0):

x > 1.0       [1, 3]
x < 1.0       [0]
isnan(x)      [0, 1]
x > 1.0 btree [1, 3]

Rows 0 and 1 are both NaN (isnan agrees), yet x > 1.0 keeps only the positive one and x < 1.0 keeps the negative one. A column-to-column comparison is affected the same way: a negative and a positive NaN compare unequal, and the negative one compares less than any number. The BTREE index agrees with the scan, so the result is consistent across plans, just not what the data means.

Expected behavior

The sign of a NaN doesn't affect comparisons: x > 1.0 returns [1, 3] plus row 0, and x < 1.0 returns neither NaN.

Lance version

13.0.0b4

Language binding

Python

Notes

Activity

  1. jonasdedden commented on Sep 17, 2026

    @jonasdedden
    ContributorAuthor

    #9324 seems to solve it in a nice way.

    EDIT: Actually, I found some further simplifcations and an additional bugfix, why #9628 is a continuation of #9324.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions