Skip to content

Send out of range trig arguments to libm - #4157

Merged
zcbenz merged 1 commit into
ml-explore:mainfrom
ayaangazali:fix-trig-large-args
Aug 11, 2026
Merged

Send out of range trig arguments to libm#4157
zcbenz merged 1 commit into
ml-explore:mainfrom
ayaangazali:fix-trig-large-args

Conversation

@ayaangazali

Copy link
Copy Markdown
Contributor

Proposed changes

mx.sin and mx.cos return values outside [-1, 1] for large finite inputs on the CPU:

>>> mx.sin(mx.array([1e9], dtype=mx.float32)).item()
13.287261009216309
>>> mx.sin(mx.array([1e10], dtype=mx.float32)).item()
-inf
>>> mx.cos(mx.array([1e10], dtype=mx.float32)).item()
inf

numpy and pytorch both return the right answer for all of these (-0.48750603 for sin(1e10)).

The cause is the Cephes range reduction in sincos:

auto y = x * 1.27323954473516f;   // x * 4/pi
Simd<uint32_t, N> emm2 = y;       // rounds y to an integer

y is a float, so it stops being an exact integer above 2^24, and the conversion overflows uint32 above 2^32. The extended precision correction that follows subtracts y * DP1..DP3 from x, so once y is wrong the reduced argument is wrong, and past the overflow it is garbage. Degradation starts around 1e8, becomes gross at 1e9, and reaches inf at 1e10.

Arguments that the reduction cannot represent exactly now go to libm instead. 2^23 is the cutoff, which keeps x * 4/pi below 2^24 with room to spare:

>>> [mx.sin(mx.array([x], dtype=mx.float32)).item() for x in (1e9, 1e10, 1e20, 1e30)]
[0.5458434820175171, -0.48750603199005127, 0.6565766930580139, -0.7911634445190430]

Those match np.sin exactly. Across 2000 values drawn from [-1e12, 1e12] plus ±1e30 and ±3.4e38, the maximum difference from numpy is now 0 for both sin and cos, nothing is NaN, and nan/±inf inputs still give nan as before.

The fast path is unchanged for ordinary arguments, since the branch is one any over the block. 4M float32 in [-10, 10], 20 iterations of each op, stayed at 0.054 s for sin and 0.053 s for cos, and accuracy there is unchanged at 6e-8 against numpy.

Checklist

Put an x in the boxes that apply.

  • I have read the CONTRIBUTING document
  • I have run pre-commit run --all-files to format my code / installed pre-commit prior to committing changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the necessary documentation (if needed)

CPU only build (MLX_BUILD_METAL=OFF) at e78d894, test_ops.py 150 tests pass and the new assertion fails on main. test_autograd.py and test_nn.py also pass. I only touched the CPU path, so the Metal and CUDA kernels are untouched.


freshman contributor, i work through these alongside Claude Code. found it by running every unary op over a fixed set of awkward values and diffing against both numpy and pytorch rather than numpy alone, which is what made this one stand out: the two references agreed with each other and not with us.

@zcbenz zcbenz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@zcbenz
zcbenz merged commit 64d392d into ml-explore:main Aug 11, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants