about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJoseph Myers <joseph@codesourcery.com>2012-09-12 23:36:19 +0000
committerJoseph Myers <joseph@codesourcery.com>2012-09-12 23:36:19 +0000
commit6c9b0f68267cf365d060d4e51e7cb8f61498b875 (patch)
tree7527c31f9593972fa3781f570b1712ba79d4a556
parent19fcedd5fcaab4355adf62350224ce53797f0f5a (diff)
downloadglibc-6c9b0f68267cf365d060d4e51e7cb8f61498b875.tar.gz
glibc-6c9b0f68267cf365d060d4e51e7cb8f61498b875.tar.xz
glibc-6c9b0f68267cf365d060d4e51e7cb8f61498b875.zip
Make strtod respect the rounding mode (bug 14518).
-rw-r--r--ChangeLog27
-rw-r--r--NEWS2
-rw-r--r--include/rounding-mode.h65
-rw-r--r--ports/ChangeLog.arm4
-rw-r--r--ports/ChangeLog.powerpc4
-rw-r--r--ports/sysdeps/arm/get-rounding-mode.h42
-rw-r--r--ports/sysdeps/powerpc/nofpu/get-rounding-mode.h35
-rw-r--r--stdlib/Makefile1
-rw-r--r--stdlib/strtod_l.c70
-rw-r--r--stdlib/tst-strtod-round.c133
-rw-r--r--sysdeps/generic/get-rounding-mode.h124
-rw-r--r--sysdeps/s390/fpu/get-rounding-mode.h37
12 files changed, 491 insertions, 53 deletions
diff --git a/ChangeLog b/ChangeLog
index 5c756e9588..11120ac8f5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,30 @@
+2012-09-12  Joseph Myers  <joseph@codesourcery.com>
+
+	[BZ #14518]
+	* include/rounding-mode.h: New file.
+	* sysdeps/generic/get-rounding-mode.h: Likewise.
+	* sysdeps/s390/fpu/get-rounding-mode.h: Likewise.
+	* stdlib/strtod_l.c: Include <rounding-mode.h>.
+	(MAX_VALUE): New macro.
+	(MIN_VALUE): Likewise.
+	(overflow_value): New function.
+	(underflow_value): Likewise.
+	(round_and_return): Use overflow_value and underflow_value to
+	determine return values in overflow and underflow cases.  Use
+	round_away to determine rounding depending on rounding mode.
+	(____STRTOF_INTERNAL): Use overflow_value and underflow_value to
+	determine return values in overflow and underflow cases.
+	* stdlib/tst-strtod-round.c: Include <fenv.h>.
+	(struct test_results): New structure.
+	(struct test): Use struct test_results to store expected results
+	for all rounding modes.
+	(TEST): Include expected results for all rounding modes.
+	(test_in_one_mode): New function.
+	(do_test): Use test_in_one_mode to compute and check results.
+	Check results for all rounding modes.
+	* stdlib/Makefile ($(objpfx)tst-strtod-round): Depend on
+	$(link-libm).
+
 2012-12-09  Allan McRae  <allan@archlinux.org>
 
         * sysdeps/i386/fpu/libm-test-ulps: Update
diff --git a/NEWS b/NEWS
index c67a651621..d5d386a129 100644
--- a/NEWS
+++ b/NEWS
@@ -13,7 +13,7 @@ Version 2.17
   13542, 13717, 13696, 13939, 13966, 14042, 14090, 14166, 14150, 14151,
   14154, 14157, 14166, 14173, 14195, 14237, 14252, 14283, 14298, 14303,
   14307, 14328, 14331, 14336, 14337, 14347, 14349, 14459, 14476, 14505,
-  14510, 14516, 14519, 14532, 14538, 14544, 14545.
+  14510, 14516, 14518, 14519, 14532, 14538, 14544, 14545.
 
 * 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/include/rounding-mode.h b/include/rounding-mode.h
new file mode 100644
index 0000000000..bde41e7b1e
--- /dev/null
+++ b/include/rounding-mode.h
@@ -0,0 +1,65 @@
+/* Handle floating-point rounding mode within libc.
+   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 _ROUNDING_MODE_H
+#define _ROUNDING_MODE_H	1
+
+#include <fenv.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+/* Get the architecture-specific definition of how to determine the
+   rounding mode in libc.  This header must also define the FE_*
+   macros for any standard rounding modes the architecture does not
+   have in <fenv.h>, to arbitrary distinct values.  */
+#include <get-rounding-mode.h>
+
+/* Return true if a number should be rounded away from zero in
+   rounding mode MODE, false otherwise.  NEGATIVE is true if the
+   number is negative, false otherwise.  LAST_DIGIT_ODD is true if the
+   last digit of the truncated value (last bit for binary) is odd,
+   false otherwise.  HALF_BIT is true if the number is at least half
+   way from the truncated value to the next value with the
+   least-significant digit in the same place, false otherwise.
+   MORE_BITS is true if the number is not exactly equal to the
+   truncated value or the half-way value, false otherwise.  */
+
+static inline bool
+round_away (bool negative, bool last_digit_odd, bool half_bit, bool more_bits,
+	    int mode)
+{
+  switch (mode)
+    {
+    case FE_DOWNWARD:
+      return negative && (half_bit || more_bits);
+
+    case FE_TONEAREST:
+      return half_bit && (last_digit_odd || more_bits);
+
+    case FE_TOWARDZERO:
+      return false;
+
+    case FE_UPWARD:
+      return !negative && (half_bit || more_bits);
+
+    default:
+      abort ();
+    }
+}
+
+#endif /* rounding-mode.h */
diff --git a/ports/ChangeLog.arm b/ports/ChangeLog.arm
index 1fae5f72f8..a0c583b997 100644
--- a/ports/ChangeLog.arm
+++ b/ports/ChangeLog.arm
@@ -1,3 +1,7 @@
+2012-09-12  Joseph Myers  <joseph@codesourcery.com>
+
+	* sysdeps/arm/get-rounding-mode.h: New file.
+
 2012-08-27  Joseph Myers  <joseph@codesourcery.com>
 
 	* sysdeps/unix/sysv/linux/arm/kernel-features.h
diff --git a/ports/ChangeLog.powerpc b/ports/ChangeLog.powerpc
index c8a8584000..af98f828ea 100644
--- a/ports/ChangeLog.powerpc
+++ b/ports/ChangeLog.powerpc
@@ -1,3 +1,7 @@
+2012-09-12  Joseph Myers  <joseph@codesourcery.com>
+
+	* sysdeps/powerpc/nofpu/get-rounding-mode.h: New file.
+
 2012-07-25  Florian Weimer  <fweimer@redhat.com>
 
 	* sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/nptl/libc.abilist:
diff --git a/ports/sysdeps/arm/get-rounding-mode.h b/ports/sysdeps/arm/get-rounding-mode.h
new file mode 100644
index 0000000000..a614fe43f8
--- /dev/null
+++ b/ports/sysdeps/arm/get-rounding-mode.h
@@ -0,0 +1,42 @@
+/* Determine floating-point rounding mode within libc.  ARM 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 _ARM_GET_ROUNDING_MODE_H
+#define _ARM_GET_ROUNDING_MODE_H	1
+
+#include <arm-features.h>
+#include <fenv.h>
+#include <fpu_control.h>
+
+/* Return the floating-point rounding mode.  */
+
+static inline int
+get_rounding_mode (void)
+{
+  if (ARM_HAVE_VFP)
+    {
+      fpu_control_t fc;
+
+      _FPU_GETCW (fc);
+      return fc & FE_TOWARDZERO;
+    }
+  else
+    return FE_TONEAREST;
+}
+
+#endif /* get-rounding-mode.h */
diff --git a/ports/sysdeps/powerpc/nofpu/get-rounding-mode.h b/ports/sysdeps/powerpc/nofpu/get-rounding-mode.h
new file mode 100644
index 0000000000..14be20f7b6
--- /dev/null
+++ b/ports/sysdeps/powerpc/nofpu/get-rounding-mode.h
@@ -0,0 +1,35 @@
+/* Determine floating-point rounding mode within libc.  PowerPC
+   soft-float 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 _POWERPC_NOFPU_GET_ROUNDING_MODE_H
+#define _POWERPC_NOFPU_GET_ROUNDING_MODE_H	1
+
+#include <fenv.h>
+
+#include "soft-supp.h"
+
+/* Return the floating-point rounding mode.  */
+
+static inline int
+get_rounding_mode (void)
+{
+  return __sim_round_mode;
+}
+
+#endif /* get-rounding-mode.h */
diff --git a/stdlib/Makefile b/stdlib/Makefile
index dfc5eaf97b..c730b47433 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -150,3 +150,4 @@ else
 link-libm = $(common-objpfx)math/libm.a
 endif
 $(objpfx)bug-getcontext: $(link-libm)
+$(objpfx)tst-strtod-round: $(link-libm)
diff --git a/stdlib/strtod_l.c b/stdlib/strtod_l.c
index ccd117a9f1..95f13e40a2 100644
--- a/stdlib/strtod_l.c
+++ b/stdlib/strtod_l.c
@@ -61,6 +61,7 @@ extern unsigned long long int ____strtoull_l_internal (const char *, char **,
 #include <stdlib.h>
 #include <string.h>
 #include <stdint.h>
+#include <rounding-mode.h>
 
 /* The gmp headers need some configuration frobs.  */
 #define HAVE_ALLOCA 1
@@ -126,6 +127,8 @@ extern unsigned long long int ____strtoull_l_internal (const char *, char **,
 #define	MIN_EXP		PASTE(FLT,_MIN_EXP)
 #define MAX_10_EXP	PASTE(FLT,_MAX_10_EXP)
 #define MIN_10_EXP	PASTE(FLT,_MIN_10_EXP)
+#define MAX_VALUE	PASTE(FLT,_MAX)
+#define MIN_VALUE	PASTE(FLT,_MIN)
 
 /* Extra macros required to get FLT expanded before the pasting.  */
 #define PASTE(a,b)	PASTE1(a,b)
@@ -172,6 +175,34 @@ extern const mp_limb_t _tens_in_limb[MAX_DIG_PER_LIMB + 1];
 	memcpy (dst, src, (dst##size = src##size) * sizeof (mp_limb_t))
 
 
+/* Set errno and return an overflowing value with sign specified by
+   NEGATIVE.  */
+static FLOAT
+overflow_value (int negative)
+{
+  __set_errno (ERANGE);
+#if FLT_EVAL_METHOD != 0
+  volatile
+#endif
+  FLOAT result = (negative ? -MAX_VALUE : MAX_VALUE) * MAX_VALUE;
+  return result;
+}
+
+
+/* Set errno and return an underflowing value with sign specified by
+   NEGATIVE.  */
+static FLOAT
+underflow_value (int negative)
+{
+  __set_errno (ERANGE);
+#if FLT_EVAL_METHOD != 0
+  volatile
+#endif
+  FLOAT result = (negative ? -MIN_VALUE : MIN_VALUE) * MIN_VALUE;
+  return result;
+}
+
+
 /* Return a floating point number of the needed type according to the given
    multi-precision number after possible rounding.  */
 static FLOAT
@@ -181,10 +212,7 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative,
   if (exponent < MIN_EXP - 1)
     {
       if (exponent < MIN_EXP - 1 - MANT_DIG)
-	{
-	  __set_errno (ERANGE);
-	  return negative ? -0.0 : 0.0;
-	}
+	return underflow_value (negative);
 
       mp_size_t shift = MIN_EXP - 1 - exponent;
 
@@ -237,9 +265,14 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative,
   if (exponent > MAX_EXP)
     goto overflow;
 
-  if ((round_limb & (((mp_limb_t) 1) << round_bit)) != 0
-      && (more_bits || (retval[0] & 1) != 0
-	  || (round_limb & ((((mp_limb_t) 1) << round_bit) - 1)) != 0))
+  int mode = get_rounding_mode ();
+
+  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, retval, RETURN_LIMB_SIZE, 1);
 
@@ -263,7 +296,7 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative,
 
   if (exponent > MAX_EXP)
   overflow:
-    return negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL;
+    return overflow_value (negative);
 
   return MPN2FLOAT (retval, exponent, negative);
 }
@@ -914,9 +947,9 @@ ____STRTOF_INTERNAL (nptr, endptr, group, loc)
 		  else
 		    {
 		      /* Overflow or underflow.  */
-		      __set_errno (ERANGE);
-		      result = (exp_negative ? (negative ? -0.0 : 0.0) :
-				negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL);
+		      result = (exp_negative
+				? underflow_value (negative)
+				: overflow_value (negative));
 		    }
 
 		  /* Accept all following digits as part of the exponent.  */
@@ -1112,16 +1145,10 @@ ____STRTOF_INTERNAL (nptr, endptr, group, loc)
   }
 
   if (__builtin_expect (exponent > MAX_10_EXP + 1 - (intmax_t) int_no, 0))
-    {
-      __set_errno (ERANGE);
-      return negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL;
-    }
+    return overflow_value (negative);
 
   if (__builtin_expect (exponent < MIN_10_EXP - (DIG + 1), 0))
-    {
-      __set_errno (ERANGE);
-      return negative ? -0.0 : 0.0;
-    }
+    return underflow_value (negative);
 
   if (int_no > 0)
     {
@@ -1182,10 +1209,7 @@ ____STRTOF_INTERNAL (nptr, endptr, group, loc)
       /* Now we know the exponent of the number in base two.
 	 Check it against the maximum possible exponent.  */
       if (__builtin_expect (bits > MAX_EXP, 0))
-	{
-	  __set_errno (ERANGE);
-	  return negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL;
-	}
+	return overflow_value (negative);
 
       /* We have already the first BITS bits of the result.  Together with
 	 the information whether more non-zero bits follow this is enough
diff --git a/stdlib/tst-strtod-round.c b/stdlib/tst-strtod-round.c
index c6ad126873..76385a94d3 100644
--- a/stdlib/tst-strtod-round.c
+++ b/stdlib/tst-strtod-round.c
@@ -17,6 +17,7 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
 #include <float.h>
 #include <math.h>
 #include <stdbool.h>
@@ -24,21 +25,32 @@
 #include <stdlib.h>
 #include <string.h>
 
-struct test {
-  const char *s;
+struct test_results {
   float f;
   double d;
-  bool ld_ok;
   long double ld;
 };
 
+struct test {
+  const char *s;
+  bool ld_ok;
+  struct test_results rd, rn, rz, ru;
+};
+
 #if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024
 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u,  \
 	      ld64id, ld64in, ld64iz, ld64iu,				      \
 	      ld64md, ld64mn, ld64mz, ld64mu,				      \
 	      ld106exact, ld106d, ld106n, ld106z, ld106u,		      \
 	      ld113d, ld113n, ld113z, ld113u)				      \
-  { s, fn, dn, true, ld53n }
+  {									\
+    s,									\
+    true,								\
+    { fd, dd, ld53d },							\
+    { fn, dn, ld53n },							\
+    { fz, dz, ld53z },							\
+    { fu, du, ld53u }							\
+  }
 #elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && LDBL_MIN_EXP == -16381
 /* This is for the Intel extended float format.  */
 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u,  \
@@ -46,7 +58,14 @@ struct test {
 	      ld64md, ld64mn, ld64mz, ld64mu,				      \
 	      ld106exact, ld106d, ld106n, ld106z, ld106u,		      \
 	      ld113d, ld113n, ld113z, ld113u)				      \
-  { s, fn, dn, true, ld64in }
+  {									\
+    s,									\
+    true,								\
+    { fd, dd, ld64id },							\
+    { fn, dn, ld64in },							\
+    { fz, dz, ld64iz },							\
+    { fu, du, ld64iu }							\
+  }
 #elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && LDBL_MIN_EXP == -16382
 /* This is for the Motorola extended float format.  */
 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u,  \
@@ -54,21 +73,42 @@ struct test {
 	      ld64md, ld64mn, ld64mz, ld64mu,				      \
 	      ld106exact, ld106d, ld106n, ld106z, ld106u,		      \
 	      ld113d, ld113n, ld113z, ld113u)				      \
-  { s, fn, dn, true, ld64mn }
+  {									\
+    s,									\
+    true,								\
+    { fd, dd, ld64md },							\
+    { fn, dn, ld64mn },							\
+    { fz, dz, ld64mz },							\
+    { fu, du, ld64mu }							\
+  }
 #elif LDBL_MANT_DIG == 106 && LDBL_MAX_EXP == 1024
 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u,  \
 	      ld64id, ld64in, ld64iz, ld64iu,				      \
 	      ld64md, ld64mn, ld64mz, ld64mu,				      \
 	      ld106exact, ld106d, ld106n, ld106z, ld106u,		      \
 	      ld113d, ld113n, ld113z, ld113u)				      \
-  { s, fn, dn, ld106exact, ld106n }
+  {									\
+    s,									\
+    ld106exact,								\
+    { fd, dd, ld106d },							\
+    { fn, dn, ld106n },							\
+    { fz, dz, ld106z },							\
+    { fu, du, ld106u }							\
+  }
 #elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384
 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u,  \
 	      ld64id, ld64in, ld64iz, ld64iu,				      \
 	      ld64md, ld64mn, ld64mz, ld64mu,				      \
 	      ld106exact, ld106d, ld106n, ld106z, ld106u,		      \
 	      ld113d, ld113n, ld113z, ld113u)				      \
-  { s, fn, dn, true, ld113n }
+  {									\
+    s,									\
+    true,								\
+    { fd, dd, ld113d },							\
+    { fn, dn, ld113n },							\
+    { fz, dz, ld113z },							\
+    { fu, du, ld113u }							\
+  }
 #else
 # error "unknown long double format"
 #endif
@@ -6819,38 +6859,73 @@ static const struct test tests[] = {
 };
 
 static int
+test_in_one_mode (const char *s, const struct test_results *expected,
+		  bool ld_ok, const char *mode_name)
+{
+  int result = 0;
+  float f = strtof (s, NULL);
+  double d = strtod (s, NULL);
+  long double ld = strtold (s, NULL);
+  if (f != expected->f
+      || copysignf (1.0f, f) != copysignf (1.0f, expected->f))
+    {
+      printf ("strtof (%s) returned %a not %a (%s)\n", s, f,
+	      expected->f, mode_name);
+      result = 1;
+    }
+  if (d != expected->d
+      || copysign (1.0, d) != copysign (1.0, expected->d))
+    {
+      printf ("strtod (%s) returned %a not %a (%s)\n", s, d,
+	      expected->d, mode_name);
+      result = 1;
+    }
+  if (ld != expected->ld
+      || copysignl (1.0L, ld) != copysignl (1.0L, expected->ld))
+    {
+      printf ("strtold (%s) returned %La not %La (%s)\n", s, ld,
+	      expected->ld, mode_name);
+      if (ld_ok)
+	result = 1;
+      else
+	printf ("ignoring this inexact long double result\n");
+    }
+  return result;
+}
+
+static int
 do_test (void)
 {
+  int save_round_mode = fegetround ();
   int result = 0;
   for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++)
     {
-      float f = strtof (tests[i].s, NULL);
-      double d = strtod (tests[i].s, NULL);
-      long double ld = strtold (tests[i].s, NULL);
-      if (f != tests[i].f
-	  || copysignf (1.0f, f) != copysignf (1.0f, tests[i].f))
+      result |= test_in_one_mode (tests[i].s, &tests[i].rn, tests[i].ld_ok,
+				  "default rounding mode");
+#ifdef FE_DOWNWARD
+      if (!fesetround (FE_DOWNWARD))
 	{
-	  printf ("strtof (%s) returned %a not %a\n", tests[i].s, f,
-		  tests[i].f);
-	  result = 1;
+	  result |= test_in_one_mode (tests[i].s, &tests[i].rd, tests[i].ld_ok,
+				      "FE_DOWNWARD");
+	  fesetround (save_round_mode);
 	}
-      if (d != tests[i].d
-	  || copysign (1.0, d) != copysign (1.0, tests[i].d))
+#endif
+#ifdef FE_TOWARDZERO
+      if (!fesetround (FE_TOWARDZERO))
 	{
-	  printf ("strtod (%s) returned %a not %a\n", tests[i].s, d,
-		  tests[i].d);
-	  result = 1;
+	  result |= test_in_one_mode (tests[i].s, &tests[i].rz, tests[i].ld_ok,
+				      "FE_TOWARDZERO");
+	  fesetround (save_round_mode);
 	}
-      if (ld != tests[i].ld
-	  || copysignl (1.0L, ld) != copysignl (1.0L, tests[i].ld))
+#endif
+#ifdef FE_UPWARD
+      if (!fesetround (FE_UPWARD))
 	{
-	  printf ("strtold (%s) returned %La not %La\n", tests[i].s, ld,
-		  tests[i].ld);
-	  if (tests[i].ld_ok)
-	    result = 1;
-	  else
-	    printf ("ignoring this inexact long double result\n");
+	  result |= test_in_one_mode (tests[i].s, &tests[i].ru, tests[i].ld_ok,
+				      "FE_UPWARD");
+	  fesetround (save_round_mode);
 	}
+#endif
     }
   return result;
 }
diff --git a/sysdeps/generic/get-rounding-mode.h b/sysdeps/generic/get-rounding-mode.h
new file mode 100644
index 0000000000..0ecaddd7bf
--- /dev/null
+++ b/sysdeps/generic/get-rounding-mode.h
@@ -0,0 +1,124 @@
+/* Determine floating-point rounding mode within libc.  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 _GET_ROUNDING_MODE_H
+#define _GET_ROUNDING_MODE_H	1
+
+#include <fpu_control.h>
+
+/* Define values for FE_* modes not defined for this architecture.  */
+#ifdef FE_DOWNWARD
+# define ORIG_FE_DOWNWARD FE_DOWNWARD
+#else
+# define ORIG_FE_DOWNWARD 0
+#endif
+#ifdef FE_TONEAREST
+# define ORIG_FE_TONEAREST FE_TONEAREST
+#else
+# define ORIG_FE_TONEAREST 0
+#endif
+#ifdef FE_TOWARDZERO
+# define ORIG_FE_TOWARDZERO FE_TOWARDZERO
+#else
+# define ORIG_FE_TOWARDZERO 0
+#endif
+#ifdef FE_UPWARD
+# define ORIG_FE_UPWARD FE_UPWARD
+#else
+# define ORIG_FE_UPWARD 0
+#endif
+#define FE_CONSTRUCT_DISTINCT_VALUE(X, Y, Z) \
+  ((((X) & 1) | ((Y) & 2) | ((Z) & 4)) ^ 7)
+#ifndef FE_DOWNWARD
+# define FE_DOWNWARD FE_CONSTRUCT_DISTINCT_VALUE (ORIG_FE_TONEAREST,	\
+						  ORIG_FE_TOWARDZERO,	\
+						  ORIG_FE_UPWARD)
+#endif
+#ifndef FE_TONEAREST
+# define FE_TONEAREST FE_CONSTRUCT_DISTINCT_VALUE (FE_DOWNWARD,		\
+						   ORIG_FE_TOWARDZERO,	\
+						   ORIG_FE_UPWARD)
+#endif
+#ifndef FE_TOWARDZERO
+# define FE_TOWARDZERO FE_CONSTRUCT_DISTINCT_VALUE (FE_DOWNWARD,	\
+						    FE_TONEAREST,	\
+						    ORIG_FE_UPWARD)
+#endif
+#ifndef FE_UPWARD
+# define FE_UPWARD FE_CONSTRUCT_DISTINCT_VALUE (FE_DOWNWARD,	\
+						FE_TONEAREST,	\
+						FE_TOWARDZERO)
+#endif
+
+/* Return the floating-point rounding mode.  */
+
+static inline int
+get_rounding_mode (void)
+{
+#if (defined _FPU_RC_DOWN			\
+     || defined _FPU_RC_NEAREST			\
+     || defined _FPU_RC_ZERO			\
+     || defined _FPU_RC_UP)
+  fpu_control_t fc;
+  const fpu_control_t mask = (0
+# ifdef _FPU_RC_DOWN
+			      | _FPU_RC_DOWN
+# endif
+# ifdef _FPU_RC_NEAREST
+			      | _FPU_RC_NEAREST
+# endif
+# ifdef _FPU_RC_ZERO
+			      | _FPU_RC_ZERO
+# endif
+# ifdef _FPU_RC_UP
+			      | _FPU_RC_UP
+# endif
+			      );
+
+  _FPU_GETCW (fc);
+  switch (fc & mask)
+    {
+# ifdef _FPU_RC_DOWN
+    case _FPU_RC_DOWN:
+      return FE_DOWNWARD;
+# endif
+
+# ifdef _FPU_RC_NEAREST
+    case _FPU_RC_NEAREST:
+      return FE_TONEAREST;
+# endif
+
+# ifdef _FPU_RC_ZERO
+    case _FPU_RC_ZERO:
+      return FE_TOWARDZERO;
+# endif
+
+# ifdef _FPU_RC_UP
+    case _FPU_RC_UP:
+      return FE_UPWARD;
+# endif
+
+    default:
+      abort ();
+    }
+#else
+  return FE_TONEAREST;
+#endif
+}
+
+#endif /* get-rounding-mode.h */
diff --git a/sysdeps/s390/fpu/get-rounding-mode.h b/sysdeps/s390/fpu/get-rounding-mode.h
new file mode 100644
index 0000000000..3e427eca2c
--- /dev/null
+++ b/sysdeps/s390/fpu/get-rounding-mode.h
@@ -0,0 +1,37 @@
+/* Determine floating-point rounding mode within libc.  S/390 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 _S390_GET_ROUNDING_MODE_H
+#define _S390_GET_ROUNDING_MODE_H	1
+
+#include <fenv.h>
+#include <fenv_libc.h>
+#include <fpu_control.h>
+
+/* Return the floating-point rounding mode.  */
+
+static inline int
+get_rounding_mode (void)
+{
+  fpu_control_t fc;
+
+  _FPU_GETCW (fc);
+  return fc & FPC_RM_MASK;
+}
+
+#endif /* get-rounding-mode.h */