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); }