about summary refs log tree commit diff
diff options
context:
space:
mode:
authornsz <nsz@port70.net>2012-06-20 23:25:58 +0200
committernsz <nsz@port70.net>2012-06-20 23:25:58 +0200
commite5fb6820a42a1f675ba09c15273953e1ace65777 (patch)
tree19d075252fdb22352fd147e7c4333cfa015d3b21
parentac4fb51dde2f1fe9371a4cb51ff309d4868245ec (diff)
downloadmusl-e5fb6820a42a1f675ba09c15273953e1ace65777.tar.gz
musl-e5fb6820a42a1f675ba09c15273953e1ace65777.tar.xz
musl-e5fb6820a42a1f675ba09c15273953e1ace65777.zip
math: fix fma bug on x86 (found by Bruno Haible with gnulib)
The long double adjustment was wrong:
The usual check is
  mant_bits & 0x7ff == 0x400
before doing a mant_bits++ or mant_bits-- adjustment since
this is the only case when rounding an inexact ld80 into
double can go wrong. (only in nearest rounding mode)

After such a check the ++ and -- is ok (the mantissa will end
in 0x401 or 0x3ff).

fma is a bit different (we need to add 3 numbers with correct
rounding: hi_xy + lo_xy + z so we should survive two roundings
at different places without precision loss)

The adjustment in fma only checks for zero low bits
  mant_bits & 0x3ff == 0
this way the adjusted value is correct when rounded to
double or *less* precision.
(this is an important piece in the fma puzzle)

Unfortunately in this case the -- is not a correct adjustment
because mant_bits might underflow so further checks are needed
and this was the source of the bug.
-rw-r--r--src/math/fma.c12
1 files changed, 10 insertions, 2 deletions
diff --git a/src/math/fma.c b/src/math/fma.c
index 5fb95406..7076d4b9 100644
--- a/src/math/fma.c
+++ b/src/math/fma.c
@@ -41,7 +41,7 @@ static void mul(long double *hi, long double *lo, long double x, long double y)
 
 /*
 assume (long double)(hi+lo) == hi
-return an adjusted hi so that rounding it to double is correct
+return an adjusted hi so that rounding it to double (or less) precision is correct
 */
 static long double adjust(long double hi, long double lo)
 {
@@ -55,17 +55,25 @@ static long double adjust(long double hi, long double lo)
 	ulo.x = lo;
 	if (uhi.bits.s == ulo.bits.s)
 		uhi.bits.m++;
-	else
+	else {
 		uhi.bits.m--;
+		/* handle underflow and take care of ld80 implicit msb */
+		if (uhi.bits.m == (uint64_t)-1/2) {
+			uhi.bits.m *= 2;
+			uhi.bits.e--;
+		}
+	}
 	return uhi.x;
 }
 
+/* adjusted add so the result is correct when rounded to double (or less) precision */
 static long double dadd(long double x, long double y)
 {
 	add(&x, &y, x, y);
 	return adjust(x, y);
 }
 
+/* adjusted mul so the result is correct when rounded to double (or less) precision */
 static long double dmul(long double x, long double y)
 {
 	mul(&x, &y, x, y);