about summary refs log tree commit diff
path: root/sysdeps
diff options
context:
space:
mode:
authorJoseph Myers <joseph@codesourcery.com>2015-10-07 23:45:29 +0000
committerJoseph Myers <joseph@codesourcery.com>2015-10-07 23:45:29 +0000
commit8afdb7ac1ecadf82abdb16a5fcfadf9537ca3d84 (patch)
tree169c0bffb741e4fb32fd61098af49445dac0cff9 /sysdeps
parent939e092a9e46e6a8bb3d7dc3cf165f384b598f46 (diff)
downloadglibc-8afdb7ac1ecadf82abdb16a5fcfadf9537ca3d84.tar.gz
glibc-8afdb7ac1ecadf82abdb16a5fcfadf9537ca3d84.tar.xz
glibc-8afdb7ac1ecadf82abdb16a5fcfadf9537ca3d84.zip
Fix lround, llround missing exceptions close to overflow threshold (bug 19088).
The dbl-64, ldbl-96 and ldbl-128 implementations of lround and llround
fail to produce "invalid" exceptions in cases where the rounded result
overflows the target type, but truncating the floating-point argument
to the next integer towards zero does not overflow it (so in
particular casts do not produce such exceptions).  (This issue cannot
arise for float, or for double with 64-bit target type, or for ldbl-96
with 64-bit target type and negative arguments, because of
insufficient precision in the floating-point type for arguments with
the relevant property to exist.)

This patch fixes these problems by inserting checks for the special
cases that can occur in each implementation, and explicitly raising
FE_INVALID (and avoiding the cast if it might raise spurious
FE_INEXACT).

Tested for x86_64, x86 and mips64.

	[BZ #19088]
	* sysdeps/ieee754/dbl-64/s_lround.c: Include <fenv.h> and
	<limits.h>.
	(__lround) [FE_INVALID]: Force FE_INVALID exception when result
	overflows but exception would not result from cast.
	* sysdeps/ieee754/dbl-64/wordsize-64/s_lround.c: Include <fenv.h>
	and <limits.h>.
	(__lround) [FE_INVALID]: Force FE_INVALID exception when result
	overflows but exception would not result from cast.
	* sysdeps/ieee754/ldbl-128/s_llroundl.c: Include <fenv.h> and
	<limits.h>.
	(__llroundl) [FE_INVALID]: Force FE_INVALID exception when result
	overflows but exception would not result from cast.
	* sysdeps/ieee754/ldbl-128/s_lroundl.c: Include <fenv.h> and
	<limits.h>.
	(__lroundl) [FE_INVALID]: Force FE_INVALID exception when result
	overflows but exception would not result from cast.
	* sysdeps/ieee754/ldbl-96/s_llroundl.c: Include <fenv.h> and
	<limits.h>.
	(__llroundl) [FE_INVALID]: Force FE_INVALID exception when result
	overflows but exception would not result from cast.
	* sysdeps/ieee754/ldbl-96/s_lroundl.c: Include <fenv.h> and
	<limits.h>.
	(__lroundl) [FE_INVALID]: Force FE_INVALID exception when result
	overflows but exception would not result from cast.
	* math/libm-test.inc (lround_test_data): Add more tests.
	(llround_test_data): Likewise.
Diffstat (limited to 'sysdeps')
-rw-r--r--sysdeps/ieee754/dbl-64/s_lround.c28
-rw-r--r--sysdeps/ieee754/dbl-64/wordsize-64/s_lround.c24
-rw-r--r--sysdeps/ieee754/ldbl-128/s_llroundl.c25
-rw-r--r--sysdeps/ieee754/ldbl-128/s_lroundl.c32
-rw-r--r--sysdeps/ieee754/ldbl-96/s_llroundl.c11
-rw-r--r--sysdeps/ieee754/ldbl-96/s_lroundl.c35
6 files changed, 142 insertions, 13 deletions
diff --git a/sysdeps/ieee754/dbl-64/s_lround.c b/sysdeps/ieee754/dbl-64/s_lround.c
index bdc838a676..91b17b0de9 100644
--- a/sysdeps/ieee754/dbl-64/s_lround.c
+++ b/sysdeps/ieee754/dbl-64/s_lround.c
@@ -17,6 +17,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
+#include <limits.h>
 #include <math.h>
 
 #include <math_private.h>
@@ -60,13 +62,33 @@ __lround (double x)
 	  if (j0 == 20)
 	    result = (long int) i0;
 	  else
-	    result = ((long int) i0 << (j0 - 20)) | (j >> (52 - j0));
+	    {
+	      result = ((long int) i0 << (j0 - 20)) | (j >> (52 - j0));
+#ifdef FE_INVALID
+	      if (sizeof (long int) == 4
+		  && sign == 1
+		  && result == LONG_MIN)
+		/* Rounding brought the value out of range.  */
+		feraiseexcept (FE_INVALID);
+#endif
+	    }
 	}
     }
   else
     {
-      /* The number is too large.  It is left implementation defined
-	 what happens.  */
+      /* The number is too large.  Unless it rounds to LONG_MIN,
+	 FE_INVALID must be raised and the return value is
+	 unspecified.  */
+#ifdef FE_INVALID
+      if (sizeof (long int) == 4
+	  && x <= (double) LONG_MIN - 0.5)
+	{
+	  /* If truncation produces LONG_MIN, the cast will not raise
+	     the exception, but may raise "inexact".  */
+	  feraiseexcept (FE_INVALID);
+	  return LONG_MIN;
+	}
+#endif
       return (long int) x;
     }
 
