From: Paul Eggert Date: Wed, 9 Jul 2025 00:34:25 +0000 (-0700) Subject: float-h: work around GCC bug 120993 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6164b4cb0887b5331a4e64449107decd37d32735;p=thirdparty%2Fgnulib.git 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. --- diff --git a/ChangeLog b/ChangeLog index f52a907487..7913e25423 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2025-07-08 Paul Eggert + + 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 options: Add reminder to include . 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 {