Skip to content

[BUG] mx.divmod for floats truncates the quotient — disagrees with mx.floor_divide and breaks q·b+r == a #4119

Description

@JasonHonKL

Describe the bug

This bug is found from property based testing.

For floating-point inputs, mx.divmod(a, b) returns a quotient that truncates
toward zero
, while mx.floor_divide(a, b) correctly floors toward −∞
(matching NumPy). So for any a/b < 0 the two operations return different
quotients, and divmod's (q, r) no longer reconstruct the dividend:

q * b + r == a      # the universal divmod invariant — BROKEN for floats

To Reproduce

import mlx.core as mx
import numpy as np

a, b = np.float32(-7.0), np.float32(2.0)
np.array(mx.floor_divide(mx.array(a), mx.array(b)))   # -4.0  (correct, floors)
q, r = mx.divmod(mx.array(a), mx.array(b))
np.array(q), np.array(r)                                # (-3.0, 1.0)  WRONG
# reconstruction:
(-3.0) * 2.0 + 1.0                                      # -5.0  != -7.0

Expected behavior

>>> a, b = np.float32(-7.0), np.float32(2.0)
>>> q, r = mx.divmod(mx.array(a), mx.array(b))
>>> np.array(q).item(), np.array(r).item()
(-3.0, 1.0)            # actual — wrong
>>> # expected (NumPy parity, and consistency with mx.floor_divide):
>>> np.floor_divide(a, b), np.remainder(a, b)
(-4.0, 1.0)            # q=-4, r=1; check: -4*2 + 1 = -7 ✓
>>> np.array(mx.floor_divide(mx.array(a), mx.array(b))).item()
-4.0                   # mx.floor_divide is already correct for floats

FYI

      a/b  floor_div  divmod_q  divmod_r    q*b+r   want  np_floor
-7.0/ 2.0      -4.00     -3.00      1.00    -5.00   -7.0     -4.00  <- BUG
 7.0/-2.0      -4.00     -3.00     -1.00     5.00    7.0     -4.00  <- BUG
-5.5/ 2.0      -3.00     -2.00      0.50    -3.50   -5.5     -3.00  <- BUG
 5.5/-2.0      -3.00     -2.00     -0.50     3.50    5.5     -3.00  <- BUG
-9.0/ 4.0      -3.00     -2.00      3.00    -5.00   -9.0     -3.00  <- BUG

floor_divide(-7.0,2.0) = -4.0   divmod(-7.0,2.0).q = -3.0   agree? False

Activity

  1. added a commit that references this issue on Aug 16, 2026
    3fca847
  2. krrishapatel commented on Aug 16, 2026

    @krrishapatel

    Reproduced on 0.32.0 and on main (a1e0e0b).

    Two things not in the report:

    1. Integers are affected too. mx.divmod(-7, 2) returns (-3, 1) on GPU and (-3, -1) on CPU. numpy gives (-4, 1).
    2. So CPU and GPU return different remainders for the same input.

    Cause: DivMod pairs a truncating quotient with a floored remainder. In binary_ops.h, FloorDivide does trunc(x/y) while Remainder floors. The CPU path uses std::trunc and std::fmod, which is self consistent but does not match numpy.

    test_divmod misses this because it only uses uniform(1, 100) and only checks out[0].

    I have a fix locally: q = floor(x/y), r = x - q*y, plus tests with negative cases. FloorDivide is only used by DivMod, so nothing else changes.

    I do not have a Metal compiler on this machine, so I verified the CPU backend only. Want me to open a PR?

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions