On 2025-07-08 01:12, Paul Eggert wrote:

After thinking about it a bit more, I filed a GCC bug report here:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120993

The responses indicate that it's a conformance bug that they won't fix as they want to focus on ieeelongdouble and leave ibmlongdouble alone. With that in mind I installed the attached further Gnulib patch, which fixed the problem for me on PowerPC. Please give it a try.
From 6164b4cb0887b5331a4e64449107decd37d32735 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Tue, 8 Jul 2025 17:34:25 -0700
Subject: [PATCH] float-h: work around GCC bug 120993
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Also, fix a too-low LDBL_MAX on this platform, a problem that
contributed to LDBL_NORM_MAX test failures with C23 PowerPC GCC
with ibmlongdouble.
* lib/float.c (gl_LDBL_MAX): On PowerPC with ibmlongdouble, the
correct value is 2**1024 - 2**918, not 2**1024 - 2**971.  Also,
use C99 hex double notation for the divisor, as it’s clearer and
should work nowadays.
* lib/float.in.h: Simplify by not worring whether it’s AIX or
GNU/Linux when redefining LDBL_* macros on PowerPC GCC with
ibmlongdouble, as the problem seems to be universal then.
(LDBL_NORM_MAX): On PowerPC GCC with ibmlongdouble, define to be
LDBL_MAX, so that it’s 2**1024 - 2**918, doubling GCC’s value
if supplied.
* modules/float-h-tests (Depends-on): Remove floorl, ldexpl.
Add truncl.
(test_float_h_LDADD): Likewise for libraries.
* tests/test-float-h.c (test_isfinitel): New static function,
so that we need not rely on isfinitel.
(normalize_long_double): Work even if X is negative or not finite.
Do not rely on frexpl or ldexpl.  Use truncl instead of floorl
so that the negative results are consistent with positive.
Defend against fritzy PowerPC long double arithmetic.
---
 ChangeLog                    | 26 +++++++++++++++++++
 doc/posix-headers/float.texi |  2 +-
 lib/float.c                  |  2 +-
 lib/float.in.h               | 50 ++++++++++++++++--------------------
 modules/float-h-tests        |  5 ++--
 tests/test-float-h.c         | 36 ++++++++++++++++++--------
 6 files changed, 77 insertions(+), 44 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index f52a907487..7913e25423 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,29 @@
+2025-07-08  Paul Eggert  <egg...@cs.ucla.edu>
+
+	float-h: work around GCC bug 120993
+	Also, fix a too-low LDBL_MAX on this platform, a problem that
+	contributed to LDBL_NORM_MAX test failures with C23 PowerPC GCC
+	with ibmlongdouble.
+	* lib/float.c (gl_LDBL_MAX): On PowerPC with ibmlongdouble, the
+	correct value is 2**1024 - 2**918, not 2**1024 - 2**971.  Also,
+	use C99 hex double notation for the divisor, as it’s clearer and
+	should work nowadays.
+	* lib/float.in.h: Simplify by not worring whether it’s AIX or
+	GNU/Linux when redefining LDBL_* macros on PowerPC GCC with
+	ibmlongdouble, as the problem seems to be universal then.
+	(LDBL_NORM_MAX): On PowerPC GCC with ibmlongdouble, define to be
+	LDBL_MAX, so that it’s 2**1024 - 2**918, doubling GCC’s value
+	if supplied.
+	* modules/float-h-tests (Depends-on): Remove floorl, ldexpl.
+	Add truncl.
+	(test_float_h_LDADD): Likewise for libraries.
+	* tests/test-float-h.c (test_isfinitel): New static function,
+	so that we need not rely on isfinitel.
+	(normalize_long_double): Work even if X is negative or not finite.
+	Do not rely on frexpl or ldexpl.  Use truncl instead of floorl
+	so that the negative results are consistent with positive.
+	Defend against fritzy PowerPC long double arithmetic.
+
 2025-07-08  Collin Funk  <collin.fu...@gmail.com>
 
 	options: Add reminder to include <config.h>.
diff --git a/doc/posix-headers/float.texi b/doc/posix-headers/float.texi
index 14e321fec4..1fbdd29114 100644
--- a/doc/posix-headers/float.texi
+++ b/doc/posix-headers/float.texi
@@ -19,7 +19,7 @@ On OpenBSD 4.0 and MirBSD 10, they are the same as the values of the
 @samp{double}.
 On FreeBSD/x86 6.4, they represent the incorrect 53-bit precision assumptions
 in the compiler, not the real 64-bit precision at runtime.
-On Linux/PowerPC with GCC 4.4, and on AIX 7.1 with GCC 4.2,
+On PowerPC with gcc 15 and the default @option{-mabi=ibmlongdouble},
 they don't reflect the ``double double'' representation of @code{long double}
 correctly.
 @item
diff --git a/lib/float.c b/lib/float.c
index 194823eee4..780a0d1ca6 100644
--- a/lib/float.c
+++ b/lib/float.c
@@ -23,7 +23,7 @@
 #if GNULIB_defined_long_double_union
 # if (defined _ARCH_PPC || defined _POWER) && (defined _AIX || defined __linux__) && (LDBL_MANT_DIG == 106) && defined __GNUC__
 const union gl_long_double_union gl_LDBL_MAX =
-  { { DBL_MAX, DBL_MAX / (double)134217728UL / (double)134217728UL } };
+  { { DBL_MAX, DBL_MAX / 0x1p53 } };
 # elif defined __i386__
 const union gl_long_double_union gl_LDBL_MAX =
   { { 0xFFFFFFFF, 0xFFFFFFFF, 32766 } };
diff --git a/lib/float.in.h b/lib/float.in.h
index 4759e83478..8d93f5eeaf 100644
--- a/lib/float.in.h
+++ b/lib/float.in.h
@@ -113,44 +113,38 @@ extern const union gl_long_double_union gl_LDBL_MAX;
 # define LDBL_MAX_10_EXP 4932
 #endif
 
-/* On AIX 7.1 with gcc 4.2, the values of LDBL_MIN_EXP, LDBL_MIN, LDBL_MAX are
-   wrong.
-   On Linux/PowerPC with gcc 4.4, the value of LDBL_MAX is wrong.  */
-#if (defined _ARCH_PPC || defined _POWER) && defined _AIX && (LDBL_MANT_DIG == 106) && defined __GNUC__
+/* On PowerPC with gcc 15 when using __ibm128 long double, the value of
+   LDBL_MIN_EXP, LDBL_MIN, LDBL_MAX, and LDBL_NORM_MAX are wrong.  */
+#if ((defined _ARCH_PPC || defined _POWER) && LDBL_MANT_DIG == 106 \
+     && defined __GNUC__)
 # undef LDBL_MIN_EXP
 # define LDBL_MIN_EXP DBL_MIN_EXP
 # undef LDBL_MIN_10_EXP
 # define LDBL_MIN_10_EXP DBL_MIN_10_EXP
 # undef LDBL_MIN
 # define LDBL_MIN 2.22507385850720138309023271733240406422e-308L /* DBL_MIN = 2^-1022 */
-#endif
-#if (defined _ARCH_PPC || defined _POWER) && (defined _AIX || defined __linux__) && (LDBL_MANT_DIG == 106) && defined __GNUC__ && !defined __LDBL_NORM_MAX__
 # undef LDBL_MAX
-/* LDBL_MAX is represented as { 0x7FEFFFFF, 0xFFFFFFFF, 0x7C8FFFFF, 0xFFFFFFFF }.
-   It is not easy to define:
-     #define LDBL_MAX 1.79769313486231580793728971405302307166e308L
-   is too small, whereas
-     #define LDBL_MAX 1.79769313486231580793728971405302307167e308L
-   is too large.  Apparently a bug in GCC decimal-to-binary conversion.
-   Also, I can't get values larger than
-     #define LDBL63 ((long double) (1ULL << 63))
-     #define LDBL882 (LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63)
-     #define LDBL945 (LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63)
-     #define LDBL1008 (LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63 * LDBL63)
-     #define LDBL_MAX (LDBL1008 * 65535.0L + LDBL945 * (long double) 9223372036821221375ULL + LDBL882 * (long double) 4611686018427387904ULL)
-   which is represented as { 0x7FEFFFFF, 0xFFFFFFFF, 0x7C8FFFFF, 0xF8000000 }.
-   So, define it like this through a reference to an external variable
+/* LDBL_MAX is 2**1024 - 2**918, represented as: { 0x7FEFFFFF, 0xFFFFFFFF,
+                                                   0x7C9FFFFF, 0xFFFFFFFF }.
+
+   Do not write it as a constant expression, as GCC would likely treat
+   that as infinity due to the vagaries of this platform's funky arithmetic.
+   Instead, define it through a reference to an external variable.
+   Like the following, but using a union to avoid type mismatches:
 
-     const double LDBL_MAX[2] = { DBL_MAX, DBL_MAX / (double)134217728UL / (double)134217728UL };
+     const double LDBL_MAX[2] = { DBL_MAX, DBL_MAX / 0x1p53 };
      extern const long double LDBL_MAX;
 
-   or through a pointer cast
+   The following alternative would not work as well when GCC is optimizing:
+
+     #define LDBL_MAX (*(long double const *) (double[])
+                       { DBL_MAX, DBL_MAX / 0x1p53 })
+
+   The following alternative would require GCC 6 or later:
 
-     #define LDBL_MAX \
-       (*(const long double *) (double[]) { DBL_MAX, DBL_MAX / (double)134217728UL / (double)134217728UL })
+     #define LDBL_MAX __builtin_pack_longdouble (DBL_MAX, DBL_MAX / 0x1p53)
 
-   Unfortunately, this is not a constant expression, and the latter expression
-   does not work well when GCC is optimizing..  */
+   Unfortunately none of the alternatives are constant expressions.  */
 # if !GNULIB_defined_long_double_union
 union gl_long_double_union
   {
@@ -161,6 +155,8 @@ union gl_long_double_union
 # endif
 extern const union gl_long_double_union gl_LDBL_MAX;
 # define LDBL_MAX (gl_LDBL_MAX.ld)
+# undef LDBL_NORM_MAX
+# define LDBL_NORM_MAX LDBL_MAX
 #endif
 
 /* On IRIX 6.5, with cc, the value of LDBL_MANT_DIG is wrong.
@@ -326,8 +322,6 @@ extern gl_DBL_SNAN_t gl_DBL_SNAN;
 #ifndef LDBL_NORM_MAX
 # ifdef __LDBL_NORM_MAX__
 #  define LDBL_NORM_MAX __LDBL_NORM_MAX__
-# elif FLT_RADIX == 2 && LDBL_MAX_EXP == 1024 && LDBL_MANT_DIG == 106
-#  define LDBL_NORM_MAX 8.98846567431157953864652595394501E+307L
 # else
 #  define LDBL_NORM_MAX LDBL_MAX
 # endif
diff --git a/modules/float-h-tests b/modules/float-h-tests
index 3ac3532501..5828f36632 100644
--- a/modules/float-h-tests
+++ b/modules/float-h-tests
@@ -5,12 +5,11 @@ tests/macros.h
 Depends-on:
 fpieee
 fpucw
-floorl
 frexpl
-ldexpl
 isnanf-nolibm
 isnand-nolibm
 isnanl-nolibm
+truncl
 float-h-c++-tests
 
 configure.ac:
@@ -18,4 +17,4 @@ configure.ac:
 Makefile.am:
 TESTS += test-float-h
 check_PROGRAMS += test-float-h
-test_float_h_LDADD = $(LDADD) @FLOORL_LIBM@ @FREXPL_LIBM@ @LDEXPL_LIBM@
+test_float_h_LDADD = $(LDADD) @FREXPL_LIBM@ @TRUNCL_LIBM@
diff --git a/tests/test-float-h.c b/tests/test-float-h.c
index 1e294c87e0..165278b295 100644
--- a/tests/test-float-h.c
+++ b/tests/test-float-h.c
@@ -398,22 +398,36 @@ test_double (void)
 
 /* -------------------- Check macros for 'long double' -------------------- */
 
-/* Return X after normalization.  This makes a difference on PowerPC
-   platforms where long double uses a "double double" format that does
-   not conform to the C23 rules for floating point.  On such a platform,
-   FLT_RADIX = 2, LDBL_MANT_DIG = 106, LDBL_EPSILON = 2**-105, and
-   1 < 1 + e < 1 + LDBL_EPSILON, where e = 2**-106 and 1 + e is a
-   representable long double value that is not normalized.
-   On such a platform, normalize_long_double (1 + e) returns 1.  */
+static int
+test_isfinitel (long double volatile x)
+{
+  if (x != x)
+    return 0;
+  long double volatile zero = x * 0;
+  return zero == 0;
+}
+
+/* Return X after normalization.  This makes a difference on platforms
+   where long double can represent unnormalized values.  For example,
+   suppose x = 1 + 2**-106 on PowerPC with IBM long double where
+   FLT_RADIX = 2, LDBL_MANT_DIG = 106, and LDBL_EPSILON = 2**-105.
+   Then 1 < x < 1 + LDBL_EPSILON, and normalize_long_double (x) returns 1.  */
 static long double
 normalize_long_double (long double volatile x)
 {
-  if (FLT_RADIX == 2)
+  if (FLT_RADIX == 2 && test_isfinitel (x))
     {
       int xexp;
-      long double volatile xfrac = frexpl (x, &xexp);
-      x = ldexpl (floorl (ldexpl (xfrac, LDBL_MANT_DIG)),
-                  xexp - LDBL_MANT_DIG);
+      long double volatile
+        frac = frexpl (x, &xexp),
+        significand = frac * pow2l (LDBL_MANT_DIG),
+        normalized_significand = truncl (significand),
+        normalized_x = normalized_significand * pow2l (xexp - LDBL_MANT_DIG);
+
+      /* The test_isfinitel defends against PowerPC with IBM long double,
+         which fritzes out near LDBL_MAX.  */
+      if (test_isfinitel (normalized_x))
+        x = normalized_x;
     }
   else
     {
-- 
2.48.1

Reply via email to