summary refs log tree commit diff
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
parente5088dc6870b072a263f207af9e410c82f80a09e (diff)
downloadglibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar.gz
glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.tar.xz
glibc-2a27fd6dae3edec949deda9a55928a0e22c8a8ae.zip
Fix strtod handling of underflow (bug 14047).
-rw-r--r--ChangeLog21
-rw-r--r--NEWS14
-rw-r--r--ports/ChangeLog.alpha5
-rw-r--r--ports/ChangeLog.am335
-rw-r--r--ports/ChangeLog.hppa5
-rw-r--r--ports/ChangeLog.ia645
-rw-r--r--ports/ChangeLog.mips5
-rw-r--r--ports/sysdeps/alpha/tininess.h1
-rw-r--r--ports/sysdeps/am33/tininess.h1
-rw-r--r--ports/sysdeps/hppa/tininess.h1
-rw-r--r--ports/sysdeps/ia64/tininess.h1
-rw-r--r--ports/sysdeps/mips/tininess.h1
-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
-rw-r--r--sysdeps/generic/tininess.h33
-rw-r--r--sysdeps/i386/tininess.h1
-rw-r--r--sysdeps/sh/tininess.h1
-rw-r--r--sysdeps/x86_64/tininess.h1
21 files changed, 434 insertions, 13 deletions
diff --git a/ChangeLog b/ChangeLog
index 29d0a9a487..067337a44b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2012-10-30  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14047]
+	* sysdeps/generic/tininess.h: New file.
+	* sysdeps/i386/tininess.h: Likewise.
+	* sysdeps/sh/tininess.h: Likewise.
+	* sysdeps/x86_64/tininess.h: Likewise.
+	* stdlib/tst-strtod-underflow.c: Likewise.
+	* stdlib/tst-tininess.c: Likewise.
+	* stdlib/strtod_l.c: Include <tininess.h>.
+	(round_and_return): Do not set errno for exact underflow cases.
+	Force an underflow exception when setting errno for underflow.
+	Determine underflow based on rounding to normal precision if
+	TININESS_AFTER_ROUNDING.
+	* stdlib/tst-strtod.c (tests): Do not expect errno to be set to
+	ERANGE for exact underflow cases.
+	* stdlib/Makefile (tests): Add tst-tininess and
+	tst-strtod-underflow.
+	($(objpfx)tst-tininess): Use $(link-libm).
+	($(objpfx)tst-strtod-underflow): Likewise.
+
 2012-10-30  Andreas Jaeger  <aj@suse.de>
 
 	[BZ#14767]
diff --git a/NEWS b/NEWS
index d548217462..e4e672680a 100644
--- a/NEWS
+++ b/NEWS
@@ -11,13 +11,13 @@ Version 2.17
 
   1349, 3479, 5044, 5298, 5400, 6530, 6778, 6808, 9685, 9914, 10014, 10038,
   10631, 11438, 11607, 12140, 13412, 13542, 13601, 13629, 13679, 13696,
-  13717, 13741, 13939, 13966, 14042, 14090, 14150, 14151, 14154, 14157,
-  14166, 14173, 14195, 14237, 14251, 14252, 14283, 14298, 14303, 14307,
-  14328, 14331, 14336, 14337, 14347, 14349, 14376, 14417, 14459, 14476,
-  14477, 14505, 14510, 14516, 14518, 14519, 14530, 14532, 14538, 14543,
-  14544, 14545, 14557, 14562, 14568, 14576, 14579, 14583, 14587, 14602,
-  14621, 14638, 14645, 14648, 14652, 14660, 14661, 14683, 14694, 14716,
-  14743, 14767.
+  13717, 13741, 13939, 13966, 14042, 14047, 14090, 14150, 14151, 14154,
+  14157, 14166, 14173, 14195, 14237, 14251, 14252, 14283, 14298, 14303,
+  14307, 14328, 14331, 14336, 14337, 14347, 14349, 14376, 14417, 14459,
+  14476, 14477, 14505, 14510, 14516, 14518, 14519, 14530, 14532, 14538,
+  14543, 14544, 14545, 14557, 14562, 14568, 14576, 14579, 14583, 14587,
+  14602, 14621, 14638, 14645, 14648, 14652, 14660, 14661, 14683, 14694,
+  14716, 14743, 14767.
 
 * Support for STT_GNU_IFUNC symbols added for s390 and s390x.
   Optimized versions of memcpy, memset, and memcmp added for System z10 and
diff --git a/ports/ChangeLog.alpha b/ports/ChangeLog.alpha
index 8ddde9f25f..eff592b0cb 100644
--- a/ports/ChangeLog.alpha
+++ b/ports/ChangeLog.alpha
@@ -1,3 +1,8 @@
+2012-10-30  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14047]
+	* sysdeps/alpha/tininess.h: New file.
+
 2012-10-19  Roland McGrath  <roland@hack.frob.com>
 
 	* sysdeps/unix/sysv/linux/alpha/nptl/libc.abilist
