diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/math/fma.c | 146 | ||||
| -rw-r--r-- | src/math/fmal.c | 8 | 
2 files changed, 143 insertions, 11 deletions
| diff --git a/src/math/fma.c b/src/math/fma.c index f44ecda7..87d450c7 100644 --- a/src/math/fma.c +++ b/src/math/fma.c @@ -1,3 +1,137 @@ +#include <fenv.h> +#include "libm.h" + +#if LDBL_MANT_DIG==64 && LDBL_MAX_EXP==16384 +union ld80 { +	long double x; +	struct { +		uint64_t m; +		uint16_t e : 15; +		uint16_t s : 1; +		uint16_t pad; +	} bits; +}; + +/* exact add, assumes exponent_x >= exponent_y */ +static void add(long double *hi, long double *lo, long double x, long double y) +{ +	long double r; + +	r = x + y; +	*hi = r; +	r -= x; +	*lo = y - r; +} + +/* exact mul, assumes no over/underflow */ +static void mul(long double *hi, long double *lo, long double x, long double y) +{ +	static const long double c = 1.0 + 0x1p32L; +	long double cx, xh, xl, cy, yh, yl; + +	cx = c*x; +	xh = (x - cx) + cx; +	xl = x - xh; +	cy = c*y; +	yh = (y - cy) + cy; +	yl = y - yh; +	*hi = x*y; +	*lo = (xh*yh - *hi) + xh*yl + xl*yh + xl*yl; +} + +/* +assume (long double)(hi+lo) == hi +return an adjusted hi so that rounding it to double is correct +*/ +static long double adjust(long double hi, long double lo) +{ +	union ld80 uhi, ulo; + +	if (lo == 0) +		return hi; +	uhi.x = hi; +	if (uhi.bits.m & 0x3ff) +		return hi; +	ulo.x = lo; +	if (uhi.bits.s == ulo.bits.s) +		uhi.bits.m++; +	else +		uhi.bits.m--; +	return uhi.x; +} + +static long double dadd(long double x, long double y) +{ +	add(&x, &y, x, y); +	return adjust(x, y); +} + +static long double dmul(long double x, long double y) +{ +	mul(&x, &y, x, y); +	return adjust(x, y); +} + +static int getexp(long double x) +{ +	union ld80 u; +	u.x = x; +	return u.bits.e; +} + +double fma(double x, double y, double z) +{ +	long double hi, lo1, lo2, xy; +	int round, ez, exy; + +	/* handle +-inf,nan */ +	if (!isfinite(x) || !isfinite(y)) +		return x*y + z; +	if (!isfinite(z)) +		return z; +	/* handle +-0 */ +	if (x == 0.0 || y == 0.0) +		return x*y + z; +	round = fegetround(); +	if (z == 0.0) { +		if (round == FE_TONEAREST) +			return dmul(x, y); +		return x*y; +	} + +	/* exact mul and add require nearest rounding */ +	/* spurious inexact exceptions may be raised */ +	fesetround(FE_TONEAREST); +	mul(&xy, &lo1, x, y); +	exy = getexp(xy); +	ez = getexp(z); +	if (ez > exy) { +		add(&hi, &lo2, z, xy); +	} else if (ez > exy - 12) { +		add(&hi, &lo2, xy, z); +		if (hi == 0) { +			fesetround(round); +			/* make sure that the sign of 0 is correct */ +			return (xy + z) + lo1; +		} +	} else { +		/* +		ez <= exy - 12 +		the 12 extra bits (1guard, 11round+sticky) are needed so with +			lo = dadd(lo1, lo2) +		elo <= ehi - 11, and we use the last 10 bits in adjust so +			dadd(hi, lo) +		gives correct result when rounded to double +		*/ +		hi = xy; +		lo2 = z; +	} +	fesetround(round); +	if (round == FE_TONEAREST) +		return dadd(hi, dadd(lo1, lo2)); +	return hi + (lo1 + lo2); +} +#else  /* origin: FreeBSD /usr/src/lib/msun/src/s_fma.c */  /*-   * Copyright (c) 2005-2011 David Schultz <das@FreeBSD.ORG> @@ -25,9 +159,6 @@   * SUCH DAMAGE.   */ -#include <fenv.h> -#include "libm.h" -  /*   * A struct dd represents a floating-point number with twice the precision   * of a double.  We maintain the invariant that "hi" stores the 53 high-order @@ -178,14 +309,14 @@ double fma(double x, double y, double z)  	 * return values here are crucial in handling special cases involving  	 * infinities, NaNs, overflows, and signed zeroes correctly.  	 */ -	if (x == 0.0 || y == 0.0) -		return (x * y + z); -	if (z == 0.0) -		return (x * y);  	if (!isfinite(x) || !isfinite(y))  		return (x * y + z);  	if (!isfinite(z))  		return (z); +	if (x == 0.0 || y == 0.0) +		return (x * y + z); +	if (z == 0.0) +		return (x * y);  	xs = frexp(x, &ex);  	ys = frexp(y, &ey); @@ -278,3 +409,4 @@ double fma(double x, double y, double z)  	else  		return (add_and_denormalize(r.hi, adj, spread));  } +#endif diff --git a/src/math/fmal.c b/src/math/fmal.c index 3944c292..cbaf46eb 100644 --- a/src/math/fmal.c +++ b/src/math/fmal.c @@ -173,14 +173,14 @@ long double fmal(long double x, long double y, long double z)  	 * return values here are crucial in handling special cases involving  	 * infinities, NaNs, overflows, and signed zeroes correctly.  	 */ -	if (x == 0.0 || y == 0.0) -		return (x * y + z); -	if (z == 0.0) -		return (x * y);  	if (!isfinite(x) || !isfinite(y))  		return (x * y + z);  	if (!isfinite(z))  		return (z); +	if (x == 0.0 || y == 0.0) +		return (x * y + z); +	if (z == 0.0) +		return (x * y);  	xs = frexpl(x, &ex);  	ys = frexpl(y, &ey); | 
