]> Savannah Git Hosting - gnulib.git/commitdiff
fmodl, remainder*: Avoid wrong results due to rounding errors.
authorBruno Haible <bruno@clisp.org>
Sat, 25 Feb 2012 23:36:01 +0000 (00:36 +0100)
committerBruno Haible <bruno@clisp.org>
Sat, 25 Feb 2012 23:36:01 +0000 (00:36 +0100)
* lib/fmodl.c (fmodl): Correct the result if it is not within the
expected bounds.
* lib/remainderf.c (remainderf): Likewise.
* lib/remainder.c (remainder): Likewise.
* lib/remainderl.c (remainderl): Likewise.

ChangeLog
lib/fmodl.c
lib/remainder.c
lib/remainderf.c
lib/remainderl.c

index e5f0d20014360f98b772821927455fba394f5707..1b6f2dd5a4b6334643ecca9970ab05864c2fd40c 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2012-02-25  Bruno Haible  <bruno@clisp.org>
+
+       fmodl, remainder*: Avoid wrong results due to rounding errors.
+       * lib/fmodl.c (fmodl): Correct the result if it is not within the
+       expected bounds.
+       * lib/remainderf.c (remainderf): Likewise.
+       * lib/remainder.c (remainder): Likewise.
+       * lib/remainderl.c (remainderl): Likewise.
+
 2012-02-25  Bruno Haible  <bruno@clisp.org>
 
        Tests for module 'remainderl'.
index 0c2542fe6f51d7d617405f89a479561a91313b9a..a7ccd8bcd582bc666190a3707ce4ff81507a0432 100644 (file)
@@ -32,8 +32,48 @@ fmodl (long double x, long double y)
 long double
 fmodl (long double x, long double y)
 {
-  long double i = truncl (x / y);
-  return fmal (- i, y, x);
+  long double q = - truncl (x / y);
+  long double r = fmal (q, y, x); /* = x + q * y, computed in one step */
+  /* Correct possible rounding errors in the quotient x / y.  */
+  if (y >= 0)
+    {
+      if (x >= 0)
+        {
+          /* Expect 0 <= r < y.  */
+          if (r < 0)
+            q += 1, r = fmal (q, y, x);
+          else if (r >= y)
+            q -= 1, r = fmal (q, y, x);
+        }
+      else
+        {
+          /* Expect - y < r <= 0.  */
+          if (r > 0)
+            q -= 1, r = fmal (q, y, x);
+          else if (r <= - y)
+            q += 1, r = fmal (q, y, x);
+        }
+    }
+  else
+    {
+      if (x >= 0)
+        {
+          /* Expect 0 <= r < - y.  */
+          if (r < 0)
+            q -= 1, r = fmal (q, y, x);
+          else if (r >= - y)
+            q += 1, r = fmal (q, y, x);
+        }
+      else
+        {
+          /* Expect y < r <= 0.  */
+          if (r > 0)
+            q += 1, r = fmal (q, y, x);
+          else if (r <= y)
+            q -= 1, r = fmal (q, y, x);
+        }
+    }
+  return r;
 }
 
 #endif
index c4e7c7ac0a6498422c0a4dd0a7c58778755cf2d9..16f09e5db0ce8d058a657316207570c2be6eeee8 100644 (file)
 double
 remainder (double x, double y)
 {
-  double i = round (x / y);
-  return fma (- i, y, x);
+  double q = - round (x / y);
+  double r = fma (q, y, x); /* = x + q * y, computed in one step */
+  /* Correct possible rounding errors in the quotient x / y.  */
+  double half_y = 0.5L * y;
+  if (y >= 0)
+    {
+      /* Expect -y/2 <= r <= y/2.  */
+      if (r > half_y)
+        q -= 1, r = fma (q, y, x);
+      else if (r < - half_y)
+        q += 1, r = fma (q, y, x);
+    }
+  else
+    {
+      /* Expect y/2 <= r <= -y/2.  */
+      if (r > - half_y)
+        q += 1, r = fma (q, y, x);
+      else if (r < half_y)
+        q -= 1, r = fma (q, y, x);
+    }
+  return r;
 }
index 5d9ddb8127df92c1606cb3368904953baa5764f2..d75cf2d39e0920e347a527745bf4cb55571fe754 100644 (file)
@@ -25,7 +25,26 @@ remainderf (float x, float y)
 #if HAVE_REMAINDER
   return (float) remainder ((double) x, (double) y);
 #else
-  float i = roundf (x / y);
-  return fmaf (- i, y, x);
+  float q = - roundf (x / y);
+  float r = fmaf (q, y, x); /* = x + q * y, computed in one step */
+  /* Correct possible rounding errors in the quotient x / y.  */
+  float half_y = 0.5L * y;
+  if (y >= 0)
+    {
+      /* Expect -y/2 <= r <= y/2.  */
+      if (r > half_y)
+        q -= 1, r = fmaf (q, y, x);
+      else if (r < - half_y)
+        q += 1, r = fmaf (q, y, x);
+    }
+  else
+    {
+      /* Expect y/2 <= r <= -y/2.  */
+      if (r > - half_y)
+        q += 1, r = fmaf (q, y, x);
+      else if (r < half_y)
+        q -= 1, r = fmaf (q, y, x);
+    }
+  return r;
 #endif
 }
index b43592a6446f3a913a542fffb93979b039364d1f..3476f948e27af4ca1e40a598cac3a0aeb1d8a98f 100644 (file)
@@ -32,8 +32,27 @@ remainderl (long double x, long double y)
 long double
 remainderl (long double x, long double y)
 {
-  long double i = roundl (x / y);
-  return fmal (- i, y, x);
+  long double q = - roundl (x / y);
+  long double r = fmal (q, y, x); /* = x + q * y, computed in one step */
+  /* Correct possible rounding errors in the quotient x / y.  */
+  long double half_y = 0.5L * y;
+  if (y >= 0)
+    {
+      /* Expect -y/2 <= r <= y/2.  */
+      if (r > half_y)
+        q -= 1, r = fmal (q, y, x);
+      else if (r < - half_y)
+        q += 1, r = fmal (q, y, x);
+    }
+  else
+    {
+      /* Expect y/2 <= r <= -y/2.  */
+      if (r > - half_y)
+        q += 1, r = fmal (q, y, x);
+      else if (r < half_y)
+        q -= 1, r = fmal (q, y, x);
+    }
+  return r;
 }
 
 #endif