aboutsummaryrefslogtreecommitdiff
path: root/stdlib
diff options
context:
space:
mode:
authorJoseph Myers <joseph@codesourcery.com>2012-10-30 13:51:27 +0000
committerJoseph Myers <joseph@codesourcery.com>2012-10-30 13:51:27 +0000
commit2a27fd6dae3edec949deda9a55928a0e22c8a8ae (patch)
tree9c9fcc6c86b0f7c04454a5833d5efbe17dc79117 /stdlib
parente5088dc6870b072a263f207af9e410c82f80a09e (diff)
downloadglibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar
glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar.gz
glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar.bz2
glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.zip
Fix strtod handling of underflow (bug 14047).
Diffstat (limited to 'stdlib')
-rw-r--r--stdlib/Makefile5
-rw-r--r--stdlib/strtod_l.c43
-rw-r--r--stdlib/tst-strtod-underflow.c225
-rw-r--r--stdlib/tst-strtod.c4
-rw-r--r--stdlib/tst-tininess.c69
5 files changed, 340 insertions, 6 deletions
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 682a70c998..57830a8cb9 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -69,7 +69,8 @@ tests := tst-strtol tst-strtod testmb testrand testsort testdiv \
tst-makecontext tst-strtod4 tst-strtod5 tst-qsort2 \
tst-makecontext2 tst-strtod6 tst-unsetenv1 \
tst-makecontext3 bug-getcontext bug-fmtmsg1 \
- tst-secure-getenv tst-strtod-overflow tst-strtod-round
+ tst-secure-getenv tst-strtod-overflow tst-strtod-round \
+ tst-tininess tst-strtod-underflow
tests-static := tst-secure-getenv
include ../Makeconfig
@@ -151,3 +152,5 @@ link-libm = $(common-objpfx)math/libm.a
endif
$(objpfx)bug-getcontext: $(link-libm)
$(objpfx)tst-strtod-round: $(link-libm)
+$(objpfx)tst-tininess: $(link-libm)
+$(objpfx)tst-strtod-underflow: $(link-libm)
diff --git a/stdlib/strtod_l.c b/stdlib/strtod_l.c
index 95f13e40a2..fdce35742a 100644
--- a/stdlib/strtod_l.c
+++ b/stdlib/strtod_l.c
@@ -62,6 +62,7 @@ extern unsigned long long int ____strtoull_l_internal (const char *, char **,
#include <string.h>
#include <stdint.h>
#include <rounding-mode.h>
+#include <tininess.h>
/* The gmp headers need some configuration frobs. */
#define HAVE_ALLOCA 1
@@ -209,12 +210,15 @@ static FLOAT
round_and_return (mp_limb_t *retval, intmax_t exponent, int negative,
mp_limb_t round_limb, mp_size_t round_bit, int more_bits)
{
+ int mode = get_rounding_mode ();
+
if (exponent < MIN_EXP - 1)
{
if (exponent < MIN_EXP - 1 - MANT_DIG)
return underflow_value (negative);
mp_size_t shift = MIN_EXP - 1 - exponent;
+ bool is_tiny = true;
more_bits |= (round_limb & ((((mp_limb_t) 1) << round_bit) - 1)) != 0;
if (shift == MANT_DIG)
@@ -248,6 +252,33 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative,
}
else if (shift > 0)
{
+ if (TININESS_AFTER_ROUNDING && shift == 1)
+ {
+ /* Whether the result counts as tiny depends on whether,
+ after rounding to the normal precision, it still has
+ a subnormal exponent. */
+ mp_limb_t retval_normal[RETURN_LIMB_SIZE];
+ if (round_away (negative,
+ (retval[0] & 1) != 0,
+ (round_limb
+ & (((mp_limb_t) 1) << round_bit)) != 0,
+ (more_bits
+ || ((round_limb
+ & ((((mp_limb_t) 1) << round_bit) - 1))
+ != 0)),
+ mode))
+ {
+ mp_limb_t cy = __mpn_add_1 (retval_normal, retval,
+ RETURN_LIMB_SIZE, 1);
+
+ if (((MANT_DIG % BITS_PER_MP_LIMB) == 0 && cy) ||
+ ((MANT_DIG % BITS_PER_MP_LIMB) != 0 &&
+ ((retval_normal[RETURN_LIMB_SIZE - 1]
+ & (((mp_limb_t) 1) << (MANT_DIG % BITS_PER_MP_LIMB)))
+ != 0)))
+ is_tiny = false;
+ }
+ }
round_limb = retval[0];
round_bit = shift - 1;
(void) __mpn_rshift (retval, retval, RETURN_LIMB_SIZE, shift);
@@ -259,14 +290,20 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative,
# define DENORM_EXP (MIN_EXP - 2)
#endif
exponent = DENORM_EXP;
- __set_errno (ERANGE);
+ if (is_tiny
+ && ((round_limb & (((mp_limb_t) 1) << round_bit)) != 0
+ || more_bits
+ || (round_limb & ((((mp_limb_t) 1) << round_bit) - 1)) != 0))
+ {
+ __set_errno (ERANGE);
+ volatile FLOAT force_underflow_exception = MIN_VALUE * MIN_VALUE;
+ (void) force_underflow_exception;
+ }
}
if (exponent > MAX_EXP)
goto overflow;
- int mode = get_rounding_mode ();
-
if (round_away (negative,
(retval[0] & 1) != 0,
(round_limb & (((mp_limb_t) 1) << round_bit)) != 0,
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"
diff --git a/stdlib/tst-strtod.c b/stdlib/tst-strtod.c
index 738e73ebba..670beb1e4d 100644
--- a/stdlib/tst-strtod.c
+++ b/stdlib/tst-strtod.c
@@ -60,10 +60,10 @@ static const struct ltest tests[] =
{ "0x00.0014p19", 160.0, '\0', 0 },
{ "0x1p-1023",
1.11253692925360069154511635866620203210960799023116591527666e-308,
- '\0', ERANGE },
+ '\0', 0 },
{ "0x0.8p-1022",
1.11253692925360069154511635866620203210960799023116591527666e-308,
- '\0', ERANGE },
+ '\0', 0 },
{ "Inf", HUGE_VAL, '\0', 0 },
{ "-Inf", -HUGE_VAL, '\0', 0 },
{ "+InFiNiTy", HUGE_VAL, '\0', 0 },
diff --git a/stdlib/tst-tininess.c b/stdlib/tst-tininess.c
new file mode 100644
index 0000000000..9312f16eec
--- /dev/null
+++ b/stdlib/tst-tininess.c
@@ -0,0 +1,69 @@
+/* Test that tininess.h is correct for this architecture.
+ 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 <fenv.h>
+#include <float.h>
+#include <stdio.h>
+#include <tininess.h>
+
+volatile float a = 0x1.fffp-126;
+volatile float b = 0x1.0008p-1;
+volatile float c;
+volatile float m = FLT_MIN;
+volatile float mm;
+
+static int
+do_test (void)
+{
+ int result = 0;
+#ifdef FE_UNDERFLOW
+ feclearexcept (FE_ALL_EXCEPT);
+ mm = m * m;
+ if (!fetestexcept (FE_UNDERFLOW))
+ {
+ puts ("underflow exception not supported at runtime, cannot test");
+ return 0;
+ }
+ feclearexcept (FE_ALL_EXCEPT);
+ c = a * b;
+ if (fetestexcept (FE_UNDERFLOW))
+ {
+ if (TININESS_AFTER_ROUNDING)
+ {
+ puts ("tininess.h says after rounding, "
+ "but detected before rounding");
+ result = 1;
+ }
+ }
+ else
+ {
+ if (!TININESS_AFTER_ROUNDING)
+ {
+ puts ("tininess.h says before rounding, "
+ "but detected after rounding");
+ result = 1;
+ }
+ }
+#else
+ puts ("underflow exception not supported at compile time, cannot test");
+#endif
+ return result;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"