From e5fb6820a42a1f675ba09c15273953e1ace65777 Mon Sep 17 00:00:00 2001 From: nsz Date: Wed, 20 Jun 2012 23:25:58 +0200 Subject: 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. --- src/math/fma.c | 12 ++++++++++-- 1 file 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); -- cgit v1.2.1