Make tst-strtod-underflow type-generic

The test tst-strtod-underflow covers various edge cases close to the
underflow threshold for strtod (especially cases where underflow on
architectures with after-rounding tininess detection depends on the
rounding mode).  Make it use the type-generic machinery, with
corresponding test inputs for each supported floating-point format, so
that other functions in the strtod family are tested for underflow
edge cases as well.

Tested for x86_64.
This commit is contained in:
Joseph Myers 2024-09-20 23:25:32 +00:00
parent 378039ca57
commit 94ca2c0894

View File

@ -17,6 +17,10 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
/* Defining _LIBC_TEST ensures long double math functions are
declared in the headers. */
#define _LIBC_TEST 1
#define __STDC_WANT_IEC_60559_TYPES_EXT__
#include <errno.h> #include <errno.h>
#include <fenv.h> #include <fenv.h>
#include <float.h> #include <float.h>
@ -25,6 +29,60 @@
#include <stdlib.h> #include <stdlib.h>
#include <tininess.h> #include <tininess.h>
#include "tst-strtod.h"
/* Logic for selecting between tests for different formats is as in
tst-strtod-skeleton.c, but here it is selecting string inputs with
different underflow properties, rather than generated test
data. */
#define _CONCAT(a, b) a ## b
#define CONCAT(a, b) _CONCAT (a, b)
#define MEMBER(FSUF, FTYPE, FTOSTR, LSUF, CSUF) \
const char *s_ ## FSUF;
#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024
# define CHOOSE_ld(f,d,...) d
#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && LDBL_MIN_EXP == -16381
# define CHOOSE_ld(f,d,ld64i,...) ld64i
#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && LDBL_MIN_EXP == -16382
# define CHOOSE_ld(f,d,ld64i,ld64m,...) ld64m
#elif LDBL_MANT_DIG == 106 && LDBL_MAX_EXP == 1024
# define CHOOSE_ld(f,d,ld64i,ld64m,ld106,...) ld106
#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384
# define CHOOSE_ld(f,d,ld64i,ld64m,ld106,ld113,...) ld113
#else
# error "unknown long double format"
#endif
#define CHOOSE_f(f,...) f
#define CHOOSE_f32(f,...) f
#define CHOOSE_d(f,d,...) d
#define CHOOSE_f64(f,d,...) d
#define CHOOSE_f32x(f,d,...) d
#define CHOOSE_f128(f,d,ld64i,ld64m,ld106,ld113,...) ld113
#if __HAVE_FLOAT64X
# if FLT64X_MANT_DIG == 113 && FLT64X_MAX_EXP == 16384
# define CHOOSE_f64x(f,d,ld64i,ld64m,ld106,ld113,...) ld113
# elif (FLT64X_MANT_DIG == 64 \
&& FLT64X_MAX_EXP == 16384 \
&& FLT64X_MIN_EXP == -16381)
# define CHOOSE_f64x(f,d,ld64i,...) ld64i
# else
# error "unknown _Float64x format"
# endif
#endif
#define _XNTRY(FSUF, FTYPE, FTOSTR, LSUF, CSUF, ...) \
CHOOSE_ ## FSUF (__VA_ARGS__),
#define XNTRY(...) \
GEN_TEST_STRTOD_FOREACH (_XNTRY, __VA_ARGS__)
#define TEST(f, d, ld64i, ld64m, ld106, ld113, u) \
{ XNTRY(f, d, ld64i, ld64m, ld106, ld113) u }
enum underflow_case enum underflow_case
{ {
/* Result is exact or outside the subnormal range. */ /* Result is exact or outside the subnormal range. */
@ -55,38 +113,194 @@ enum underflow_case
struct test struct test
{ {
const char *s; GEN_TEST_STRTOD_FOREACH (MEMBER)
enum underflow_case c; enum underflow_case c;
}; };
static const struct test tests[] = static const struct test tests[] =
{ {
{ "0x1p-1022", UNDERFLOW_NONE }, TEST ("0x1p-126",
{ "-0x1p-1022", UNDERFLOW_NONE }, "0x1p-1022",
{ "0x0p-10000000000000000000000000", UNDERFLOW_NONE }, "0x1p-16382",
{ "-0x0p-10000000000000000000000000", UNDERFLOW_NONE }, "0x1p-16383",
{ "0x1p-10000000000000000000000000", UNDERFLOW_ALWAYS }, "0x1p-969",
{ "-0x1p-10000000000000000000000000", UNDERFLOW_ALWAYS }, "0x1p-16382",
{ "0x1.000000000000000000001p-1022", UNDERFLOW_NONE }, UNDERFLOW_NONE),
{ "-0x1.000000000000000000001p-1022", UNDERFLOW_NONE }, TEST ("-0x1p-126",
{ "0x1p-1075", UNDERFLOW_ALWAYS }, "-0x1p-1022",
{ "-0x1p-1075", UNDERFLOW_ALWAYS }, "-0x1p-16382",
{ "0x1p-1023", UNDERFLOW_NONE }, "-0x1p-16383",
{ "-0x1p-1023", UNDERFLOW_NONE }, "-0x1p-969",
{ "0x1p-1074", UNDERFLOW_NONE }, "-0x1p-16382",
{ "-0x1p-1074", UNDERFLOW_NONE }, UNDERFLOW_NONE),
{ "0x1.ffffffffffffep-1023", UNDERFLOW_NONE }, TEST ("0x0p-10000000000000000000000000",
{ "-0x1.ffffffffffffep-1023", UNDERFLOW_NONE }, "0x0p-10000000000000000000000000",
{ "0x1.fffffffffffffp-1023", UNDERFLOW_ALWAYS }, "0x0p-10000000000000000000000000",
{ "-0x1.fffffffffffffp-1023", UNDERFLOW_ALWAYS }, "0x0p-10000000000000000000000000",
{ "0x1.fffffffffffff0001p-1023", UNDERFLOW_EXCEPT_UPWARD }, "0x0p-10000000000000000000000000",
{ "-0x1.fffffffffffff0001p-1023", UNDERFLOW_EXCEPT_DOWNWARD }, "0x0p-10000000000000000000000000",
{ "0x1.fffffffffffff7fffp-1023", UNDERFLOW_EXCEPT_UPWARD }, UNDERFLOW_NONE),
{ "-0x1.fffffffffffff7fffp-1023", UNDERFLOW_EXCEPT_DOWNWARD }, TEST ("-0x0p-10000000000000000000000000",
{ "0x1.fffffffffffff8p-1023", UNDERFLOW_ONLY_DOWNWARD_ZERO }, "-0x0p-10000000000000000000000000",
{ "-0x1.fffffffffffff8p-1023", UNDERFLOW_ONLY_UPWARD_ZERO }, "-0x0p-10000000000000000000000000",
{ "0x1.fffffffffffffffffp-1023", UNDERFLOW_ONLY_DOWNWARD_ZERO }, "-0x0p-10000000000000000000000000",
{ "-0x1.fffffffffffffffffp-1023", UNDERFLOW_ONLY_UPWARD_ZERO }, "-0x0p-10000000000000000000000000",
"-0x0p-10000000000000000000000000",
UNDERFLOW_NONE),
TEST ("0x1p-10000000000000000000000000",
"0x1p-10000000000000000000000000",
"0x1p-10000000000000000000000000",
"0x1p-10000000000000000000000000",
"0x1p-10000000000000000000000000",
"0x1p-10000000000000000000000000",
UNDERFLOW_ALWAYS),
TEST ("-0x1p-10000000000000000000000000",
"-0x1p-10000000000000000000000000",
"-0x1p-10000000000000000000000000",
"-0x1p-10000000000000000000000000",
"-0x1p-10000000000000000000000000",
"-0x1p-10000000000000000000000000",
UNDERFLOW_ALWAYS),
TEST ("0x1.000000000000000000001p-126",
"0x1.000000000000000000001p-1022",
"0x1.000000000000000000001p-16382",
"0x1.000000000000000000001p-16383",
"0x1.000000000000000000001p-969",
"0x1.00000000000000000000000000000000000000001p-16382",
UNDERFLOW_NONE),
TEST ("-0x1.000000000000000000001p-126",
"-0x1.000000000000000000001p-1022",
"-0x1.000000000000000000001p-16382",
"-0x1.000000000000000000001p-16383",
"-0x1.000000000000000000001p-969",
"-0x1.00000000000000000000000000000000000000001p-16382",
UNDERFLOW_NONE),
TEST ("0x1p-150",
"0x1p-1075",
"0x1p-16446",
"0x1p-16447",
"0x1p-1075",
"0x1p-16495",
UNDERFLOW_ALWAYS),
TEST ("-0x1p-150",
"-0x1p-1075",
"-0x1p-16446",
"-0x1p-16447",
"-0x1p-1075",
"-0x1p-16495",
UNDERFLOW_ALWAYS),
TEST ("0x1p-127",
"0x1p-1023",
"0x1p-16383",
"0x1p-16384",
"0x1p-970",
"0x1p-16383",
UNDERFLOW_NONE),
TEST ("-0x1p-127",
"-0x1p-1023",
"-0x1p-16383",
"-0x1p-16384",
"-0x1p-970",
"-0x1p-16383",
UNDERFLOW_NONE),
TEST ("0x1p-149",
"0x1p-1074",
"0x1p-16445",
"0x1p-16446",
"0x1p-1074",
"0x1p-16494",
UNDERFLOW_NONE),
TEST ("-0x1p-149",
"-0x1p-1074",
"-0x1p-16445",
"-0x1p-16446",
"-0x1p-1074",
"-0x1p-16494",
UNDERFLOW_NONE),
TEST ("0x1.fffffcp-127",
"0x1.ffffffffffffep-1023",
"0x1.fffffffffffffffcp-16383",
"0x1.fffffffffffffffcp-16384",
"0x1.ffffffffffffffffffffffffffp-970",
"0x1.fffffffffffffffffffffffffffep-16383",
UNDERFLOW_NONE),
TEST ("-0x1.fffffcp-127",
"-0x1.ffffffffffffep-1023",
"-0x1.fffffffffffffffcp-16383",
"-0x1.fffffffffffffffcp-16384",
"-0x1.ffffffffffffffffffffffffffp-970",
"-0x1.fffffffffffffffffffffffffffep-16383",
UNDERFLOW_NONE),
TEST ("0x1.fffffep-127",
"0x1.fffffffffffffp-1023",
"0x1.fffffffffffffffep-16383",
"0x1.fffffffffffffffep-16384",
"0x1.ffffffffffffffffffffffffff8p-970",
"0x1.ffffffffffffffffffffffffffffp-16383",
UNDERFLOW_ALWAYS),
TEST ("-0x1.fffffep-127",
"-0x1.fffffffffffffp-1023",
"-0x1.fffffffffffffffep-16383",
"-0x1.fffffffffffffffep-16384",
"-0x1.ffffffffffffffffffffffffff8p-970",
"-0x1.ffffffffffffffffffffffffffffp-16383",
UNDERFLOW_ALWAYS),
TEST ("0x1.fffffe0001p-127",
"0x1.fffffffffffff0001p-1023",
"0x1.fffffffffffffffe0001p-16383",
"0x1.fffffffffffffffe0001p-16384",
"0x1.ffffffffffffffffffffffffff80001p-970",
"0x1.ffffffffffffffffffffffffffff0001p-16383",
UNDERFLOW_EXCEPT_UPWARD),
TEST ("-0x1.fffffe0001p-127",
"-0x1.fffffffffffff0001p-1023",
"-0x1.fffffffffffffffe0001p-16383",
"-0x1.fffffffffffffffe0001p-16384",
"-0x1.ffffffffffffffffffffffffff80001p-970",
"-0x1.ffffffffffffffffffffffffffff0001p-16383",
UNDERFLOW_EXCEPT_DOWNWARD),
TEST ("0x1.fffffeffffp-127",
"0x1.fffffffffffff7fffp-1023",
"0x1.fffffffffffffffeffffp-16383",
"0x1.fffffffffffffffeffffp-16384",
"0x1.ffffffffffffffffffffffffffbffffp-970",
"0x1.ffffffffffffffffffffffffffff7fffp-16383",
UNDERFLOW_EXCEPT_UPWARD),
TEST ("-0x1.fffffeffffp-127",
"-0x1.fffffffffffff7fffp-1023",
"-0x1.fffffffffffffffeffffp-16383",
"-0x1.fffffffffffffffeffffp-16384",
"-0x1.ffffffffffffffffffffffffffbffffp-970",
"-0x1.ffffffffffffffffffffffffffff7fffp-16383",
UNDERFLOW_EXCEPT_DOWNWARD),
TEST ("0x1.ffffffp-127",
"0x1.fffffffffffff8p-1023",
"0x1.ffffffffffffffffp-16383",
"0x1.ffffffffffffffffp-16384",
"0x1.ffffffffffffffffffffffffffcp-970",
"0x1.ffffffffffffffffffffffffffff8p-16383",
UNDERFLOW_ONLY_DOWNWARD_ZERO),
TEST ("-0x1.ffffffp-127",
"-0x1.fffffffffffff8p-1023",
"-0x1.ffffffffffffffffp-16383",
"-0x1.ffffffffffffffffp-16384",
"-0x1.ffffffffffffffffffffffffffcp-970",
"-0x1.ffffffffffffffffffffffffffff8p-16383",
UNDERFLOW_ONLY_UPWARD_ZERO),
TEST ("0x1.ffffffffffp-127",
"0x1.fffffffffffffffffp-1023",
"0x1.ffffffffffffffffffffp-16383",
"0x1.ffffffffffffffffffffp-16384",
"0x1.ffffffffffffffffffffffffffffffp-970",
"0x1.ffffffffffffffffffffffffffffffffp-16383",
UNDERFLOW_ONLY_DOWNWARD_ZERO),
TEST ("-0x1.ffffffffffp-127",
"-0x1.fffffffffffffffffp-1023",
"-0x1.ffffffffffffffffffffp-16383",
"-0x1.ffffffffffffffffffffp-16384",
"-0x1.ffffffffffffffffffffffffffffffp-970",
"-0x1.ffffffffffffffffffffffffffffffffp-16383",
UNDERFLOW_ONLY_UPWARD_ZERO),
}; };
/* Return whether to expect underflow from a particular testcase, in a /* Return whether to expect underflow from a particular testcase, in a
@ -133,39 +347,62 @@ static bool support_underflow_exception = false;
volatile double d = DBL_MIN; volatile double d = DBL_MIN;
volatile double dd; volatile double dd;
static int static bool
test_in_one_mode (const char *s, enum underflow_case c, int rm, test_got_fe_underflow (void)
const char *mode_name)
{ {
int result = 0;
feclearexcept (FE_ALL_EXCEPT);
errno = 0;
double d = strtod (s, NULL);
int got_errno = errno;
#ifdef FE_UNDERFLOW #ifdef FE_UNDERFLOW
bool got_fe_underflow = fetestexcept (FE_UNDERFLOW) != 0; return fetestexcept (FE_UNDERFLOW) != 0;
#else #else
bool got_fe_underflow = false; return false;
#endif #endif
printf ("strtod (%s) (%s) returned %a, errno = %d, %sunderflow exception\n",
s, mode_name, d, got_errno, got_fe_underflow ? "" : "no ");
bool this_expect_underflow = expect_underflow (c, rm);
if (got_errno != 0 && got_errno != ERANGE)
{
puts ("FAIL: errno neither 0 nor ERANGE");
result = 1;
} }
else if (this_expect_underflow != (errno == ERANGE))
{ #define TEST_STRTOD(FSUF, FTYPE, FTOSTR, LSUF, CSUF) \
puts ("FAIL: underflow from errno differs from expectations"); static int \
result = 1; test_strto ## FSUF (int i, int rm, const char *mode_name) \
{ \
const char *s = tests[i].s_ ## FSUF; \
enum underflow_case c = tests[i].c; \
int result = 0; \
feclearexcept (FE_ALL_EXCEPT); \
errno = 0; \
FTYPE d = strto ## FSUF (s, NULL); \
int got_errno = errno; \
bool got_fe_underflow = test_got_fe_underflow (); \
char buf[FSTRLENMAX]; \
FTOSTR (buf, sizeof (buf), "%a", d); \
printf ("strto" #FSUF \
" (%s) (%s) returned %s, errno = %d, " \
"%sunderflow exception\n", \
s, mode_name, buf, got_errno, \
got_fe_underflow ? "" : "no "); \
bool this_expect_underflow = expect_underflow (c, rm); \
if (got_errno != 0 && got_errno != ERANGE) \
{ \
puts ("FAIL: errno neither 0 nor ERANGE"); \
result = 1; \
} \
else if (this_expect_underflow != (errno == ERANGE)) \
{ \
puts ("FAIL: underflow from errno differs from expectations"); \
result = 1; \
} \
if (support_underflow_exception \
&& got_fe_underflow != this_expect_underflow) \
{ \
puts ("FAIL: underflow from exceptions " \
"differs from expectations"); \
result = 1; \
} \
return result; \
} }
if (support_underflow_exception && got_fe_underflow != this_expect_underflow)
GEN_TEST_STRTOD_FOREACH (TEST_STRTOD)
static int
test_in_one_mode (size_t i, int rm, const char *mode_name)
{ {
puts ("FAIL: underflow from exceptions differs from expectations"); return STRTOD_TEST_FOREACH (test_strto, i, rm, mode_name);
result = 1;
}
return result;
} }
static int static int
@ -191,12 +428,12 @@ do_test (void)
#endif #endif
for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++) for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++)
{ {
result |= test_in_one_mode (tests[i].s, tests[i].c, fe_tonearest, result |= test_in_one_mode (i, fe_tonearest,
"default rounding mode"); "default rounding mode");
#ifdef FE_DOWNWARD #ifdef FE_DOWNWARD
if (!fesetround (FE_DOWNWARD)) if (!fesetround (FE_DOWNWARD))
{ {
result |= test_in_one_mode (tests[i].s, tests[i].c, FE_DOWNWARD, result |= test_in_one_mode (i, FE_DOWNWARD,
"FE_DOWNWARD"); "FE_DOWNWARD");
fesetround (save_round_mode); fesetround (save_round_mode);
} }
@ -204,7 +441,7 @@ do_test (void)
#ifdef FE_TOWARDZERO #ifdef FE_TOWARDZERO
if (!fesetround (FE_TOWARDZERO)) if (!fesetround (FE_TOWARDZERO))
{ {
result |= test_in_one_mode (tests[i].s, tests[i].c, FE_TOWARDZERO, result |= test_in_one_mode (i, FE_TOWARDZERO,
"FE_TOWARDZERO"); "FE_TOWARDZERO");
fesetround (save_round_mode); fesetround (save_round_mode);
} }
@ -212,7 +449,7 @@ do_test (void)
#ifdef FE_UPWARD #ifdef FE_UPWARD
if (!fesetround (FE_UPWARD)) if (!fesetround (FE_UPWARD))
{ {
result |= test_in_one_mode (tests[i].s, tests[i].c, FE_UPWARD, result |= test_in_one_mode (i, FE_UPWARD,
"FE_UPWARD"); "FE_UPWARD");
fesetround (save_round_mode); fesetround (save_round_mode);
} }