I'm trying to write a test to check I get all the subnormal
corner cases for ieee-754 double precision floating point division right.
The usual thing to do with a new test is to try it on a known good
platform. Unfortunately, the x86 processor of my workstation is actually
known to get these calculations wrong. Could someone with a fully
ieee compliant FPU try this test?
I've currently made it output lots of debug information and to continue
in the faceof errors; to get only a single result via the exit code,
just disable the DEBUG define.
It appears my machine gets about one in 3.7 of the fraction approximation
tests wrong :-(
FWIW, the random stuff is cribbed from arith-rand-ll.c.
/* Test proper rounding for division when generating subnormal numbers. */
/* The debug code is only meant to run on linux with long double support. */
#define DEBUG
long long
simple_rand ()
{
static unsigned long long seed = 47114711;
unsigned long long this = seed * 1103515245 + 12345;
seed = this;
return this >> 8;
}
unsigned long long int
random_bitstring (int *lenp)
{
unsigned long long int x;
int n_bits;
int tot_bits = 0;
int len = 0;
int limit = *lenp;
x = 0;
for (;;)
{
long long ran = simple_rand ();
int ones_p = ran & 1;
n_bits = (ran >> 1) % 16;
if (x || ones_p)
{
if (n_bits > limit - len)
n_bits = limit - len;
*lenp = len += n_bits;
}
tot_bits += n_bits;
if (n_bits == 0 && len)
return x;
else if (n_bits)
{
x <<= n_bits;
if (ones_p)
x |= (1 << n_bits) - 1;
if (len == limit
|| (len && tot_bits > 8 * sizeof (long long) + 6))
return x;
}
}
}
main ()
{
int i;
double e0, e1, ie1, i2e1;
/* Hack to get x86 math to work. */
volatile double eval_tmp;
#define EVAL(x) (eval_tmp = (x))
for (i = 0, e1 = 0.5; EVAL (1. + e1) > 1.; e1 *= 0.5, i++);
e1 *= 2.;
ie1 = 1./e1;
i2e1 = 2./e1;
if (i < 52 || (sizeof (double) == 8 && i > 52))
abort ();
for (i = 1, e0 = 0.5; EVAL (e0 * e0); e0 *= e0, i <<= 1);
for (;EVAL (e0 * 0.5); e0 *= 0.5, i++);
if (i < 0x3ff + 51 || (sizeof (double) == 8 && i > 0x3ff + 51))
abort ();
/* First, check that a quotient that can be computed exactly is properly
rounded, and try variantions on the fraction to do some simple
round-to-nearest checks for inexact results. */
for (i = 0; i < 10000; i++)
{
unsigned long long x, y;
int xlen, ylen;
long long ran;
double xd, xr, yd, yd2, e2y, pd, ep;
xlen = 53;
x = random_bitstring (&xlen);
ylen = 54-xlen;
y = random_bitstring (&ylen);
y |= 1;
if (x > 2 &&
(double) (x|3) * y >= (double) (1LL << 53))
{
x >>= 1;
xlen--;
}
x |= 1;
ran = simple_rand ();
x ^= ran & 2;
xd = (double)x * e0;
yd = (double)y * ie1;
yd2 = yd * 2.;
e2y = (double) (1LL << ylen);
if (EVAL (yd2 + e2y) == yd2)
abort ();
if ((yd2 + e2y) / yd2 > (1.+e1)/1.)
abort ();
pd = xd * yd;
ep = e0 * (1LL << xlen-1) * (1LL << ylen-1);
if (EVAL (pd + ep) == pd)
ep += ep;
if (pd + ep == pd)
abort ();
if (EVAL((pd + ep) / pd) > EVAL (1 + e1))
abort ();
if (EVAL (pd / yd) != x * e0)
abort ();
/* Round to even. */
xr = ((x & 2) + x) >> 1;
if (EVAL (pd / yd2) != xr * e0)
abort ();
/* Round to nearest - upwards. */
xr = x+1 >> 1;
if (EVAL (pd / (yd2-e2y)) < xr * e0)
abort ();
if (EVAL ((pd + ep) / yd2) < xr * e0)
abort ();
/* Round to nearest - downwards. */
xr = x >> 1;
if (EVAL (pd / (yd2+e2y)) > xr * e0)
abort ();
if (EVAL ((pd - ep) / yd2) > xr * e0)
abort ();
}
/* Now generate a set of 53 bit random numbers, calculate a fractional
approximation which is likely to be hard to distinguish from the
exact result, and check for proper rounding. */
for (i = 0; i < 10000; i++)
{
unsigned long long x, y, x0, y0, x1, y1, x2, y2, x3, tmp;
int rest_sign;
long long ran;
int xlen;
long long a[10];
int j, k;
do
{
xlen = 53;
x = random_bitstring (&xlen);
}
while (xlen < 10);
x |= 1;
x0 = x;
/* Look for a close, but inexact approximation that fits in 53 bits
numerator / denominator. */
y = 1LL << xlen - 1;
y0 = y;
rest_sign = 0;
for (j = 0; j < 20; j++)
{
a[j] = x/y;
x1 = a[j], y1 = 1;
for (k = j - 1; k >= 0; k--)
{
tmp = a[k] * x1 + y1;
if (tmp >= 1LL << 53 || tmp/a[k] < x1)
goto end_approx;
y1 = x1;
x1 = tmp;
}
tmp = x - a[j] * y;
if (!tmp)
break;
rest_sign = j & 1 ? -1 : 1;
x2 = x1, y2 = y1;
x = y;
y = tmp;
}
end_approx:
if (!rest_sign)
continue;
#ifdef DEBUG
printf ("%d %d %f %f %e\n", j, rest_sign,
(double)x0/y0, (double)x2/y2,
(double)((long double)x0/y0-((long double)x2/y2)));
#endif
x3 = EVAL (x2*e0*y0/(2.*y2)) / e0 * 2.;
#ifdef DEBUG
printf ("%llx %llx\n", x0, x3);
#endif
if (rest_sign > 0 ? x3 >= x0 : x3 <= x0)
#ifdef DEBUG
printf("ERROR!\n");
#else
abort ();
#endif
}
exit (0);
}