Skip to content

floor division truncates toward zero on duckdb (and 0//0 gives 0 on pandas) while every other backend floors #3984

Closed

Description

@ptimizeroracle

describe the bug

// (floor division) truncates toward zero on duckdb but floors everywhere else, so the same expression returns different numbers for negative operands.

import duckdb, pandas as pd
import narwhals as nw

vals = pd.DataFrame({"a": [-7, 7, -1, 0, None], "b": [2, 2, 3, 0, 2]})

# duckdb: -3, 3, 0, null, null  <- truncates toward zero
# polars / pandas / pyarrow / sqlframe: -4, 3, -1, ?, null  <- floors
a // b polars pandas pyarrow duckdb sqlframe
-7 // 2 -4 -4 -4 -3 -4
-1 // 3 -1 -1 -1 0 -1
0 // 0 null 0 (numpy path) null null null

two divergences in one operator: duckdb truncates instead of flooring, and the pandas backend returns 0 for 0 // 0 (numpy semantics) where the rest return null.

this is the sibling of the floor-modulus work in #3929: % was floor-corrected across backends, but // on duckdb still follows the engine's native // which truncates. src/narwhals/_sql/expr.py maps floordiv straight through with only a divide-by-zero guard (_when(other != 0, ...)).

since duckdb's mod already returns the floor-consistent remainder (verified: -7 % 2 = 1, matching polars), the identity floor_div(a, b) = trunc_div(a - floor_mod(a, b), b) should give exact floor semantics without float round-trips: -7 - 1 = -8, trunc(-8 / 2) = -4.

expected behavior

duckdb // should floor like every other backend (and like % already does post-#3929).

Activity

  1. ReguiguiMohamed commented on Sep 27, 2026

    @ReguiguiMohamed
    Contributor

    I've started working on this. I'll link a PR.

  2. ptimizeroracle commented on Sep 27, 2026

    @ptimizeroracle
    ContributorAuthor

    nice, thanks

    two things that may help:

    • narwhals already has floor_mod in _utils.py (landed with fix: align __mod__ and __rmod__ behavior with negative operands across backends #3929), so the duckdb/sql side can reuse it for the correction term: floor_div(a, b) = (a - floor_mod(a, b)) / b, with the existing truncating division doing the rest
    • for reference, ibis normalizes this exact case by compiling floor division differently for duckdb: their // returns -4 where raw duckdb // returns -3, might be worth a look at how they emit it

    the pandas 0//0 == 0 half lives in a separate site (pandas-like backend). happy to run any patch through the cross-backend grid used for the original report

  3. ReguiguiMohamed commented on Sep 27, 2026

    @ReguiguiMohamed
    Contributor

    nice, thanks

    two things that may help:

    • narwhals already has floor_mod in _utils.py (landed with fix: align __mod__ and __rmod__ behavior with negative operands across backends #3929), so the duckdb/sql side can reuse it for the correction term: floor_div(a, b) = (a - floor_mod(a, b)) / b, with the existing truncating division doing the rest
    • for reference, ibis normalizes this exact case by compiling floor division differently for duckdb: their // returns -4 where raw duckdb // returns -3, might be worth a look at how they emit it

    the pandas 0//0 == 0 half lives in a separate site (pandas-like backend). happy to run any patch through the cross-backend grid used for the original report

    thanks! PR is up in #3994

    I tried the floor_mod route first but (a % b) + b overflows near int64 max, e.g. 10 // (2**63-1), so the fix corrects the truncated quotient instead.

    ibis emits FLOOR(a / b) which goes through float64, so it loses precision on big ints: (2**62+1) // -3 gives ...216 instead of ...302.

    would be great if you ran it through your grid

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions