Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 11 Jun 2013 11:22:09 +1000 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Bruce Evans <brde@optusnet.com.au>
Cc:        David Schultz <das@freebsd.org>, freebsd-numerics@freebsd.org, Steve Kargl <sgk@troutmask.apl.washington.edu>
Subject:   Re: Implementation for coshl.
Message-ID:  <20130611104252.G50308@besplex.bde.org>
In-Reply-To: <20130611040059.A17928@besplex.bde.org>
References:  <20130610003645.GA16444@troutmask.apl.washington.edu> <20130610110740.V24058@besplex.bde.org> <20130610055834.GA68643@zim.MIT.EDU> <20130610162927.GA20856@troutmask.apl.washington.edu> <20130610170603.GA72597@zim.MIT.EDU> <20130611040059.A17928@besplex.bde.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Tue, 11 Jun 2013, Bruce Evans wrote:

> On Mon, 10 Jun 2013, David Schultz wrote:
>
>> On Mon, Jun 10, 2013, Steve Kargl wrote:
>...
>>> I originally had
>>> 
>>>                 if (ix < BIAS - 33) {   /* |x| < 0x1p-33 */
>>>                         /* cosh(+-0) = 1. */
>>>                         if (x == 0)
>>>                                 return (one);
>>>                         /* cosh(tiny) = one */
>>>                         return (one + tiny * tiny);
>>>                 }
>>> 
>>> because C99 requires cosh(+-0) to be exact.  For |x| != the
>>> return value will raise inexact.
> ...
> No, the above is quite broken.  tiny * tiny gives underflow, and
> when the underflow is to 0, adding 1 to it doesn't give inexact.
> Also, the 'tiny' in the comment is confusing, since it refers to
> a different variable than the 'tiny' in the code.
>
> Just copy the known-good fdlibm code.  It uses one+t, where t is
> expm1(fabs(x)).  This has many subtleties:
> - it avoids pessimizing the usual case
> - it moves all the complications for setting inexact to expm1().  These
>  include:
>  - the expression 1+x doesn't work for tiny x since it rounds incorrectly
>    in some modes.  It also requires x >= 0 for correct rounding, so
>    we may have to caclulate |x| earlier than we want.
>  - the expression 1+x*x doesn't work for tiny x since it may underflow
>    (just like 1+tiny*tiny)
>  - the expression x doesn't work for tiny x in expm1() since it doesn't
>    set inexact.
>  - the expression x+x*x doesn't work for tiny x in expm1() since it may
>    underflow.

Bah, this is more magic and broken than first appeared.  The fdlibm code
is now known-bad :-).  I stumbled across this when implementing the use of
k_expf() in ccoshf().

o One subtlety in the fdlibm cosh() is that it uses an artificially small
   threshold of 2**-55 to "ensure" that 1+this gives 1 with inexact
   (actually 1+eps in some rounding modes).  A threshold of about 2**26.5
   is sufficient for cosh() to decay to 1, but fdlibm wants to abuse the
   tiny value given by each arg below the threshold for setting inexact
   as a side effect of evaluating the expression 'one + expm1f(x)'.  This
   obviously requires a threshold of <= 2**-53 or 2**-54.  The value of
   2**-55 actually used makes no sense.
o I broke this in coshf() in 2005 when fixing its threshold.  The
   threshold of 2**-55 was blindly copied.  It should have been reduced
   to 2**-26 for a direct translation.  I changed it to the natural
   threshold of 2**-12.  This also gives a micro-optimization.  This
   made extra-precision bugs more apparent, and I fixed them by
   returning 'one' instead of 'one + expm1f(x)' for tiny x.  Returning
   'one' gives another micro-optimization.  But this weakens the support
   for non-default rounding modes.
o fdlibm cosh()'s large threshold is not large enough to actually work.
   'one + 2**-55' may be evaluated and returned in extra precision, and
   it only takes 2  or 3 bits extra for it to evaluate to itself instead
   of 1.  coshf()'s blindly copied threshold accidentally protected
   against using '1.0F + 2**-26F' which always evaluates to itself on
   i386, and is then normally returned unchanged.  Assertions like
   assert(cosh(tiny) == 1) should then fail, but cosh() may return
   extra precision so is also a bug in the assertions -- essentially
   the same one that I found recently for exp() non-overlow.  This
   is still a bug in cosh() which would be found by a more careful
   assertion -- cosh(x) may be evaluated in extra precision, but then
   its value must be ~1+x*x/2, not ~1+|x|.

FreeBSD's default rounding precision protects against these bugs in both
float and double precision provided the theshold is <= 2**-53 for both.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20130611104252.G50308>