Use of C99 extra long double math functions after r236148
Bruce Evans
brde at optusnet.com.au
Sun Aug 12 23:10:05 UTC 2012
On Fri, 20 Jul 2012, Bruce Evans wrote:
> I was going to leave all the fixes to you, but now want to try the
> approximation for tiny y :-).
This version now beats Apple clog() for accuracy. The maximum difference
observed is down from the exa-ulp range to 16 ulps with all of the 4-16
ulp differences checked against pari being innaccuracies in Apple clog().
2**28 cases were tested, but most of the errors found look like this:
% x = 0x3fe0000000000000 0.5
% y = 0x3fec000000000000 0.875
% loghypota(x, y) = 0x3ff7fe054587e01ea000 0x3f7fc0a8b0fc03d4 0.00775209326798261336156
% loghypot(x, y) = 0x3ff7fe054587e01f2000 0x3f7fc0a8b0fc03e4 0.00775209326798262723934
% err = +0x8000 16.00000
% pari log(0.5+0.875*I):
% 0.007752093267982627075427023021 + 1.051650212548373667459867312*I
so my tests barely cover fractions like 1/8 and the coverage may be too
limited. New errors kept turning up as I expanded the coverage.
% #include <complex.h>
% #include <float.h>
% #include <math.h>
% #include <fenv.h>
%
% #include "math_private.h"
%
% /*
% * gcc doesn't implement complex multiplication or division correctly, so we
% * need to handle infinities specially. We turn on this pragma to notify
% * conforming c99 compilers that the fast-but-incorrect code that gcc
% * generates is acceptable, since the special cases have already been
% * handled.
% */
% #pragma STDC CX_LIMITED_RANGE ON
%
% double complex
% clog(double complex z)
% {
% double x, y, h, t1, t2, t3;
% double x0, y0, x1, y1;
%
% x = creal(z);
% y = cimag(z);
%
% #define NANMIX_APPLE_CLOG_COMPAT 1
% /* Handle special cases when x or y is NAN. */
% if (isnan(x)) {
% if (isinf(y))
% return (cpack(INFINITY, NAN));
% else {
% #if NANMIX_HYPOTF_COMPAT
% y = fabs(y + 0);
% t1 = (y - y) / (y - y); /* Raise invalid flag if y is
% * not NaN */
% t1 = fabs(x + 0) + t1; /* Mix NaN(s). */
% #elif NANMIX_APPLE_CLOG_COMPAT
% /* No actual mixing. */
% return (cpack(x, copysign(x, y)));
% #else
% t1 = (y - y) / (y - y); /* Raise invalid flag if y is
% * not NaN */
% #endif
% return (cpack(t1, t1));
% }
% } else if (isnan(y)) {
% if (isinf(x))
% return (cpack(INFINITY, NAN));
% else {
% #ifdef NANMIX_HYPOTF_COMPAT
% x = fabs(x + 0);
% t1 = (x - x) / (x - x); /* Raise invalid flag if x is
% * not NaN */
% t1 = t1 + fabs(y + 0); /* Mix NaN(s). */
% #elif NANMIX_APPLE_CLOG_COMPAT
% /* No actual mixing. */
% return (cpack(y, y));
% #else
% t1 = (x - x) / (x - x); /* Raise invalid flag if x is
% * not NaN */
% #endif
% return (cpack(t1, t1));
% }
% } else if (isfinite(x) && isfinite(y) &&
% (fabs(x) > 1e308 || fabs(y) > 1e308))
% /*
% * To avoid unnecessary overflow, if x or y are very large,
% * divide x and y by M_E, and then add 1 to the logarithm.
% * This depends on M_E being larger than sqrt(2).
% */
% return (cpack(log(hypot(x / M_E, y / M_E)) + 1, atan2(y, x)));
Important fix here. 1 was added to the log() arg instead of to log().
% else if (fabs(x) < 1e-50 && fabs(y) != 1 ||
% fabs(y) < 1e-50 && fabs(x) != 1 ||
% fabs(x) > 1e50 || fabs(y) > 1e50)
The special case for |x| == 1 and |y| == 1 was defeated by returning for
it here.
% /*
% * Because atan2 and hypot conform to C99, this also covers
% * all the edge cases when x or y are 0 or infinite.
% */
% return (cpack(log(hypot(x, y)), atan2(y, x)));
% else {
% /* We don't need to worry about overflow in x*x+y*y. */
% h = x * x + y * y;
% if (h < 0.1 || h > 10)
% return (cpack(log(h) / 2, atan2(y, x)));
% /* Take extra care if h is moderately close to 1 */
% else {
% #if 1
% if (fabs(x) == 1)
% return (cpack(log1p(y * y) / 2,
% atan2(y, x)));
% if (fabs(y) == 1)
% return (cpack(log1p(x * x) / 2,
% atan2(y, x)));
% #endif
Special case. It seems too special, but Apple clog() doesn't do any more,
and this with the other special cases is enough to beat Apple clog().
% /*
% * x0 and y0 are good approximations to x and y, but
% * have their bits trimmed so that double precision
% * floating point is capable of calculating x0*x0 +
% * y0*y0 - 1 exactly.
% */
The only way for x*x + y*y to be _very_ near 1 in infinite precision
is for |x| or y| to be 1 (I think). Other cases are bounded away from
1, and if you are lucky the bound is fairly far from 1 so that sloppier
approximations work OK. Mathematicians should determine the bound
exactly using continued fractions or something like they do for
approximations to N*Pi/2. This becomes especially interesting in high
precisions where you can't hope to get near the worst case by random
testing.
% x0 = ldexp(floor(ldexp(x, 24)), -24);
% x1 = x - x0;
% y0 = ldexp(floor(ldexp(y, 24)), -24);
% y1 = y - y0;
This has a chance of working iff the bound away from 1 is something like
2**-24. Otherwise, multiplying by 2**24 and flooring a positive value
will just produce 0. 2**-24 seems much too small a bound. My test
coverage is not wide enough to hit many bad cases.
% /* Notice that mathematically, h = t1*(1+t3). */
% t1 = x0 * x0 + y0 * y0;
% t2 = 2 * x0 * x1 + x1 * x1 + 2 * y0 * y1 + y1 * y1;
% t3 = t2 / t1;
% return (cpack((log1p(t1 - 1) + log1p(t3)) / 2,
% atan2(y, x)));
% }
% }
% }
Bruce
More information about the freebsd-numerics
mailing list