Repository navigation
Description
Is this a duplicate?
- I confirmed there appear to be no duplicate issues for this bug and that I agree to the Code of Conduct
Type of Bug
Silent Failure
Component
libcu++
Describe the bug
Many cuda::fp32mp2 and cuda::fp64mp2 math functions return wrong results for special inputs: signed zeros, infinities, NaN, arguments outside the domain, subnormals and ±max. Often the result is a plausible finite number where C99 Annex F (and CUDA's own double functions) give NaN or ±inf, so nothing signals the error.
A probe of 50 unary functions (plus sincos and sincospi) at 19 special inputs finds 198 wrong results in 63 (type, function) pairs. It covers both types, on the host and on the device. All of them are on current main (89e1909). The open fpmp PRs #12519, #12530 and #12543 leave every one unchanged. pow is fixed separately in #12548.
Finite results where NaN or ±inf is due. These are the most serious, because nothing looks wrong:
fp32mp2log,log2andlog10ignore the sign, and do not handle 0, inf or NaN:- log(x) for x < 0 returns log|x|, e.g. log(−2) = 0.693.
- log(±0) = −104.7 (ln 2⁻¹⁵¹), log(+inf) = 88.72 and log(NaN) = 89.13.
- log2(±0) = −151 and log2(+inf) = 128; log10 behaves the same way.
fp32mp2normcdfinvreturns ±inf instead of NaN outside [0, 1], e.g. normcdfinv(2) = +inf.fp64mp2boys_f0returns 1 for every x < 1e-15, including x < 0 and −inf (F0(−1) = 1.4627).
NaN where ±inf or a finite value is due:
fp32mp2sinh and cosh at ±inf and ±FLT_MAX.fp32mp2atan(±inf), which should be ±π/2.fp32mp2boys_f0 at +inf (should be 0), and at −inf and −FLT_MAX(should be +inf).- For both types: sqrt(+inf); rsqrt(±0), which should be ±inf; and rsqrt(+inf), which should be +0.
- For both types: ceil, floor, trunc, round, rint and nearbyint of ±inf.
Subnormal inputs, on the device only:
fp32mp2sqrt and rsqrt return NaN for subnormal inputs; for example sqrt(2⁻¹⁴⁹) should be 3.74e-23. The host is correct.fp64mp2logb of a subnormal returns INT_MIN instead of −1074 … −1023.
Signed zero. Odd functions return +0 for −0:
- Both types: sin (and sincos), tan, asin, atan, sinh, tanh, erf, expm1, log1p, sqrt and round. On the device this also applies to the placeholders erfinv, j1, cyl_bessel_i1, rint and nearbyint, and tgamma(−0) returns +inf instead of −inf.
fp32mp2only: trunc does the same for −0.5 and for negative subnormals. On the device, ceil does too, and floor(−0) returns +0.fp64mp2only: asinh, atanh, cbrt and sinpi (and sincospi) return +0, and rcbrt(−0) returns +inf instead of −inf. On the device, normcdfinv(0.5) returns −0.
Host and device disagree:
fp32mp2cospi(±0.5) returns −0 on the host.- On the host,
fp64mp2sinpi and cospi return sin(πx) rounded indouble: sinpi(1) = 1.2e-16, cospi(0.5) = 6.1e-17, and NaN at ±DBL_MAX. The device is exact. - On the host,
fp64mp2normcdfinv returns ±inf outside [0, 1], and −inf for subnormal inputs.
The specification (docs/libcudacxx/extended_api/fp/fpmp_spec.rst) has 42 unary "Special Values Table"s, and all of them list the input as the result. For example, they give log(−1) = −1, sqrt(−1) = −1, exp(−INF) = −inf and sinh(+maxN) = +maxN, so they document neither the current behavior nor the correct one.
Not covered here:
- two-argument functions other than
pow; - arithmetic at the
midandhighlevels, where an infinite operand gives NaN (−inf + (−1), −inf · (−1)). That arithmetic is a separate design question.
All 198 failing cases
The method:
- The inputs are ±inf, ±max, ±2, ±1, ±0.5, ±min (smallest normal), ±largest subnormal, ±smallest subnormal, ±0 and NaN, each with a zero low limb.
- The reference is CUDA's
doublefunction on the same input, evaluated on the device and rounded tofloatforfp32mp2.boys_f0is integrated numerically for x < 0. - A result fails when its class differs from the reference's (NaN, ±inf, ±0 with its sign, or finite), or when a finite result is off by more than 1e-5 (
fp32mp2) or 1e-10 (fp64mp2) relative. - The host is GCC 15.2
-O2 -march=x86-64-v3and the device sm_89 with CUDA 13.4. - The placeholders erfinv, erfcinv, erfcx, normcdf, lgamma, tgamma, j0, j1, y0, y1, cyl_bessel_i0, cyl_bessel_i1, rint, nearbyint and logb exist on the device only.
- A note in parentheses means the result is wrong only there.
| Type | Function | Input → result, expected |
|---|---|---|
fp32mp2 |
asin |
-0 → +0, expected -0 |
fp32mp2 |
atan |
-INF → nan, expected -1.5708; -0 → +0, expected -0; +INF → nan, expected 1.5708 |
fp32mp2 |
boys_f0 |
-INF → nan, expected +inf; -maxN → nan, expected +inf; +INF → nan, expected +0 |
fp32mp2 |
ceil |
-INF → nan, expected -inf; -0.5 → +0 (device), expected -0; -minN → +0 (device), expected -0; -maxD → +0 (device), expected -0; -minD → +0 (device), expected -0; -0 → +0 (device), expected -0; +INF → nan, expected +inf |
fp32mp2 |
cosh |
-INF → nan, expected +inf; -maxN → nan, expected +inf; +maxN → nan, expected +inf; +INF → nan, expected +inf |
fp32mp2 |
cospi |
-0.5 → -0 (host), expected +0; +0.5 → -0 (host), expected +0 |
fp32mp2 |
cyl_bessel_i1 |
-0 → +0, expected -0 |
fp32mp2 |
erf |
-0 → +0, expected -0 |
fp32mp2 |
erfinv |
-0 → +0, expected -0 |
fp32mp2 |
expm1 |
-0 → +0, expected -0 |
fp32mp2 |
floor |
-INF → nan, expected -inf; -0 → +0 (device), expected -0; +INF → nan, expected +inf |
fp32mp2 |
j1 |
-0 → +0, expected -0 |
fp32mp2 |
log |
-INF → 88.7228, expected nan; -maxN → 88.7228, expected nan; -2 → 0.693147, expected nan; -1 → +0, expected nan; -0.5 → -0.693147, expected nan; -minN → -87.3365, expected nan; -maxD → -87.3365, expected nan; -minD → -103.279, expected nan; -0 → -104.665, expected -inf; +0 → -104.665, expected -inf; +INF → 88.7228, expected +inf; QNAN → 89.1283, expected nan |
fp32mp2 |
log10 |
-INF → 38.5318, expected nan; -maxN → 38.5318, expected nan; -2 → 0.30103, expected nan; -1 → +0, expected nan; -0.5 → -0.30103, expected nan; -minN → -37.9298, expected nan; -maxD → -37.9298, expected nan; -minD → -44.8535, expected nan; -0 → -45.4555, expected -inf; +0 → -45.4555, expected -inf; +INF → 38.5318, expected +inf; QNAN → 38.7079, expected nan |
fp32mp2 |
log1p |
-0 → +0, expected -0 |
fp32mp2 |
log2 |
-INF → 128, expected nan; -maxN → 128, expected nan; -2 → 1, expected nan; -1 → +0, expected nan; -0.5 → -1, expected nan; -minN → -126, expected nan; -maxD → -126, expected nan; -minD → -149, expected nan; -0 → -151, expected -inf; +0 → -151, expected -inf; +INF → 128, expected +inf; QNAN → 128.585, expected nan |
fp32mp2 |
nearbyint |
-INF → nan, expected -inf; -0 → +0, expected -0; +INF → nan, expected +inf |
fp32mp2 |
normcdfinv |
-INF → -inf, expected nan; -maxN → -inf, expected nan; -2 → -inf, expected nan; -1 → -inf, expected nan; -0.5 → -inf, expected nan; -minN → -inf, expected nan; -maxD → -inf, expected nan; -minD → -inf, expected nan; +2 → +inf, expected nan; +maxN → +inf, expected nan; +INF → +inf, expected nan |
fp32mp2 |
rint |
-INF → nan, expected -inf; -0 → +0, expected -0; +INF → nan, expected +inf |
fp32mp2 |
round |
-INF → nan, expected -inf; -minN → +0 (device), expected -0; -maxD → +0 (device), expected -0; -minD → +0 (device), expected -0; -0 → +0, expected -0; +INF → nan, expected +inf |
fp32mp2 |
rsqrt |
-0 → nan, expected -inf; +0 → nan, expected +inf; +minD → nan (device), expected 2.67137e+22; +maxD → nan (device), expected 9.22337e+18; +INF → nan, expected +0 |
fp32mp2 |
sin |
-0 → +0, expected -0 |
fp32mp2 |
sincos_s |
-0 → +0, expected -0 |
fp32mp2 |
sincospi_c |
-0.5 → -0 (host), expected +0; +0.5 → -0 (host), expected +0 |
fp32mp2 |
sinh |
-INF → nan, expected -inf; -maxN → nan, expected -inf; -0 → +0, expected -0; +maxN → nan, expected +inf; +INF → nan, expected +inf |
fp32mp2 |
sqrt |
-0 → +0, expected -0; +minD → nan (device), expected 3.74339e-23; +maxD → nan (device), expected 1.0842e-19; +INF → nan, expected +inf |
fp32mp2 |
tan |
-0 → +0, expected -0 |
fp32mp2 |
tanh |
-0 → +0, expected -0 |
fp32mp2 |
tgamma |
-0 → +inf, expected -inf |
fp32mp2 |
trunc |
-INF → nan, expected -inf; -0.5 → +0, expected -0; -minN → +0, expected -0; -maxD → +0, expected -0; -minD → +0, expected -0; -0 → +0, expected -0; +INF → nan, expected +inf |
fp64mp2 |
asin |
-0 → +0, expected -0 |
fp64mp2 |
asinh |
-0 → +0, expected -0 |
fp64mp2 |
atan |
-0 → +0, expected -0 |
fp64mp2 |
atanh |
-0 → +0, expected -0 |
fp64mp2 |
boys_f0 |
-INF → 1, expected +inf; -maxN → 1, expected +inf; -2 → 1, expected 2.36445; -1 → 1, expected 1.46265; -0.5 → 1, expected 1.19496 |
fp64mp2 |
cbrt |
-0 → +0, expected -0 |
fp64mp2 |
ceil |
-INF → nan, expected -inf; +INF → nan, expected +inf |
fp64mp2 |
cospi |
-maxN → nan (host), expected 1; -0.5 → 6.12323e-17 (host), expected +0; +0.5 → 6.12323e-17 (host), expected +0; +maxN → nan (host), expected 1 |
fp64mp2 |
cyl_bessel_i1 |
-0 → +0, expected -0 |
fp64mp2 |
erf |
-0 → +0, expected -0 |
fp64mp2 |
erfinv |
-0 → +0, expected -0 |
fp64mp2 |
expm1 |
-0 → +0, expected -0 |
fp64mp2 |
floor |
-INF → nan, expected -inf; +INF → nan, expected +inf |
fp64mp2 |
j1 |
-0 → +0, expected -0 |
fp64mp2 |
log1p |
-0 → +0, expected -0 |
fp64mp2 |
logb |
-maxD → -2.14748e+09, expected -1023; -minD → -2.14748e+09, expected -1074; +minD → -2.14748e+09, expected -1074; +maxD → -2.14748e+09, expected -1023 |
fp64mp2 |
nearbyint |
-INF → nan, expected -inf; +INF → nan, expected +inf |
fp64mp2 |
normcdfinv |
-INF → -inf (host), expected nan; -maxN → -inf (host), expected nan; -2 → -inf (host), expected nan; -1 → -inf (host), expected nan; -0.5 → -inf (host), expected nan; -minN → -inf (host), expected nan; -maxD → -inf (host), expected nan; -minD → -inf (host), expected nan; +minD → -inf (host), expected -38.4674; +maxD → -inf (host), expected -37.5194; +minN → -inf (host), expected -37.5194; +0.5 → -0 (device), expected +0; +2 → +inf (host), expected nan; +maxN → +inf (host), expected nan; +INF → +inf (host), expected nan |
fp64mp2 |
rcbrt |
-0 → +inf, expected -inf |
fp64mp2 |
rint |
-INF → nan, expected -inf; +INF → nan, expected +inf |
fp64mp2 |
round |
-INF → nan, expected -inf; -0 → +0, expected -0; +INF → nan, expected +inf |
fp64mp2 |
rsqrt |
-0 → nan, expected -inf; +0 → nan, expected +inf; +INF → nan, expected +0 |
fp64mp2 |
sin |
-0 → +0, expected -0 |
fp64mp2 |
sincos_s |
-0 → +0, expected -0 |
fp64mp2 |
sincospi_c |
-maxN → nan (host), expected 1; -0.5 → 6.12323e-17 (host), expected +0; +0.5 → 6.12323e-17 (host), expected +0; +maxN → nan (host), expected 1 |
fp64mp2 |
sincospi_s |
-maxN → nan (host), expected -0; -2 → 2.44929e-16 (host), expected -0; -1 → -1.22465e-16 (host), expected -0; -0 → +0, expected -0; +1 → 1.22465e-16 (host), expected +0; +2 → -2.44929e-16 (host), expected +0; +maxN → nan (host), expected +0 |
fp64mp2 |
sinh |
-0 → +0, expected -0 |
fp64mp2 |
sinpi |
-maxN → nan (host), expected -0; -2 → 2.44929e-16 (host), expected -0; -1 → -1.22465e-16 (host), expected -0; -0 → +0, expected -0; +1 → 1.22465e-16 (host), expected +0; +2 → -2.44929e-16 (host), expected +0; +maxN → nan (host), expected +0 |
fp64mp2 |
sqrt |
-0 → +0, expected -0; +INF → nan, expected +inf |
fp64mp2 |
tan |
-0 → +0, expected -0 |
fp64mp2 |
tanh |
-0 → +0, expected -0 |
fp64mp2 |
tgamma |
-0 → +inf, expected -inf |
fp64mp2 |
trunc |
-INF → nan, expected -inf; +INF → nan, expected +inf |
How to Reproduce
#include <cuda/fpmp>
#include <cfloat>
#include <cmath>
#include <cstdio>
using T = cuda::fp32mp2;
__host__ __device__ void show(const char* expr, const char* expected, T r)
{
printf(" %-22s = %-12g (expected %s)\n", expr, static_cast<double>(r.hi()), expected);
}
__host__ __device__ void probe()
{
show("log(+0)", "-inf", cuda::log(T(0.0f)));
show("log(-2)", "nan", cuda::log(T(-2.0f)));
show("log(+inf)", "+inf", cuda::log(T(INFINITY)));
show("log(nan)", "nan", cuda::log(T(NAN)));
show("log2(-1)", "nan", cuda::log2(T(-1.0f)));
show("sinh(+inf)", "+inf", cuda::sinh(T(INFINITY)));
show("cosh(FLT_MAX)", "+inf", cuda::cosh(T(FLT_MAX)));
show("atan(+inf)", "1.5708", cuda::atan(T(INFINITY)));
show("boys_f0(+inf)", "0", cuda::boys_f0(T(INFINITY)));
show("sqrt(+inf)", "+inf", cuda::sqrt(T(INFINITY)));
show("sqrt(2^-149)", "3.74339e-23", cuda::sqrt(T(0x1p-149f)));
show("rsqrt(+0)", "+inf", cuda::rsqrt(T(0.0f)));
show("floor(-inf)", "-inf", cuda::floor(T(-INFINITY)));
show("normcdfinv(2)", "nan", cuda::normcdfinv(T(2.0f)));
show("sin(-0)", "-0", cuda::sin(T(-0.0f)));
show("cospi(0.5)", "0", cuda::cospi(T(0.5f)));
}
__global__ void kernel()
{
printf("device:\n");
probe();
}
int main()
{
printf("host:\n");
probe();
kernel<<<1, 1>>>();
cudaDeviceSynchronize();
}Built with nvcc -std=c++17 -arch=sm_89 -I <cccl>/libcudacxx/include repro.cu against main, it prints:
host:
log(+0) = -104.665 (expected -inf)
log(-2) = 0.693147 (expected nan)
log(+inf) = 88.7228 (expected +inf)
log(nan) = 89.1283 (expected nan)
log2(-1) = 0 (expected nan)
sinh(+inf) = -nan (expected +inf)
cosh(FLT_MAX) = -nan (expected +inf)
atan(+inf) = -nan (expected 1.5708)
boys_f0(+inf) = -nan (expected 0)
sqrt(+inf) = -nan (expected +inf)
sqrt(2^-149) = 3.74339e-23 (expected 3.74339e-23)
rsqrt(+0) = -nan (expected +inf)
floor(-inf) = -nan (expected -inf)
normcdfinv(2) = inf (expected nan)
sin(-0) = 0 (expected -0)
cospi(0.5) = -0 (expected 0)
device:
log(+0) = -104.665 (expected -inf)
log(-2) = 0.693147 (expected nan)
log(+inf) = 88.7228 (expected +inf)
log(nan) = 89.1283 (expected nan)
log2(-1) = 0 (expected nan)
sinh(+inf) = nan (expected +inf)
cosh(FLT_MAX) = nan (expected +inf)
atan(+inf) = nan (expected 1.5708)
boys_f0(+inf) = nan (expected 0)
sqrt(+inf) = nan (expected +inf)
sqrt(2^-149) = nan (expected 3.74339e-23)
rsqrt(+0) = nan (expected +inf)
floor(-inf) = nan (expected -inf)
normcdfinv(2) = inf (expected nan)
sin(-0) = 0 (expected -0)
cospi(0.5) = 0 (expected 0)
Expected behavior
The C99 Annex F / IEEE 754 results, which are also what CUDA's float and double functions return for these inputs:
- log(x < 0) = NaN, log(±0) = −inf and log(+inf) = +inf;
- sinh(±inf) = ±inf, cosh(±inf) = +inf and atan(±inf) = ±π/2;
- sqrt(+inf) = +inf and rsqrt(±0) = ±inf;
- the rounding functions return ±inf for ±inf;
- odd functions return −0 for −0;
- normcdfinv returns NaN outside [0, 1];
- host and device give the same results.
One way to get there is to handle special inputs explicitly at each function's entry, as #12548 does for pow, instead of relying on the shared kernels to propagate them. The spec's special-value tables should then be regenerated from the implementation.
Reproduction link
No response
Operating System
Ubuntu 26.04.1 LTS (WSL2)
nvidia-smi output
NVIDIA GeForce RTX 4070 Laptop GPU (sm_89), driver 621.14
NVCC version
Cuda compilation tools, release 13.4, V13.4.92; host compiler GCC 15.2
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
github-actions commentedon Nov 9, 2026
Hi @rmlarsen!
Thanks for submitting this issue - the CCCL team has been notified and we'll get back to you as soon as we can!
In the mean time, feel free to add any relevant information to this issue.
davebayer commentedon Nov 9, 2026
@akolesov-nvidia could you please have a look at this one? Thank you
akolesov-nvidia commentedon Nov 9, 2026
We planned math functions set improvements for the next update, WIP. Thank you!
[fp] fp32mp2 math: IEEE special values for log/log2/log10, atan, the …
[fp] fp32mp2 math: IEEE special values for log/log2/log10, atan, the …
[fp] fp32mp2 math: IEEE special values for log/log2/log10, atan, the …