[PATCH 2/2] Simplify powl computation for small integral y [BZ #33411]
Siddhesh Poyarekar
siddhesh@sourceware.org
Tue Oct 21 03:02:14 GMT 2025
The powl implementation for x86_64 ends up multiplying X once more than
necessary and then throwing away that result. This results in an
overflow flag being set in cases where there is no overflow.
Simplify the relevant portion by special casing the -3 to 3 range and
simply multiplying repetitively.
Resolves: BZ #33411
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
math/test-powl.c | 22 +++++++++++--
sysdeps/x86_64/fpu/e_powl.S | 61 +++++++++++++++++++++----------------
2 files changed, 54 insertions(+), 29 deletions(-)
diff --git a/math/test-powl.c b/math/test-powl.c
index 2c716043b8..25253d51d7 100644
--- a/math/test-powl.c
+++ b/math/test-powl.c
@@ -16,10 +16,11 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <stdio.h>
-#include <math.h>
+#include <fenv.h>
#include <float.h>
#include <ieee754.h>
+#include <math.h>
+#include <stdio.h>
#include <support/check.h>
/* Smoke test for bz12775, which discovered a typo in the x86_64 powl
@@ -46,10 +47,27 @@ do_test_bz12775 (void)
#endif
}
+/* Make sure that a long double ^ 1 does not set the overflow flag. For
+ context see BZ #33411. */
+static void
+do_test_overflow (void)
+{
+ long double x = 1.090748135619415929463e+2466L;
+
+ feclearexcept( FE_ALL_EXCEPT );
+
+ long double res = powl (x, 1.L);
+ int flags = fetestexcept( FE_ALL_EXCEPT );
+
+ TEST_VERIFY (res == x);
+ TEST_COMPARE (flags & FE_OVERFLOW, 0);
+}
+
static int
do_test (void)
{
do_test_bz12775 ();
+ do_test_overflow ();
return 0;
}
diff --git a/sysdeps/x86_64/fpu/e_powl.S b/sysdeps/x86_64/fpu/e_powl.S
index 620ef765a7..5695e1a9a6 100644
--- a/sysdeps/x86_64/fpu/e_powl.S
+++ b/sysdeps/x86_64/fpu/e_powl.S
@@ -144,40 +144,47 @@ ENTRY(__ieee754_powl)
fcomip %st(1), %st // 4 : y : x
fstp %st(0) // y : x
jnc 3f
- mov -8(%rsp),%eax
- mov -4(%rsp),%edx
- orl $0, %edx
+
+ /* Here onwards, it's just integral y in range [-3, 3]. */
+ movq -8(%rsp),%rax
+ orq $0, %rax
fstp %st(0) // x
jns 4f // y >= 0, jump
fdivrl MO(one) // 1/x (now referred to as x)
- negl %eax
- adcl $0, %edx
- negl %edx
+ negq %rax
4: fldl MO(one) // 1 : x
- fxch
- /* If y is even, take the absolute value of x. Otherwise,
- ensure all intermediate values that might overflow have the
- sign of x. */
+ /* y range is further reduced to [0, 3]. Simply walk through the
+ options. */
+ test %eax, %eax
+ jnz 5f
+ fstp %st(1) // 1
+ jmp 7f
+
+ /* y == 1. */
+5: movl %eax, %ecx
+ xorl $1, %ecx
+ fstp %st(0) // x
+ jz 7f
+
+ /* y == 2. */
testb $1, %al
jnz 6f
- fabs
-
-6: shrdl $1, %edx, %eax
- jnc 5f
- fxch
- fabs
- fmul %st(1) // x : ST*x
- fxch
-5: fld %st // x : x : ST*x
- fabs // |x| : x : ST*x
- fmulp // |x|*x : ST*x
- shrl $1, %edx
- movl %eax, %ecx
- orl %edx, %ecx
- jnz 6b
- fstp %st(0) // ST*x
- LDBL_CHECK_FORCE_UFLOW_NONNAN
+ fabs // |x|
+ fld %st // |x| : |x|
+ fmulp %st(1) // |x| * |x|
+ jmp 7f
+
+ /* y == 3. Special cased because x * x and |x| * |x| decay towards
+ infinity faster compared to x * |x|. */
+6: fld %st // x : x
+ fabs // |x| : x
+ fxch // x : |x|
+ fld %st(1) // |x| : x : |x|
+ fmulp // |x| * x : |x|
+ fmulp // |x| * x * |x|
+
+7: LDBL_CHECK_FORCE_UFLOW_NONNAN
ret
/* y is ±NAN */
--
2.51.0
More information about the Libc-alpha
mailing list