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.
This commit is contained in:
parent
ac4fb51dde
commit
e5fb6820a4
1 changed files with 10 additions and 2 deletions
|
|
@ -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
|
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)
|
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;
|
ulo.x = lo;
|
||||||
if (uhi.bits.s == ulo.bits.s)
|
if (uhi.bits.s == ulo.bits.s)
|
||||||
uhi.bits.m++;
|
uhi.bits.m++;
|
||||||
else
|
else {
|
||||||
uhi.bits.m--;
|
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;
|
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)
|
static long double dadd(long double x, long double y)
|
||||||
{
|
{
|
||||||
add(&x, &y, x, y);
|
add(&x, &y, x, y);
|
||||||
return adjust(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)
|
static long double dmul(long double x, long double y)
|
||||||
{
|
{
|
||||||
mul(&x, &y, x, y);
|
mul(&x, &y, x, y);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue