Keep the sign in integer powers of negative numbers - #3301
Merged
maleadt merged 2 commits intoSep 28, 2026
Merged
Conversation
`@fastmath x^n` with an integer `n` outside -1:3 was computed as `__nv_fast_powf(x, Float32(n))`, that is exp2(n * log2(x)), which is NaN for x < 0: `(-0.5f0)^7` gave NaN where the CPU gives -0.0078125. This also affected literal exponents, which reach the same method through `pow_fast(x, ::Val)`. Float64 goes through `__nv_pow` and was not affected. Take the power of `abs(x)` and copy the sign of `x` for odd `n`, which is how LLVM's AMDGPU back-end expands a fast `pown`. The parity comes from the integer exponent, so an odd exponent that Float32 rounds to an even number (16_777_217) keeps its sign. The Float16 method now calls the Float32 one with the integer exponent, so it gets the same handling. Found while differentiating WaterLily.jl kernels with Enzyme on a GTX 1080. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`^(::Float32, ::Integer)` converts the exponent and calls `__nv_powf`, which handles negative bases, but the conversion can round an odd exponent to an even one: `(-1f0)^16_777_217` gave 1. The same holds for Float64 beyond 2^53. Take the sign from the integer exponent, as the fast variant now does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
maleadt
force-pushed
the
pr/fastmath-pow-negative-base
branch
from
September 28, 2026 16:27
8f0df95 to
d284892
Compare
Member
|
LGTM, thanks! I also applied this to the non-fastmath |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3301 +/- ##
=======================================
Coverage 85.89% 85.90%
=======================================
Files 187 187
Lines 19022 19025 +3
=======================================
+ Hits 16339 16343 +4
+ Misses 2683 2682 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On the device,
@fastmath x^nwithx::Float32orFloat16and an integernoutside -1:3 returned NaN for negativex. Literal exponents were affected too, since@fastmath x^7goes throughpow_fast(x, Val(7))and then the same method.The override from #3098 calls
__nv_fast_powf(x, Float32(n)), which computesexp2(n * log2(x)). This PR computes the power ofabs(x)and copies the sign ofxwhennis odd, the same way LLVM's AMDGPU back-end expands a fastpown(AMDGPULibCalls.cpp). The parity comes from the integern, becauseFloat32(n)can round an odd exponent to an even one (16_777_217). With a literal exponent the sign handling compiles to a singlecopysign.f32, and positive bases give the same results as before.The precise
^(::Float32/Float64, ::Integer)from #3218 had the same rounding problem:(-1f0)^16_777_217returned1. The second commit takes the sign from the integer exponent there as well.Alternatives considered:
__nv_powif(what CUDA C'spow(float, int)uses, and the equivalent of the CPU'sllvm.powi) matches the CPU@fastmathresult bit for bit and is more accurate (for example 0 against 5 ulp on(-0.5f0)^7, and 153 against 1726 ulp on1.001f0^5000). But with an exponent that is only known at run time it is 1.6× slower forn = 7and 5× slower forn = 1000(RTX 5080), sopow_fastkeeps the fast path.