diff --git a/sysdeps/ieee754/dbl-64/wordsize-64/s_lround.c b/sysdeps/ieee754/dbl-64/wordsize-64/s_lround.c
index 390e733ffd..bbc0628c04 100644
--- a/sysdeps/ieee754/dbl-64/wordsize-64/s_lround.c
+++ b/sysdeps/ieee754/dbl-64/wordsize-64/s_lround.c
@@ -16,6 +16,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
+#include <limits.h>
 #include <math.h>
 
 #include <math_private.h>
@@ -48,12 +50,30 @@ __lround (double x)
 	  i0 += UINT64_C(0x8000000000000) >> j0;
 
 	  result = i0 >> (52 - j0);
+#ifdef FE_INVALID
+	  if (sizeof (long int) == 4
+	      && sign == 1
+	      && result == LONG_MIN)
+	    /* Rounding brought the value out of range.  */
+	    feraiseexcept (FE_INVALID);
+#endif
 	}
     }
   else
     {
-      /* The number is too large.  It is left implementation defined
-	 what happens.  */
+      /* The number is too large.  Unless it rounds to LONG_MIN,
+	 FE_INVALID must be raised and the return value is
+	 unspecified.  */
+#ifdef FE_INVALID
+      if (sizeof (long int) == 4
+	  && x <= (double) LONG_MIN - 0.5)
+	{
+	  /* If truncation produces LONG_MIN, the cast will not raise
+	     the exception, but may raise "inexact".  */
+	  feraiseexcept (FE_INVALID);
+	  return LONG_MIN;
+	}
+#endif
       return (long int) x;
     }
 
diff --git a/sysdeps/ieee754/ldbl-128/s_llroundl.c b/sysdeps/ieee754/ldbl-128/s_llroundl.c
index 4adc50eaa4..235a433dbe 100644
--- a/sysdeps/ieee754/ldbl-128/s_llroundl.c
+++ b/sysdeps/ieee754/ldbl-128/s_llroundl.c
@@ -18,6 +18,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
+#include <limits.h>
 #include <math.h>
 
 #include <math_private.h>
@@ -60,13 +62,30 @@ __llroundl (long double x)
 	  if (j0 == 48)
 	    result = (long long int) i0;
 	  else
-	    result = ((long long int) i0 << (j0 - 48)) | (j >> (112 - j0));
+	    {
+	      result = ((long long int) i0 << (j0 - 48)) | (j >> (112 - j0));
+#ifdef FE_INVALID
+	      if (sign == 1 && result == LLONG_MIN)
+		/* Rounding brought the value out of range.  */
+		feraiseexcept (FE_INVALID);
+#endif
+	    }
 	}
     }
   else
     {
-      /* The number is too large.  It is left implementation defined
-	 what happens.  */
+      /* The number is too large.  Unless it rounds to LLONG_MIN,
+	 FE_INVALID must be raised and the return value is
+	 unspecified.  */
+#ifdef FE_INVALID
+      if (x <= (long double) LLONG_MIN - 0.5L)
+	{
+	  /* If truncation produces LLONG_MIN, the cast will not raise
+	     the exception, but may raise "inexact".  */
+	  feraiseexcept (FE_INVALID);
+	  return LLONG_MIN;
+	}
+#endif
       return (long long int) x;
     }
 
diff --git a/sysdeps/ieee754/ldbl-128/s_lroundl.c b/sysdeps/ieee754/ldbl-128/s_lroundl.c
index 64b285e291..3c6d26abe1 100644
--- a/sysdeps/ieee754/ldbl-128/s_lroundl.c
+++ b/sysdeps/ieee754/ldbl-128/s_lroundl.c
@@ -18,6 +18,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
+#include <limits.h>
 #include <math.h>
 
 #include <math_private.h>