diff --git a/ports/ChangeLog.am33 b/ports/ChangeLog.am33
index e8243c5326..d732d2c2b8 100644
--- a/ports/ChangeLog.am33
+++ b/ports/ChangeLog.am33
@@ -1,3 +1,8 @@
+2012-10-30  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14047]
+	* sysdeps/am33/tininess.h: New file.
+
 2012-10-09  Roland McGrath  <roland@hack.frob.com>
 
 	* sysdeps/unix/sysv/linux/am33/configure: Regenerated.
diff --git a/ports/ChangeLog.hppa b/ports/ChangeLog.hppa
index 41b594c6e5..6c828ddbfc 100644
--- a/ports/ChangeLog.hppa
+++ b/ports/ChangeLog.hppa
@@ -1,3 +1,8 @@
+2012-10-30  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14047]
+	* sysdeps/hppa/tininess.h: New file.
+
 2012-10-29  Carlos O'Donell  <carlos@systemhalted.org>
 
 	* sysdeps/unix/sysv/linux/hppa/sysdep.h (ENTRY): Add cfi_startproc.
diff --git a/ports/ChangeLog.ia64 b/ports/ChangeLog.ia64
index 47f51c1ae1..1531304ad6 100644
--- a/ports/ChangeLog.ia64
+++ b/ports/ChangeLog.ia64
@@ -1,3 +1,8 @@
+2012-10-30  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14047]
+	* sysdeps/ia64/tininess.h: New file.
+
 2012-10-25  Andreas Jaeger  <aj@suse.de>
 
 	* sysdeps/unix/sysv/linux/ia64/bits/fcntl.h: Remove all
diff --git a/ports/ChangeLog.mips b/ports/ChangeLog.mips
index 76f2703881..507deb6fef 100644
--- a/ports/ChangeLog.mips
+++ b/ports/ChangeLog.mips
@@ -1,3 +1,8 @@
+2012-10-30  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14047]
+	* sysdeps/mips/tininess.h: New file.
+
 2012-10-29  Steve Ellcey  <sellcey@mips.com>
 
 	* sysdeps/unix/sysv/linux/mips/mips32/Makefile: Remove.
diff --git a/ports/sysdeps/alpha/tininess.h b/ports/sysdeps/alpha/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/ports/sysdeps/alpha/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
diff --git a/ports/sysdeps/am33/tininess.h b/ports/sysdeps/am33/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/ports/sysdeps/am33/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
diff --git a/ports/sysdeps/hppa/tininess.h b/ports/sysdeps/hppa/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/ports/sysdeps/hppa/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
diff --git a/ports/sysdeps/ia64/tininess.h b/ports/sysdeps/ia64/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/ports/sysdeps/ia64/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
diff --git a/ports/sysdeps/mips/tininess.h b/ports/sysdeps/mips/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/ports/sysdeps/mips/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
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"
diff --git a/sysdeps/generic/tininess.h b/sysdeps/generic/tininess.h
new file mode 100644
index 0000000000..fd01739950
--- /dev/null
+++ b/sysdeps/generic/tininess.h
@@ -0,0 +1,33 @@
+/* Specify architecture-specific rules for determining tininess of
+   floating-point results.  Generic version.
+   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/>.  */
+
+#ifndef _TININESS_H
+#define _TININESS_H	1
+
+/* Under IEEE 754, an architecture may determine tininess of
+   floating-point results either "before rounding" or "after
+   rounding", but must do so in the same way for all operations
+   returning binary results.  Define TININESS_AFTER_ROUNDING to 1 for
+   "after rounding" architectures, 0 for "before rounding"
+   architectures.  The test stdlib/tst-tininess will fail if the
+   definition is incorrect.  */
+
+#define TININESS_AFTER_ROUNDING	0
+
+#endif /* tininess.h */
diff --git a/sysdeps/i386/tininess.h b/sysdeps/i386/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/sysdeps/i386/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
diff --git a/sysdeps/sh/tininess.h b/sysdeps/sh/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/sysdeps/sh/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1
diff --git a/sysdeps/x86_64/tininess.h b/sysdeps/x86_64/tininess.h
new file mode 100644
index 0000000000..1db37790f8
--- /dev/null
+++ b/sysdeps/x86_64/tininess.h
@@ -0,0 +1 @@
+#define TININESS_AFTER_ROUNDING	1