I've used atan2 much more frequently than atan in my career, and ChatGPT confirms that it's much more frequent in graphics, robotics, physics engines, geographic mapping, and more. I propose that we add it to the standard library. For example, this could be Dec.atan2:
atan2 : { x: Dec, y: Dec } -> Dec
atan2 = |{ x, y }| {
if x > 0 {
(y / x).atan()
} else if x < 0 {
(y / x).atan() + if y > 0 { Dec.pi } else { -Dec.pi }
} else if y > 0 {
Dec.pi / 2
} else if y < 0 {
-Dec.pi / 2
} else {
0
}
}
Most languages define atan2(y, x) (i.e., with y as the first argument). I've been bitten by this surprising convention a few times, which is why I chose to use the record { x, y } to make it explicit.
The special case atan2({ x: 0, y: 0}) is mathematically undefined, but almost all implementations return 0 because that's what IEEE 754 recommends (according to ChatGPT).
For F64, the IEEE 754 standard actually defines special cases for the 4 combinations of +0.0 and -0.0:
* atan2(0.0, 0.0) == 0.0
* atan2(-0.0, 0.0) == -0.0
* atan2(0.0, -0.0) == F64.pi
* atan2(-0.0, -0.0) == -F64.pi
Same here, atan2 is so much more useful :)
that all makes sense to me! :thumbs_up:
Is the idea to implement atan2 in zig or pure Roc?
FWIW I have a Claude-written atan2 in my Roc code in a couple places, including https://github.com/showell/roc-apps/blob/master/canvas_apps/lib/Trig.roc.
I've got Astra implementing it in zig right now, the PR should be available within a few minutes.
It ran some performance tests and found that its Dec.atan2 implementation is about 2.1 times faster than a pure Roc implementation on my machine.
For Dec, it used a division-free integer CORDIC (i.e., it computes angles using a sequence of small rotations).
Here's PR #11565 which adds atan2 to Dec, F32, and F64.
@Aurélien Geron I agree about is_negative_zero, good call! :+1:
Aurélien Geron said:
Here's PR #11565 which adds
atan2toDec,F32, andF64.
@Aurélien Geron for any changes to the builtins I've been making fuzz-targets here https://github.com/lukewilliamboswell/roc-fuzz/tree/trunk/examples/builtins. Almost every PR we've found some gaps. Would you be interested in giving that a shot?
I'm not sure if it makes sense to fuzz atan2 though...
Sure, I can give it a shot. I've never done this so it will be a fun learning experience.
I asked Astra to tweak Dec.atan to use the same optimizations as Dec.atan2, sharing the same CORDIC implementation. It's almost done, and Dec.atan is now 1.5x faster for inputs greater than 1, because the new implementation avoids computing the reciprocal in this case.
Would you like me to include this change to the same PR or should it be a separate PR?
yeah I don't think we need to fuzz atan2 when we're just delegating to a well-tested implementation :smile:
11 messages were moved here from #beginners > Testing for -0.0 or +0.0 by Luke Boswell.
@Richard Feldman I've been surprised by the bugs we've caught in the zig implementation of other builtins though... I've found memory leaks, refcounting bugs, and other strange things in edgecases.
ok fair, but like...atan? :sweat_smile:
I mean it's fine haha
doesn't seem like a blocker to me, but fine to include
Yeah I don't know, sometimes there is nothing interesting. I just have been doing it for all of the builtins and most of them I've found strange things I would never have expected.
Last updated: Sep 24 2026 at 15:59 UTC