@@ -47,6 +49,13 @@ __lroundl (long double x)
 	    {
 	      i0 += 0x0000800000000000LL >> j0;
 	      result = i0 >> (48 - j0);
+#ifdef FE_INVALID
+	      if (sizeof (long int) == 4
+		  && sign == 1
+		  && result == LONG_MIN)
+		/* Rounding brought the value out of range.  */
+		feraiseexcept (FE_INVALID);
+#endif
 	    }
 	}
       else if (j0 >= 112)
@@ -60,11 +69,32 @@ __lroundl (long double x)
 	  if (j0 == 48)
 	    result = (long int) i0;
 	  else
-	    result = ((long int) i0 << (j0 - 48)) | (j >> (112 - j0));
+	    {
+	      result = ((long int) i0 << (j0 - 48)) | (j >> (112 - j0));
+#ifdef FE_INVALID
+	      if (sizeof (long int) == 8
+		  && sign == 1
+		  && result == LONG_MIN)
+		/* Rounding brought the value out of range.  */
+		feraiseexcept (FE_INVALID);
+#endif
+	    }
 	}
     }
   else
     {
+      /* The number is too large.  Unless it rounds to LONG_MIN,
+	 FE_INVALID must be raised and the return value is
+	 unspecified.  */
+#ifdef FE_INVALID
+      if (x <= (long double) LONG_MIN - 0.5L)
+	{
+	  /* If truncation produces LONG_MIN, the cast will not raise
+	     the exception, but may raise "inexact".  */
+	  feraiseexcept (FE_INVALID);
+	  return LONG_MIN;
+	}
+#endif
       /* The number is too large.  It is left implementation defined
 	 what happens.  */
       return (long int) x;
diff --git a/sysdeps/ieee754/ldbl-96/s_llroundl.c b/sysdeps/ieee754/ldbl-96/s_llroundl.c
index 8381649269..17a3ccac08 100644
--- a/sysdeps/ieee754/ldbl-96/s_llroundl.c
+++ b/sysdeps/ieee754/ldbl-96/s_llroundl.c
@@ -17,6 +17,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
+#include <limits.h>
 #include <math.h>
 
 #include <math_private.h>
@@ -64,7 +66,14 @@ __llroundl (long double x)
 	    ++result;
 
 	  if (j0 > 31)
-	    result = (result << (j0 - 31)) | (j >> (63 - j0));
+	    {
+	      result = (result << (j0 - 31)) | (j >> (63 - j0));
+#ifdef FE_INVALID
+	      if (sign == 1 && result == LLONG_MIN)
+		/* Rounding brought the value out of range.  */
+		feraiseexcept (FE_INVALID);
+#endif
+	    }
 	}
     }
   else
diff --git a/sysdeps/ieee754/ldbl-96/s_lroundl.c b/sysdeps/ieee754/ldbl-96/s_lroundl.c
index 7a59835783..6bfc2560c3 100644
--- a/sysdeps/ieee754/ldbl-96/s_lroundl.c
+++ b/sysdeps/ieee754/ldbl-96/s_lroundl.c
@@ -17,6 +17,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <fenv.h>
+#include <limits.h>
 #include <math.h>
 
 #include <math_private.h>
@@ -49,6 +51,13 @@ __lroundl (long double x)
 	    }
 
 	  result = j >> (31 - j0);
+#ifdef FE_INVALID
+	  if (sizeof (long int) == 4
+	      && sign == 1
+	      && result == LONG_MIN)
+	    /* Rounding brought the value out of range.  */
+	    feraiseexcept (FE_INVALID);
+#endif
 	}
     }
   else if (j0 < (int32_t) (8 * sizeof (long int)) - 1)
@@ -66,13 +75,33 @@ __lroundl (long double x)
 	  if (j0 == 31)
 	    result = ures;
 	  else
-	    result = (ures << (j0 - 31)) | (j >> (63 - j0));
+	    {
+	      result = (ures << (j0 - 31)) | (j >> (63 - j0));
+#ifdef FE_INVALID
+	      if (sizeof (long int) == 8
+		  && sign == 1
+		  && result == LONG_MIN)
+		/* Rounding brought the value out of range.  */
+		feraiseexcept (FE_INVALID);
+#endif
+	    }
 	}
     }
   else
     {
-      /* The number is too large.  It is left implementation defined
-	 what happens.  */
+      /* The number is too large.  Unless it rounds to LONG_MIN,
+	 FE_INVALID must be raised and the return value is
+	 unspecified.  */
+#ifdef FE_INVALID
+      if (sizeof (long int) == 4
+	  && x <= (long double) LONG_MIN - 0.5L)
+	{
+	  /* If truncation produces LONG_MIN, the cast will not raise
+	     the exception, but may raise "inexact".  */
+	  feraiseexcept (FE_INVALID);
+	  return LONG_MIN;
+	}
+#endif
       return (long int) x;
     }