[libc-commits] [libc] [libc][math] Fix low-precision range reporting and rounding (PR #236472)
via libc-commits
libc-commits at lists.llvm.org
Wed Oct 28 20:28:34 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: sriramshastry
<details>
<summary>Changes</summary>
Keep five scoped fixes as separate commits in one review branch. Their file
overlap connects exponential overflow, bfloat edge paths, sinh subnormals,
exp2m1 rounding, and atanbf16's smoke-test registration. PR #<!-- -->236310 is not
included; this branch contains no expm1f16 subnormal repair.
## Group validation
Tested on upstream ea08830d976fbe61384695d33e0acafee8d88737, x86-64 Linux, Release,
with GCC 13 and Clang 18. The exact five-patch group passes 38 native target
configurations per compiler. Ten finite-domain MPFR runs (five functions,
two compilers) pass all four modes with zero value or scoped-reporting
mismatches. Reporting screens are narrower than value domains.
No non-x86, performance, or full-libc-suite result is claimed.
## Commit 1: Raise overflow for directed finite exponential results
Raise FE_OVERFLOW and FE_INEXACT before the overflow path chooses infinity or the
maximum finite result. This covers directed finite returns in the half precision
exponential family, sinhf16, coshf16 and exp2bf16. Preserve the existing errno behavior
for finite overflow. Add tests for all supported rounding modes and strengthen existing
overflow assertions.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux,
Release, GCC 13 and Clang 18. With only the revised tests, 32/32 smoke target
configurations fail per compiler; with the repair, 32/32 pass. Tests distinguish result
bits, overflow/inexact flags, and retained errno behavior, including negative sinh
overflow. Current logs are in the local packet's evidence/upstream-latest-20261028/.
### Scope and Review
Keep the nine functions together. Review signed sinh overflow, infinity inputs, and
finite-overflow errno policy. This patch shares implementations with the separate bfloat
exponential-edge, sinhf16-subnormal and exp2m1f16-boundary proposals, and with PR
#<!-- -->236310.
This patch preserves the existing errno choices on finite saturation; it repairs
exception flags without establishing a new errno policy. Combined validation is recorded
separately in the local packet. No non-x86 or performance result is claimed.
## Commit 2: Avoid spurious atanbf16 range exceptions
Avoid intermediate range exceptions in atanbf16. Delay squaring until it is needed, use
a double intermediate for the small correction, and use the signed pi divided by two
result when the magnitude is at least 4096. Add direct range flag regression tests. MPFR
comparison across every finite bfloat16 encoding and all four rounding modes finds zero
value mismatches, and the scoped range-reporting screen passes.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux with
GCC 13 and Clang 18. Both baseline smoke configurations fail per compiler; both patched
configurations pass. MPFR 4.2.1 comparisons cover all 65,280 finite bfloat encodings in
all four modes, with zero patched value mismatches. A separate screen checks spurious
overflow/underflow and errno for finite magnitudes greater than 0x0080; it also passes
after the repair. Baseline values already matched, but the screen exposes the reporting
defect.
### Scope and Review
The exception screen is narrower than the value domain. Review double-intermediate cost
and target support. No speedup, complete exception oracle, or non-x86 execution is
claimed.
## Commit 3: Fix bfloat16 exponential underflow and tiny input paths
Correct the far-negative expbf16 upward result and the -92.5 nearest-only boundary.
Add tiny-input paths with double intermediates to expbf16 and exp2bf16 to avoid
spurious float-intermediate range exceptions. Add explicit result and range-flag
regressions.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux with
GCC 13 and Clang 18. Per compiler, 6/8 baseline smoke configurations fail and all 8/8
patched configurations pass. MPFR 4.2.1 comparisons cover all 65,280 finite bfloat
encodings in all four modes for both functions: zero patched value mismatches. A
reporting screen for magnitudes at most 0x3980 also passes. Changed-hunk formatting and
whitespace checks pass.
### Scope and Review
The reporting screen is not exhaustive over the full value domain. Review tiny-path
double arithmetic and target cost. Shares exp2bf16 with the separate directed-overflow
proposal, combined validation is recorded separately in the local packet. No performance
or non-x86 result is claimed.
## Commit 4: Correct sinhf16 subnormal rounding and range reporting
Select correctly rounded sinhf16 results directly for nonzero half subnormals,
using the sign of the cubic correction. Fix the negative downward result and
inexact-underflow reporting. Test every signed subnormal in each supported mode.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux
with GCC 13 and Clang 18. All four baseline smoke configurations fail per
compiler; all four patched configurations pass. MPFR 4.2.1 checks all 63,488
finite half encodings in four modes, with zero value mismatches. Separate reporting
checks cover every nonzero signed subnormal and four adjacent normal/subnormal boundary
inputs, with zero mismatches against tininess after rounding. MPFR's own underflow flag
is recorded separately; it differs at the outward steps from largest subnormal to
minimum normal.
### Scope and Review
For $0<a=|x|<2^{-14}$, $0<\sinh(a)-a<a^3\cosh(a)/6<a^3/3<2^{-25}$. Odd symmetry makes
the correction point away from zero, with magnitude below half a subnormal ulp. Nearest
returns x; only outward directed modes choose the adjacent value. Review the
after-rounding underflow/errno policy, particularly rounding to minimum normal. Shares
the implementation with the separate directed-overflow proposal; combined validation is
recorded separately in the local packet.
## Commit 5: Correct a subnormal exp2m1f16 rounding boundary
Add the missing low-range exp2m1f16 exceptional input 0x8461 with independently checked
directed outputs. Report underflow and ERANGE when the exception table returns a
subnormal. Add a four-mode regression.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux with
GCC 13 and Clang 18. All four baseline smoke configurations fail per compiler; all
four patched configurations pass. With the accurate pass enabled, MPFR 4.2.1 checks
all 63,488 finite half encodings in four modes: one baseline upward value mismatch,
zero patched value mismatches. A reporting screen specifically for 0x8461 also passes
after the repair.
### Scope and Review
At 0x8461, expected encodings are RN 0x8309, RU 0x8308, RD 0x8309, RZ 0x8308. The old
upward result is one binary16 subnormal ulp too negative. This fix depends on the
accurate pass; the separate normal-boundary tininess issue is not repaired. Shares the
implementation with the separate directed-overflow proposal; combined validation is
recorded separately in the local packet. Exhaustive value coverage does not imply
exhaustive exception coverage.
## Updated submission base
Submission base: 10c142fa799e258285b6cfb8fce153864586152d.
The standalone baseline/patched results above remain recorded at their original
tested snapshot. At this newer base, the fourteen-only combined suite passes
150 native configurations per compiler and twelve four-mode numerical runs,
with zero value or scoped-reporting mismatches.
The exact five-commit group also passes 38 native configurations per compiler
and ten four-mode numerical runs at this newer base.
Signed-off-by: sriramshastry <sriramshastry@<!-- -->gmail.com>
---
Patch is 41.51 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/236472.diff
23 Files Affected:
- (modified) libc/src/__support/math/atanbf16.h (+14-3)
- (modified) libc/src/__support/math/coshf16.h (+2-2)
- (modified) libc/src/__support/math/exp10f16.h (+2-2)
- (modified) libc/src/__support/math/exp10m1f16.h (+2-2)
- (modified) libc/src/__support/math/exp2bf16.h (+10-2)
- (modified) libc/src/__support/math/exp2f16.h (+2-2)
- (modified) libc/src/__support/math/exp2m1f16.h (+11-4)
- (modified) libc/src/__support/math/expbf16.h (+9)
- (modified) libc/src/__support/math/expf16.h (+2-2)
- (modified) libc/src/__support/math/expm1f16.h (+2-2)
- (modified) libc/src/__support/math/sinhf16.h (+21-2)
- (modified) libc/test/src/math/smoke/CMakeLists.txt (+3)
- (modified) libc/test/src/math/smoke/atanbf16_test.cpp (+46)
- (modified) libc/test/src/math/smoke/coshf16_test.cpp (+26-4)
- (modified) libc/test/src/math/smoke/exp10f16_test.cpp (+22)
- (modified) libc/test/src/math/smoke/exp10m1f16_test.cpp (+24-2)
- (modified) libc/test/src/math/smoke/exp2bf16_test.cpp (+62)
- (modified) libc/test/src/math/smoke/exp2f16_test.cpp (+22)
- (modified) libc/test/src/math/smoke/exp2m1f16_test.cpp (+49-2)
- (modified) libc/test/src/math/smoke/expbf16_test.cpp (+55)
- (modified) libc/test/src/math/smoke/expf16_test.cpp (+22)
- (modified) libc/test/src/math/smoke/expm1f16_test.cpp (+24-2)
- (modified) libc/test/src/math/smoke/sinhf16_test.cpp (+70-4)
``````````diff
diff --git a/libc/src/__support/math/atanbf16.h b/libc/src/__support/math/atanbf16.h
index 563b8325b84285..796420a6cf4b62 100644
--- a/libc/src/__support/math/atanbf16.h
+++ b/libc/src/__support/math/atanbf16.h
@@ -58,7 +58,6 @@ LIBC_INLINE bfloat16 atanbf16(bfloat16 x) {
};
float xf = x;
- float x_sq = xf * xf;
// |x| <= 1
if (x_abs <= 0x3f80) {
@@ -67,9 +66,13 @@ LIBC_INLINE bfloat16 atanbf16(bfloat16 x) {
return x;
// For smaller x
- if (LIBC_UNLIKELY(x_abs <= 0x3db8))
- return fputil::cast<bfloat16>(fputil::multiply_add(xf, -0x1p-9f, xf));
+ if (LIBC_UNLIKELY(x_abs <= 0x3db8)) {
+ // Keep the small correction normal even for the smallest bfloat16 input.
+ double xd = xf;
+ return fputil::cast<bfloat16>(fputil::multiply_add(xd, -0x1p-9, xd));
+ }
+ float x_sq = xf * xf;
float result = atan_eval(x_sq);
return fputil::cast<bfloat16>(fputil::multiply_add(xf * x_sq, result, xf));
}
@@ -88,6 +91,14 @@ LIBC_INLINE bfloat16 atanbf16(bfloat16 x) {
return fputil::cast<bfloat16>(sign * PI_2);
}
+ // For |x| >= 2^12, atan(|x|) lies strictly between 0x1.92p0 and pi/2.
+ // This interval has the same bfloat16 rounding as pi/2 in every mode.
+ // Avoid squaring large inputs or forming tiny reciprocal intermediates.
+ if (LIBC_UNLIKELY(x_abs >= 0x4580))
+ return fputil::cast<bfloat16>(sign * PI_2);
+
+ float x_sq = xf * xf;
+
// If |x| > 1:
// atan(x) = sign(x) * (pi/2 - atan(1/|x|))
// Since 1/|x| < 1, we can use the same polynomial.
diff --git a/libc/src/__support/math/coshf16.h b/libc/src/__support/math/coshf16.h
index f5015199b3f936..d8cd53942a87ba 100644
--- a/libc/src/__support/math/coshf16.h
+++ b/libc/src/__support/math/coshf16.h
@@ -90,16 +90,16 @@ LIBC_INLINE constexpr float16 coshf16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
diff --git a/libc/src/__support/math/exp10f16.h b/libc/src/__support/math/exp10f16.h
index 727914f1bb6013..3a525ea4baa09a 100644
--- a/libc/src/__support/math/exp10f16.h
+++ b/libc/src/__support/math/exp10f16.h
@@ -82,16 +82,16 @@ LIBC_INLINE constexpr float16 exp10f16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
diff --git a/libc/src/__support/math/exp10m1f16.h b/libc/src/__support/math/exp10m1f16.h
index cb685298f01c78..aa99797f33b9df 100644
--- a/libc/src/__support/math/exp10m1f16.h
+++ b/libc/src/__support/math/exp10m1f16.h
@@ -93,16 +93,16 @@ LIBC_INLINE constexpr float16 exp10m1f16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
diff --git a/libc/src/__support/math/exp2bf16.h b/libc/src/__support/math/exp2bf16.h
index 44fa5da0ebe477..7bbd39f7756905 100644
--- a/libc/src/__support/math/exp2bf16.h
+++ b/libc/src/__support/math/exp2bf16.h
@@ -82,10 +82,11 @@ LIBC_INLINE bfloat16 exp2bf16(bfloat16 x) {
if (x_bits.is_inf())
return x;
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
// If we can only RN, then it'll go towards INF
fputil::set_errno_if_required(ERANGE);
- fputil::raise_overflow_except_if_required<float>();
return FPBits::inf().get_val();
#else
// If not, get what other rounding modes are available
@@ -94,7 +95,6 @@ LIBC_INLINE bfloat16 exp2bf16(bfloat16 x) {
case FE_UPWARD:
// Both of these go to Inf
fputil::set_errno_if_required(ERANGE);
- fputil::raise_overflow_except_if_required<float>();
return FPBits::inf().get_val();
default:
// RD and RZ go to the maximum possible value in the
@@ -140,6 +140,14 @@ LIBC_INLINE bfloat16 exp2bf16(bfloat16 x) {
}
}
+ // This interval rounds like 1 + x * log(2) in every bfloat16 mode.
+ // Double intermediates avoid underflow in the float polynomial for tiny x.
+ if (LIBC_UNLIKELY(x_bits.abs().uintval() <= 0x3980U)) {
+ double xd = static_cast<float>(x);
+ return fputil::cast<bfloat16>(
+ fputil::multiply_add(xd, 0x1.62e42fefa39efp-1, 1.0));
+ }
+
// For inputs not handled above in -134.0 < x < 128.0,
// we perform the following range reduction:
// x = hi + mid + lo
diff --git a/libc/src/__support/math/exp2f16.h b/libc/src/__support/math/exp2f16.h
index fe2c78faeef981..68c50f2e1ce35f 100644
--- a/libc/src/__support/math/exp2f16.h
+++ b/libc/src/__support/math/exp2f16.h
@@ -66,16 +66,16 @@ LIBC_INLINE constexpr float16 exp2f16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
diff --git a/libc/src/__support/math/exp2m1f16.h b/libc/src/__support/math/exp2m1f16.h
index 7ea8898200259f..05555a395b8bde 100644
--- a/libc/src/__support/math/exp2m1f16.h
+++ b/libc/src/__support/math/exp2m1f16.h
@@ -31,8 +31,10 @@ namespace math {
LIBC_INLINE constexpr float16 exp2m1f16(float16 x) {
#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
- constexpr fputil::ExceptValues<float16, 6> EXP2M1F16_EXCEPTS_LO = {{
+ constexpr fputil::ExceptValues<float16, 7> EXP2M1F16_EXCEPTS_LO = {{
// (input, RZ output, RU offset, RD offset, RN offset)
+ // Subnormal output straddling the upward rounding boundary.
+ {0x8461U, 0x8308U, 0U, 1U, 1U},
// x = 0x1.cf4p-13, exp2m1f16(x) = 0x1.41p-13 (RZ)
{0x0b3dU, 0x0904U, 1U, 0U, 1U},
// x = 0x1.4fcp-12, exp2m1f16(x) = 0x1.d14p-13 (RZ)
@@ -106,16 +108,16 @@ LIBC_INLINE constexpr float16 exp2m1f16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
@@ -155,8 +157,13 @@ LIBC_INLINE constexpr float16 exp2m1f16(float16 x) {
if (x_abs <= 0x3000U) {
#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
if (auto r = EXP2M1F16_EXCEPTS_LO.lookup(x_u);
- LIBC_UNLIKELY(r.has_value()))
+ LIBC_UNLIKELY(r.has_value())) {
+ if (FPBits(r.value()).is_subnormal()) {
+ fputil::set_errno_if_required(ERANGE);
+ fputil::raise_except_if_required(FE_UNDERFLOW);
+ }
return r.value();
+ }
#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS
float xf = x;
diff --git a/libc/src/__support/math/expbf16.h b/libc/src/__support/math/expbf16.h
index 5fdde6d6204f74..47e290c69caef0 100644
--- a/libc/src/__support/math/expbf16.h
+++ b/libc/src/__support/math/expbf16.h
@@ -65,6 +65,12 @@ LIBC_INLINE bfloat16 expbf16(bfloat16 x) {
uint16_t x_u = x_bits.uintval();
uint16_t x_abs = x_u & 0x7fffU;
+ // For |x| <= 2^-12, exp(x) rounds like 1 + x in bfloat16.
+ // Use double so tiny products in the float polynomial cannot underflow.
+ if (LIBC_UNLIKELY(x_abs <= 0x3980U))
+ return fputil::cast<bfloat16>(1.0 +
+ static_cast<double>(static_cast<float>(x)));
+
// 0 <= |x| <= 2^(-3), or |x| >= 89, or x is NaN
if (LIBC_UNLIKELY(x_abs <= 0x3e00U || x_abs >= 0x42b2U)) {
@@ -111,10 +117,13 @@ LIBC_INLINE bfloat16 expbf16(bfloat16 x) {
fputil::raise_except_if_required(FE_UNDERFLOW | FE_INEXACT);
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+ if (LIBC_UNLIKELY(x_u == 0xc2b9U))
+ return FPBits::min_subnormal().get_val();
return FPBits::zero().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_UPWARD:
+ return FPBits::min_subnormal().get_val();
case FE_TONEAREST:
if (LIBC_UNLIKELY(x_u == 0xc2b9U))
return FPBits::min_subnormal().get_val();
diff --git a/libc/src/__support/math/expf16.h b/libc/src/__support/math/expf16.h
index fd0c92b791ecfc..7e6ca831bf2fe6 100644
--- a/libc/src/__support/math/expf16.h
+++ b/libc/src/__support/math/expf16.h
@@ -75,16 +75,16 @@ LIBC_INLINE constexpr float16 expf16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
diff --git a/libc/src/__support/math/expm1f16.h b/libc/src/__support/math/expm1f16.h
index 08234a0ad4ace9..c1f6b9ff306fe7 100644
--- a/libc/src/__support/math/expm1f16.h
+++ b/libc/src/__support/math/expm1f16.h
@@ -86,16 +86,16 @@ LIBC_INLINE constexpr float16 expm1f16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf().get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
diff --git a/libc/src/__support/math/sinhf16.h b/libc/src/__support/math/sinhf16.h
index 45d7eddd01fed6..1601bf35db38e2 100644
--- a/libc/src/__support/math/sinhf16.h
+++ b/libc/src/__support/math/sinhf16.h
@@ -105,6 +105,25 @@ LIBC_INLINE constexpr float16 sinhf16(float16 x) {
uint16_t x_u = x_bits.uintval();
uint16_t x_abs = x_u & 0x7fffU;
+ // The correction to x is nonzero but less than half a subnormal ulp.
+ // Select the adjacent half value directly, preserving signed zero.
+ if (LIBC_UNLIKELY(x_abs != 0 && x_abs < 0x0400U)) {
+ uint16_t out = x_u;
+#ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+ int mode = fputil::quick_get_round();
+ if ((x_bits.is_pos() && mode == FE_UPWARD) ||
+ (x_bits.is_neg() && mode == FE_DOWNWARD))
+ out = static_cast<uint16_t>(x_u + 1);
+#endif
+ int excepts = FE_INEXACT;
+ if ((out & 0x7fffU) < 0x0400U) {
+ fputil::set_errno_if_required(ERANGE);
+ excepts |= FE_UNDERFLOW;
+ }
+ fputil::raise_except_if_required(excepts);
+ return FPBits(out).get_val();
+ }
+
// When |x| = 0, or -2^(-14) <= x <= -2^(-9), or |x| >= asinh(2^16), or x is
// NaN.
if (LIBC_UNLIKELY(x_abs == 0U || (x_u >= 0x8400U && x_u <= 0xa400U) ||
@@ -129,9 +148,10 @@ LIBC_INLINE constexpr float16 sinhf16(float16 x) {
if (x_bits.is_inf())
return FPBits::inf(x_bits.sign()).get_val();
+ fputil::raise_overflow_except_if_required<float>();
+
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf(x_bits.sign()).get_val();
#else
int rounding_mode = fputil::quick_get_round();
@@ -139,7 +159,6 @@ LIBC_INLINE constexpr float16 sinhf16(float16 x) {
(x_bits.is_pos() && rounding_mode == FE_UPWARD) ||
(x_bits.is_neg() && rounding_mode == FE_DOWNWARD)) {
fputil::set_errno_if_required(ERANGE);
- fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf(x_bits.sign()).get_val();
}
#endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt
index b1bb1a3f3b3434..670b4136de17c7 100644
--- a/libc/test/src/math/smoke/CMakeLists.txt
+++ b/libc/test/src/math/smoke/CMakeLists.txt
@@ -1483,6 +1483,7 @@ add_fp_unittest(
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.math.expbf16
+ libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.bfloat16
libc.src.__support.FPUtil.cast
)
@@ -1528,6 +1529,7 @@ add_fp_unittest(
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.math.exp2bf16
+ libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.bfloat16
)
@@ -5137,6 +5139,7 @@ add_fp_unittest(
libc.src.math.atanbf16
libc.hdr.errno_macros
libc.hdr.fenv_macros
+ libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.bfloat16
)
diff --git a/libc/test/src/math/smoke/atanbf16_test.cpp b/libc/test/src/math/smoke/atanbf16_test.cpp
index 36b0fd69566b6d..4df1931b43fc3d 100644
--- a/libc/test/src/math/smoke/atanbf16_test.cpp
+++ b/libc/test/src/math/smoke/atanbf16_test.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
+#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/bfloat16.h"
#include "src/math/atanbf16.h"
#include "test/UnitTest/FEnvSafeTest.h"
@@ -47,3 +48,48 @@ class LlvmLibcAtanBf16Test : public LIBC_NAMESPACE::testing::FEnvSafeTest {
}
};
TEST_F(LlvmLibcAtanBf16Test, SpecialNumbers) { test_special_numbers(); }
+
+TEST_F(LlvmLibcAtanBf16Test, NoSpuriousRangeExceptions) {
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<bfloat16>;
+ using namespace LIBC_NAMESPACE::fputil::testing;
+#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+ constexpr RoundingMode MODES[] = {RoundingMode::Nearest};
+#else
+ constexpr RoundingMode MODES[] = {RoundingMode::Nearest, RoundingMode::Upward,
+ RoundingMode::Downward,
+ RoundingMode::TowardZero};
+#endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+ // Each result is normal, including the tiny inputs that used to underflow
+ // while squaring x or computing the small correction in float.
+ constexpr uint16_t INPUTS[] = {0x0100, 0x2000, 0x3c00,
+ 0x4580, 0x5f80, 0x7f7f};
+ for (RoundingMode mode : MODES) {
+ ForceRoundingMode rounding(mode);
+ if (!rounding.success)
+ continue;
+ for (uint16_t magnitude : INPUTS) {
+ constexpr uint16_t SIGNS[] = {0, 0x8000};
+ for (uint16_t sign : SIGNS) {
+ bool negative = sign != 0;
+ uint16_t expected = magnitude < 0x4580 ? magnitude : uint16_t(0x3fc9);
+ if (magnitude < 0x4580) {
+ if (mode == RoundingMode::TowardZero ||
+ (negative && mode == RoundingMode::Upward) ||
+ (!negative && mode == RoundingMode::Downward))
+ --expected;
+ } else if ((negative && mode == RoundingMode::Downward) ||
+ (!negative && mode == RoundingMode::Upward)) {
+ ++expected;
+ }
+ LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+ bfloat16 result = LIBC_NAMESPACE::atanbf16(
+ Bits(uint16_t(magnitude | sign)).get_val());
+ EXPECT_FP_EQ(Bits(uint16_t(expected | sign)).get_val(), result);
+ EXPECT_EQ(
+ LIBC_NAMESPACE::fputil::test_except(FE_OVERFLOW | FE_UNDERFLOW), 0);
+ EXPECT_MATH_ERRNO(0);
+ LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+ }
+ }
+ }
+}
diff --git a/libc/test/src/math/smoke/coshf16_test.cpp b/libc/test/src/math/smoke/coshf16_test.cpp
index 6d5824d37dafbd..570cee472225b2 100644
--- a/libc/test/src/math/smoke/coshf16_test.cpp
+++ b/libc/test/src/math/smoke/coshf16_test.cpp
@@ -58,11 +58,11 @@ TEST_F(LlvmLibcCoshf16Test, Overflow) {
EXPECT_MATH_ERRNO(ERANGE);
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(
- max_normal, LIBC_NAMESPACE::coshf16(x), FE_INEXACT);
+ max_normal, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(0);
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(
- max_normal, LIBC_NAMESPACE::coshf16(x), FE_INEXACT);
+ max_normal, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(0);
// round(-acosh(2^16), HP, RD);
@@ -77,10 +77,32 @@ TEST_F(LlvmLibcCoshf16Test, Overflow) {
EXPECT_MATH_ERRNO(ERANGE);
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(
- max_normal, LIBC_NAMESPACE::coshf16(x), FE_INEXACT);
+ max_normal, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(0);
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(
- max_normal, LIBC_NAMESPACE::coshf16(x), FE_INEXACT);
+ max_normal, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(0);
}
+
+TEST_F(LlvmLibcCoshf16Test, OverflowStatusAllRoundingModes) {
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<float16>;
+ const float16 x = Bits(uint16_t(0x5000)).get_val();
+ const float16 largest = Bits(uint16_t(0x7bff)).get_val();
+ const float16 infinity = Bits::inf().get_val();
+ EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST(
+ infinity, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
+ EXPECT_MATH_ERRNO(ERANGE);
+
+#ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+ EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD(
+ infinity, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
+ EXPECT_MATH_ERRNO(ERANGE);
+ EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(
+ largest, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
+ EXPECT_MATH_ERRNO(0);
+ EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(
+ largest, LIBC_NAMESPACE::coshf16(x), FE_OVERFLOW | FE_INEXACT);
+ EXPECT_MATH_ERRNO(0);
+#endif // !LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+}
diff --git a/libc/test/src/math/smoke/exp10f16_test.cpp b/libc/test/src/math/smoke/exp10f16_test.cpp
index c2dffd1be06762..ab57145327ed26 100644
--- a/libc/test/src/math/smoke/exp10f16_test.cpp
+++ b/libc/test/src/math/smoke/exp10f16_test.cpp
@@ -59,3 +59,25 @@ TES...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/236472
More information about the libc-commits
mailing list