diff options
author | Joseph Myers <joseph@codesourcery.com> | 2012-10-30 13:51:27 +0000 |
---|---|---|
committer | Joseph Myers <joseph@codesourcery.com> | 2012-10-30 13:51:27 +0000 |
commit | 2a27fd6dae3edec949deda9a55928a0e22c8a8ae (patch) | |
tree | 9c9fcc6c86b0f7c04454a5833d5efbe17dc79117 /stdlib/tst-strtod-underflow.c | |
parent | e5088dc6870b072a263f207af9e410c82f80a09e (diff) | |
download | glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar.gz glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar.xz glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.zip |
Fix strtod handling of underflow (bug 14047).
Diffstat (limited to 'stdlib/tst-strtod-underflow.c')
-rw-r--r-- | stdlib/tst-strtod-underflow.c | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/stdlib/tst-strtod-underflow.c b/stdlib/tst-strtod-underflow.c new file mode 100644 index 0000000000..892ef158ac --- /dev/null +++ b/stdlib/tst-strtod-underflow.c @@ -0,0 +1,225 @@ +/* Test for strtod handling of arguments that may cause floating-point + underflow. + Copyright (C) 2012 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <fenv.h> +#include <float.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <tininess.h> + +enum underflow_case + { + /* Result is exact or outside the subnormal range. */ + UNDERFLOW_NONE, + /* Result has magnitude at most half way between the largest + subnormal value and the smallest positive normal value, and is + not exact, so underflows in all rounding modes and independent + of how tininess is detected. */ + UNDERFLOW_ALWAYS, + /* Result is positive, with magnitude larger than half way between + the largest subnormal value and the least positive normal + value, but would underflow when rounded to nearest to normal + precision, so underflows after rounding in all modes except + rounding upward. */ + UNDERFLOW_EXCEPT_UPWARD, + /* Likewise, for a negative result, underflowing after rounding + except when rounding downward. */ + UNDERFLOW_EXCEPT_DOWNWARD, + /* Result is positive, with magnitude at least three quarters of + the way from the largest subnormal value to the smallest + positive normal value, so underflows after rounding only when + rounding downward or toward zero. */ + UNDERFLOW_ONLY_DOWNWARD_ZERO, + /* Likewise, for a negative result, underflowing after rounding + only when rounding upward or toward zero. */ + UNDERFLOW_ONLY_UPWARD_ZERO, + }; + +struct test +{ + const char *s; + enum underflow_case c; +}; + +static const struct test tests[] = + { + { "0x1p-1022", UNDERFLOW_NONE }, + { "-0x1p-1022", UNDERFLOW_NONE }, + { "0x0p-10000000000000000000000000", UNDERFLOW_NONE }, + { "-0x0p-10000000000000000000000000", UNDERFLOW_NONE }, + { "0x1p-10000000000000000000000000", UNDERFLOW_ALWAYS }, + { "-0x1p-10000000000000000000000000", UNDERFLOW_ALWAYS }, + { "0x1.000000000000000000001p-1022", UNDERFLOW_NONE }, + { "-0x1.000000000000000000001p-1022", UNDERFLOW_NONE }, + { "0x1p-1075", UNDERFLOW_ALWAYS }, + { "-0x1p-1075", UNDERFLOW_ALWAYS }, + { "0x1p-1023", UNDERFLOW_NONE }, + { "-0x1p-1023", UNDERFLOW_NONE }, + { "0x1p-1074", UNDERFLOW_NONE }, + { "-0x1p-1074", UNDERFLOW_NONE }, + { "0x1.ffffffffffffep-1023", UNDERFLOW_NONE }, + { "-0x1.ffffffffffffep-1023", UNDERFLOW_NONE }, + { "0x1.fffffffffffffp-1023", UNDERFLOW_ALWAYS }, + { "-0x1.fffffffffffffp-1023", UNDERFLOW_ALWAYS }, + { "0x1.fffffffffffff0001p-1023", UNDERFLOW_EXCEPT_UPWARD }, + { "-0x1.fffffffffffff0001p-1023", UNDERFLOW_EXCEPT_DOWNWARD }, + { "0x1.fffffffffffff7fffp-1023", UNDERFLOW_EXCEPT_UPWARD }, + { "-0x1.fffffffffffff7fffp-1023", UNDERFLOW_EXCEPT_DOWNWARD }, + { "0x1.fffffffffffff8p-1023", UNDERFLOW_ONLY_DOWNWARD_ZERO }, + { "-0x1.fffffffffffff8p-1023", UNDERFLOW_ONLY_UPWARD_ZERO }, + { "0x1.fffffffffffffffffp-1023", UNDERFLOW_ONLY_DOWNWARD_ZERO }, + { "-0x1.fffffffffffffffffp-1023", UNDERFLOW_ONLY_UPWARD_ZERO }, + }; + +/* Return whether to expect underflow from a particular testcase, in a + given rounding mode. */ + +static bool +expect_underflow (enum underflow_case c, int rm) +{ + if (c == UNDERFLOW_NONE) + return false; + if (c == UNDERFLOW_ALWAYS) + return true; + if (TININESS_AFTER_ROUNDING) + { + switch (rm) + { +#ifdef FE_DOWNWARD + case FE_DOWNWARD: + return (c == UNDERFLOW_EXCEPT_UPWARD + || c == UNDERFLOW_ONLY_DOWNWARD_ZERO); +#endif + +#ifdef FE_TOWARDZERO + case FE_TOWARDZERO: + return true; +#endif + +#ifdef FE_UPWARD + case FE_UPWARD: + return (c == UNDERFLOW_EXCEPT_DOWNWARD + || c == UNDERFLOW_ONLY_UPWARD_ZERO); +#endif + + default: + return (c == UNDERFLOW_EXCEPT_UPWARD + || c == UNDERFLOW_EXCEPT_DOWNWARD); + } + } + else + return true; +} + +static bool support_underflow_exception = false; +volatile double d = DBL_MIN; +volatile double dd; + +static int +test_in_one_mode (const char *s, enum underflow_case c, int rm, + 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 + bool got_fe_underflow = fetestexcept (FE_UNDERFLOW) != 0; +#else + bool got_fe_underflow = false; +#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)) + { + 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; +} + +static int +do_test (void) +{ + int save_round_mode = fegetround (); + int result = 0; +#ifdef FE_TONEAREST + const int fe_tonearest = FE_TONEAREST; +#else + const int fe_tonearest = 0; +# if defined FE_DOWNWARD || defined FE_TOWARDZERO || defined FE_UPWARD +# error "FE_TONEAREST not defined, but another rounding mode is" +# endif +#endif +#ifdef FE_UNDERFLOW + feclearexcept (FE_ALL_EXCEPT); + dd = d * d; + if (fetestexcept (FE_UNDERFLOW)) + support_underflow_exception = true; + else + puts ("underflow exception not supported at runtime, only testing errno"); +#endif + 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, + "default rounding mode"); +#ifdef FE_DOWNWARD + if (!fesetround (FE_DOWNWARD)) + { + result |= test_in_one_mode (tests[i].s, tests[i].c, FE_DOWNWARD, + "FE_DOWNWARD"); + fesetround (save_round_mode); + } +#endif +#ifdef FE_TOWARDZERO + if (!fesetround (FE_TOWARDZERO)) + { + result |= test_in_one_mode (tests[i].s, tests[i].c, FE_TOWARDZERO, + "FE_TOWARDZERO"); + fesetround (save_round_mode); + } +#endif +#ifdef FE_UPWARD + if (!fesetround (FE_UPWARD)) + { + result |= test_in_one_mode (tests[i].s, tests[i].c, FE_UPWARD, + "FE_UPWARD"); + fesetround (save_round_mode); + } +#endif + } + return result; +} + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" |