From b1cbd70743f0e0e0295e92c3e38d7599114db8c6 Mon Sep 17 00:00:00 2001 From: nsz Date: Mon, 19 Mar 2012 00:36:55 +0100 Subject: add fma implementation for x86 correctly rounded double precision fma using extended precision arithmetics for ld80 systems (x87) --- src/math/fma.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 7 deletions(-) diff --git a/src/math/fma.c b/src/math/fma.c index f44ecda7..a9de2cb3 100644 --- a/src/math/fma.c +++ b/src/math/fma.c @@ -1,3 +1,141 @@ +#include +#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; +} + +/* +TODO(nsz): probably simpler mul is enough if we assume x and y are doubles +so last 11bits are all zeros, no subnormals etc +*/ +/* 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); + /* TODO: verify that the sign of 0 is always 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 @@ -25,9 +163,6 @@ * SUCH DAMAGE. */ -#include -#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 +313,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 +413,4 @@ double fma(double x, double y, double z) else return (add_and_denormalize(r.hi, adj, spread)); } +#endif -- cgit v1.2.1 From d09a83f613c1d06442ed920ec55a0e5eedacb422 Mon Sep 17 00:00:00 2001 From: nsz Date: Mon, 19 Mar 2012 00:59:16 +0100 Subject: fmal bug fix: nan input should not raise exception --- src/math/fmal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); -- cgit v1.2.1 From 682e471400da612769b6e061f2e377db837274ef Mon Sep 17 00:00:00 2001 From: nsz Date: Mon, 19 Mar 2012 02:05:57 +0100 Subject: remove unnecessary TODO comments from fma.c --- src/math/fma.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/math/fma.c b/src/math/fma.c index a9de2cb3..87d450c7 100644 --- a/src/math/fma.c +++ b/src/math/fma.c @@ -23,10 +23,6 @@ static void add(long double *hi, long double *lo, long double x, long double y) *lo = y - r; } -/* -TODO(nsz): probably simpler mul is enough if we assume x and y are doubles -so last 11bits are all zeros, no subnormals etc -*/ /* exact mul, assumes no over/underflow */ static void mul(long double *hi, long double *lo, long double x, long double y) { @@ -115,7 +111,7 @@ double fma(double x, double y, double z) add(&hi, &lo2, xy, z); if (hi == 0) { fesetround(round); - /* TODO: verify that the sign of 0 is always correct */ + /* make sure that the sign of 0 is correct */ return (xy + z) + lo1; } } else { -- cgit v1.2.1 From 8051e08e10d2b739fcfcbc6bc7466e8d77fa49f1 Mon Sep 17 00:00:00 2001 From: nsz Date: Mon, 19 Mar 2012 10:54:07 +0100 Subject: simplify scalbn*.c implementations The old scalbn.c was wrong and slow, the new one is just slow. (scalbn(0x1p+1023,-2097) should give 0x1p-1074, but the old code gave 0) --- src/math/scalbn.c | 76 +++++++++++++++--------------------------------------- src/math/scalbnf.c | 70 ++++++++++++++++--------------------------------- src/math/scalbnl.c | 71 ++++++++++++++++---------------------------------- 3 files changed, 65 insertions(+), 152 deletions(-) diff --git a/src/math/scalbn.c b/src/math/scalbn.c index b51551b5..c9c7af80 100644 --- a/src/math/scalbn.c +++ b/src/math/scalbn.c @@ -1,62 +1,28 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_scalbn.c */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ -/* - * scalbn (double x, int n) - * scalbn(x,n) returns x* 2**n computed by exponent - * manipulation rather than by actually performing an - * exponentiation or a multiplication. - */ - #include "libm.h" -static const double -two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ -twom54 = 5.55111512312578270212e-17, /* 0x3C900000, 0x00000000 */ -huge = 1.0e+300, -tiny = 1.0e-300; - double scalbn(double x, int n) { -// FIXME: k+n check depends on signed int overflow.. use unsigned hx -// TODO: when long != int: -// scalbln(x,long n) { if(n>9999)n=9999; else if(n<-9999)n=-9999; return scalbn(x,n); } -// TODO: n < -50000 ... - int32_t k,hx,lx; + double scale; - EXTRACT_WORDS(hx, lx, x); - k = (hx&0x7ff00000)>>20; /* extract exponent */ - if (k == 0) { /* 0 or subnormal x */ - if ((lx|(hx&0x7fffffff)) == 0) /* +-0 */ - return x; - x *= two54; - GET_HIGH_WORD(hx, x); - k = ((hx&0x7ff00000)>>20) - 54; - if (n < -50000) - return tiny*x; /*underflow*/ - } - if (k == 0x7ff) /* NaN or Inf */ - return x + x; - k = k + n; - if (k > 0x7fe) - return huge*copysign(huge, x); /* overflow */ - if (k > 0) { /* normal result */ - SET_HIGH_WORD(x, (hx&0x800fffff)|(k<<20)); - return x; + if (n > 1023) { + x *= 0x1p1023; + n -= 1023; + if (n > 1023) { + x *= 0x1p1023; + n -= 1023; + if (n > 1023) + return x * 0x1p1023; + } + } else if (n < -1022) { + x *= 0x1p-1022; + n += 1022; + if (n < -1022) { + x *= 0x1p-1022; + n += 1022; + if (n < -1022) + return x * 0x1p-1022; + } } - if (k <= -54) - if (n > 50000) /* in case integer overflow in n+k */ - return huge*copysign(huge, x); /*overflow*/ - return tiny*copysign(tiny, x); /*underflow*/ - k += 54; /* subnormal result */ - SET_HIGH_WORD(x, (hx&0x800fffff)|(k<<20)); - return x*twom54; + INSERT_WORDS(scale, (uint32_t)(0x3ff+n)<<20, 0); + return x * scale; } diff --git a/src/math/scalbnf.c b/src/math/scalbnf.c index 0a6168b0..243dafdf 100644 --- a/src/math/scalbnf.c +++ b/src/math/scalbnf.c @@ -1,54 +1,28 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_scalbnf.c */ -/* - * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com. - */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ - #include "libm.h" -static const float -two25 = 3.355443200e+07, /* 0x4c000000 */ -twom25 = 2.9802322388e-08, /* 0x33000000 */ -huge = 1.0e+30, -tiny = 1.0e-30; - float scalbnf(float x, int n) { - int32_t k, ix; - GET_FLOAT_WORD(ix, x); - k = (ix&0x7f800000)>>23; /* extract exponent */ - if (k == 0) { /* 0 or subnormal x */ - if ((ix&0x7fffffff) == 0) /* +-0 */ - return x; - x *= two25; - GET_FLOAT_WORD(ix, x); - k = ((ix&0x7f800000)>>23) - 25; - if (n < -50000) - return tiny*x; /*underflow*/ - } - if (k == 0xff) /* NaN or Inf */ - return x + x; - k = k + n; - if (k > 0xfe) - return huge*copysignf(huge, x); /* overflow */ - if (k > 0) { /* normal result */ - SET_FLOAT_WORD(x, (ix&0x807fffff)|(k<<23)); - return x; + float scale; + + if (n > 127) { + x *= 0x1p127f; + n -= 127; + if (n > 127) { + x *= 0x1p127f; + n -= 127; + if (n > 127) + return x * 0x1p127f; + } + } else if (n < -126) { + x *= 0x1p-126f; + n += 126; + if (n < -126) { + x *= 0x1p-126f; + n += 126; + if (n < -126) + return x * 0x1p-126f; + } } - if (k <= -25) - if (n > 50000) /* in case integer overflow in n+k */ - return huge*copysignf(huge,x); /*overflow*/ - return tiny*copysignf(tiny, x); /*underflow*/ - k += 25; /* subnormal result */ - SET_FLOAT_WORD(x, (ix&0x807fffff)|(k<<23)); - return x*twom25; + SET_FLOAT_WORD(scale, (uint32_t)(0x7f+n)<<23); + return x * scale; } diff --git a/src/math/scalbnl.c b/src/math/scalbnl.c index 0ed5b7fd..a5d0adba 100644 --- a/src/math/scalbnl.c +++ b/src/math/scalbnl.c @@ -1,21 +1,3 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_scalbnl.c */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ -/* - * scalbnl (long double x, int n) - * scalbnl(x,n) returns x* 2**n computed by exponent - * manipulation rather than by actually performing an - * exponentiation or a multiplication. - */ - #include "libm.h" #if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 @@ -24,40 +6,31 @@ long double scalbnl(long double x, int n) return scalbn(x, n); } #elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384 -static const long double -huge = 0x1p16000L, -tiny = 0x1p-16000L; - long double scalbnl(long double x, int n) { - union IEEEl2bits u; - int k; + union IEEEl2bits scale; - u.e = x; - k = u.bits.exp; /* extract exponent */ - if (k == 0) { /* 0 or subnormal x */ - if ((u.bits.manh|u.bits.manl) == 0) /* +-0 */ - return x; - u.e *= 0x1p128; - k = u.bits.exp - 128; - if (n < -50000) - return tiny*x; /*underflow*/ - } - if (k == 0x7fff) /* NaN or Inf */ - return x + x; - k = k + n; - if (k >= 0x7fff) - return huge*copysignl(huge, x); /* overflow */ - if (k > 0) { /* normal result */ - u.bits.exp = k; - return u.e; + if (n > 16383) { + x *= 0x1p16383L; + n -= 16383; + if (n > 16383) { + x *= 0x1p16383L; + n -= 16383; + if (n > 16383) + return x * 0x1p16383L; + } + } else if (n < -16382) { + x *= 0x1p-16382L; + n += 16382; + if (n < -16382) { + x *= 0x1p-16382L; + n += 16382; + if (n < -16382) + return x * 0x1p-16382L; + } } - if (k <= -128) - if (n > 50000) /* in case integer overflow in n+k */ - return huge*copysign(huge, x); /*overflow*/ - return tiny*copysign(tiny, x); /*underflow*/ - k += 128; /* subnormal result */ - u.bits.exp = k; - return u.e*0x1p-128; + scale.e = 1.0L; + scale.bits.exp = 0x3fff + n; + return x * scale.e; } #endif -- cgit v1.2.1