Inline version [x86] of exp is buggy, exp(-Inf) should be zero.

Peter Dalgaard BSA p.dalgaard@biostat.ku.dk
04 May 2000 16:52:00 +0200


During the development of the R package (www.r-project.org) we
stumbled over some boundary cases where we were calculating
exp(log(0)) and it was giving wrong results.

There seems to be a bug in glibc2.1 on Intel architectures:

$ gcc glibc-bug.c -lm && ./a.out
0
$ gcc -O glibc-bug.c -lm && ./a.out
nan
$ cat glibc-bug.c
#include<stdio.h>
#include<math.h>
main(){
        volatile double neginf = -1./0.;
        printf("%g\n", exp(neginf));
}

I'm pretty sure one does want exp(-Inf) to be zero, not NaN, although
I don't know whether it is in the IEEE standard. The problem can be
traced to the subtraction of two infinities in the inline math code.

I took the liberty of picking the brains of Bill Metzenthen (of FPU
emulation fame) and he came up with the enclosed suggestion for a patch.

-- 
   O__  ---- Peter Dalgaard             Blegdamsvej 3  
  c/ /'_ --- Dept. of Biostatistics     2200 Cph. N   
 (*) \(*) -- University of Copenhagen   Denmark      Ph: (+45) 35327918
~~~~~~~~~~ - (p.dalgaard@biostat.ku.dk)             FAX: (+45) 35327907
------- Start of forwarded message -------
From: Personal files for Bill <billm@melbpc.org.au>
Message-Id: <200004281221.WAA07092@melbpc.lorentz.au>
Subject: Re: IEEE standard for exp(-Inf)
To: p.dalgaard@biostat.ku.dk (Peter Dalgaard BSA)
Date: Fri, 28 Apr 2000 22:21:28 +1000 (EST)
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Type: multipart/mixed; boundary=ELM956924487-5786-0_




--ELM956924487-5786-0_
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit

Peter,
      you wrote:

> You wouldn't happen to be able to cook up a patch that fixes the
> assembler code while using zero extra clock cycles, would you (well,
> ok, near-zero...)? It might be easier to convince the glibc guys to
> fix things if they were given a patch.

I don't know of any neat trick to solve this problem.  The obvious fix
(fxam, conditional jump, etc) has a relatively low cost (at least on a
486 where f2xm1 costs between 140 and 279 cycles) in terms of speed
but does add bytes.

I've attched a patch to this message which will do the job.


Cheers,
       Bill


--ELM956924487-5786-0_
Content-Type: *unknown*/
Content-Disposition: attachment; filename=glibc-patch
Content-Description: /tmp/glibc-patch
Content-Transfer-Encoding: 7bit

--- /usr/include/bits/mathinline.h~	Sun Aug 22 11:02:02 1999
+++ /usr/include/bits/mathinline.h	Fri Apr 28 21:35:41 2000
@@ -332,13 +332,21 @@
   register long double __value;						      \
   register long double __exponent;					      \
   __asm __volatile__							      \
-    ("fldl2e			# e^x = 2^(x * log2(e))\n\t"		      \
+    ("fxam\n\t"								      \
+     "fstsw %%ax\n\t"							      \
+     "sahf\n\t"								      \
+     "jnc 1f\n\t"							      \
+     "fld1\n\t"								      \
+     "jmp 2f			# st(1) has NaN or Infinity\n\t"	      \
+     "1:\n\t"								      \
+     "fldl2e			# e^x = 2^(x * log2(e))\n\t"		      \
      "fmul	%%st(1)		# x * log2(e)\n\t"			      \
      "fst	%%st(1)\n\t"						      \
      "frndint			# int(x * log2(e))\n\t"			      \
      "fxch\n\t"								      \
      "fsub	%%st(1)		# fract(x * log2(e))\n\t"		      \
      "f2xm1			# 2^(fract(x * log2(e))) - 1\n\t"	      \
+     "2:\n\t"								      \
      : "=t" (__value), "=u" (__exponent) : "0" (__x));			      \
   __value += 1.0;							      \
   __asm __volatile__							      \

--ELM956924487-5786-0_--

------- End of forwarded message -------