JuliaMath / openlibm

High quality system independent, portable, open source libm implementation

Home Page:https://openlibm.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

bad accuracy of powf for integer exponent

zimmermann6 opened this issue · comments

while doing some heuristic search for large errors (in ulps) of the single-precision powf function (with rounding to nearest) I noticed that for integer exponent we can get some quite bad accuracy.

One example is x=0xd.65874p-4f and y=4.0f, where I get an error of 1.9ulps with respect to the infinite precision result.

This is with OpenLibm 0.4.1 on x86_64 under Linux.

Is it possible for you to try it in the latest release? The library just takes a couple of mins to download and build and try out.

the last tar.gz available on https://openlibm.org/ is 0.4.1, this is the one I used

I don't that website has been updated in quite a while. You can get the 0.7.0 tarball from the GitHub releases page: https://github.com/JuliaMath/openlibm/archive/v0.7.0.tar.gz. You could also do a git clone.

We should update the website. Thanks for bringing to our notice. Most people just download straight from GitHub.

Website updated.

commented

@zimmermann6 can you tell us what value powf(x,y) returned and the value you expect. powf from FreeBSD matches the code in Openlibm. With MPFR set to 96-bit precision, I get

% ./testf -a 0xd.65874p-4f 4.0f
libm = 4.91470873e-01f, /* 0x3efba212 */
mpfr = 4.91470873e-01f, /* 0x3efba212 */
 ulp = 0.09839

Steve, here is what I get (same with 0.7.0):

#include <stdio.h>
#include <math.h>

int
main()
{
  float x = 0xd.65874p-4f;
  float y = 4.0f;
  float z = powf (x, y);
  printf ("z=%a\n", z);
}
$ gcc bug_openlibm.c /localdisk/zimmerma/openlibm-0.4.1/libopenlibm.a
$ ./a.out
z=0x1.f7442p-2

The expected value is 0x7.dd109p-4=0x1f74424p-2, which is the one I get with GNU libc.

I confirm this fixes the example I gave, now we get the same result as glibc. However for x=0x1.ffffeep-1 and y=-0x1.000002p+27 openlibm powf(x,y) gives +Inf whereas the correct result is 0x1.d53532p+103:

$ cat test_pow_openlibm.c
#include <stdio.h>
#include <math.h>

int main()
{
float x = 0x1.ffffeep-1f;
float y = -0x1.000002p+27f;
float z = powf (x, y);
printf ("x=%a y=%a z=%a\n", x, y, z);
}

$ gcc -fno-builtin test_pow_openlibm.c -lm
$ ./a.out
x=0x1.ffffeep-1 y=-0x1.000002p+27 z=0x1.d53532p+103

$ gcc -fno-builtin test_pow_openlibm.c /tmp/openlibm/libopenlibm.a
$ ./a.out
x=0x1.ffffeep-1 y=-0x1.000002p+27 z=inf

Should I open a separate issue for that?

Seems to be the same general issue so I think we can reopen this.

Ah well - this is most likely then an issue in the freebsd msun.

commented

Interesting corner case x->1 and y >> 1. This patch, lacking any additional analysis, fixes the issue on FreeBSD.

 Index: src/e_powf.c
 ===================================================================
 --- src/e_powf.c        (revision 2342)
 +++ src/e_powf.c        (working copy)
@@ -136,7 +136,7 @@ __ieee754_powf(float x, float y)
     /* |y| is huge */
        if(iy>0x4d000000) { /* if |y| > 2**27 */
        /* over/underflow if x is not close to one */
-           if(ix<0x3f7ffff8) return (hy<0)? sn*huge*huge:sn*tiny*tiny;
+           if(ix<0x3f7ffff7) return (hy<0)? sn*huge*huge:sn*tiny*tiny;
            if(ix>0x3f800007) return (hy>0)? sn*huge*huge:sn*tiny*tiny;
        /* now |1-x| is tiny <= 2**-20, suffice to compute
           log(x) by x-x^2/2+x^3/3-x^4/4 */

I confirm this fixes the issue. Now the largest error I find with my program in a quick test is less than 1 ulp.

Thanks, committed in FreeBSD here: freebsd/freebsd-src@93fc678