0

I am trying to perform a Tuckerman Rounding Test in order to determine the correctly rounded to nearest result.

I created a program in C++ to compare two solutions to a square root of a number and perform a tuckerman test on them. However, the C++ math library solution fails to pass the tuckerman test, so I'm wondering what could be wrong?

Here is my output:

Square root program started
Input value is 62a83003

===Tuckerman Test with MATLAB result===
Square root result from MATLAB = 5112b968
g*(g-ulp) = 62a83001
b = 62a83003
g*(g+ulp) = 62a83003
=====>Passes Tuckerman test


===Tuckerman Test with correct result===
Correct square root result = 5112b969
g*(g-ulp) = 62a83003
b = 62a83003
g*(g+ulp) = 62a83005
=====>Fails Tuckerman test

Here is my code (C++):

#include <iostream>
#include <cmath>
#include <fstream>

using namespace std;

union newfloat{
    float f;
    unsigned int i;
};

int main ()
{
// Declare new floating point numbers
newfloat input;
newfloat result, resultm1, resultp1;
newfloat correct_result, correct_resultm1, correct_resultp1;
newfloat resultm1_times_result, resultp1_times_result;
newfloat correct_resultm1_times_result, correct_resultp1_times_result;

// Print message at start of program
cout << "Square root program started" << endl;
input.i = 0x62A83003; // Input we are trying to find the square root of
cout << "Input value is " << hex << input.i << "\n" <<  endl; // Print input value

result.i = 0x5112B968; // Result from MATLAB
resultm1.i = result.i - 1; // value minus 1 ulp
resultp1.i = result.i + 1; // value plus 1 ulp

correct_result.f = sqrt(input.f);          // Compute correct square root
correct_resultm1.i = correct_result.i - 1; // correct value minus 1 ulp
correct_resultp1.i = correct_result.i + 1; // correct value plus 1 ulp

resultm1_times_result.f = result.f * resultm1.f; // Compute g(g-ulp) for matlab result
resultp1_times_result.f = result.f * resultp1.f; // Compute g(g+ulp) for matlab result

correct_resultm1_times_result.f = correct_result.f * correct_resultm1.f; // Compute g*(g-ulp) for correct result
correct_resultp1_times_result.f = correct_result.f * correct_resultp1.f; // Compute g*(g+ulp) for correct result


// Print output from MATLAB algorithm and perform tuckerman test
cout << "===Tuckerman Test with MATLAB result===" << endl;
cout << "Square root result from MATLAB = " << result.i << endl;
cout << "g*(g-ulp) = " << hex << resultm1_times_result.i << endl;
cout << "b = " << hex << input.i << endl;
cout << "g*(g+ulp) = " << hex << resultp1_times_result.i << endl;
if ((resultm1_times_result.f < input.f) && (input.f <= resultp1_times_result.f))
  cout << "=====>Passes Tuckerman test" << endl;
else
  cout << "=====>Fails Tuckerman test" << endl;


cout << "\n" << endl;

// Print output from C++ sqrt math library and perform tuckerman test
cout << "===Tuckerman Test with correct result===" << endl;
cout << "Correct square root result = " << hex << correct_result.i << endl;
cout << "g*(g-ulp) = " << hex << correct_resultm1_times_result.i << endl;
cout << "b = " << hex << input.i << endl;
cout << "g*(g+ulp) = " << hex << correct_resultp1_times_result.i << endl;
if ((correct_resultm1_times_result.f < input.f) && (input.f <= correct_resultp1_times_result.f))
  cout << "=====>Passes Tuckerman test" << endl;
else
  cout << "=====>Fails Tuckerman test" << endl;

return 0;
}
Veridian
  • 3,531
  • 12
  • 46
  • 80
  • I don't have experience using the Tuckerman test, but as I understand, it is to be applied mathematically, not to be evaluated in the target floating-point arithmetic precision. Consider: `g=0x1.2572d20000000p+35 (5112b969); g*(g-ulp)=0x1.506005e8cea00p+70 < arg=0x1.5060060000000p+70 <= g*(g+ulp)=0x1.50600a7e99e80p+70 T;;;; g=0x1.2572d00000000p+35 (5112b968); g*(g-ulp)=0x1.5060015303600p+70 < arg=0x1.5060060000000p+70 <= g*(g+ulp)=0x1.506005e8cea00p+70 F`. – njuffa Feb 05 '15 at 01:17
  • All that seems needed to apply the test correctly is to compute the two products `g*(g-ulp)` and `g*(g+ulp)` to twice the width of the tentative result `g`. In a hardware multiplier, the full double-wide product should be available prior to final rounding, so the additional hardware needed (a double-width comparator) will be at a minimum if its not already there for other reasons. – njuffa Feb 05 '15 at 01:30
  • @njuffa, Assuming I don't have the hardware to perform the computation in a wider precision (which is the case with what I'm working on), is it possible to perform tuckerman rounding? – Veridian Feb 05 '15 at 02:33
  • I don't know. It seems to me that if the preliminary result is within 1 ulp of the final result, the leading bits in the comparisons should cancel and you could get away with comparing only the low-order bits of the two products with the argument. That way you would not need double-wide intermediate results. Do you have a way of generating these low-order bits? Some context to your question might be useful: What do you have available in terms of hardware and software? – njuffa Feb 05 '15 at 02:41
  • Here is a different angle you could try. Tuckerman rounding was introduced in R. Agarwal et al., "New scalar and vector elementary functions for the IBM System/370", IBM J. Res. Develop., Vol. 30, No. 2, March 1986, p. 126. The paper specifically mentions that the products g*(g+ulp) and g*(g-ulp) are computed with the *truncating* (not rounding!) multiplier of the System/370, without requiring any extra precision. – njuffa Feb 05 '15 at 03:03
  • 1
    Using the truncating multiplication fixes the specific case mentioned in the question: `g=5112b969; mul_rz(g,g-ulp)=62a83002 < arg=62a83003 <= mul_rz(g,g+ulp)=62a83005 T;;;; g=5112b968; mul_rz(g,g-ulp)=62a83000 < arg=62a83003 <= mul_rz(g,g+ulp)=62a83002 F`. So this looks like an approach worth looking into. – njuffa Feb 05 '15 at 03:30

1 Answers1

2

The original publication that introduced Tuckerman rounding for the square root was:

Ramesh C. Agarwal, James W. Cooley, Fred G. Gustavson, James B. Shearer, Gordon Slishman, Bryant Tuckerman, "New scalar and vector elementary functions for the IBM System/370", IBM J. Res. Develop., Vol. 30, No. 2, March 1986, pp. 126-144.

This paper specifically points out that the multiplications used to compute the products g*(g-ulp) and g*(g+ulp) are truncating, not rounding multiplications:

"However, these inequalities can be shown to be equivalent to

y- * y < x <= y * y+ ,

where * denotes System/360/370 multiplication (which truncates the result), so that the tests are easily carried out without the need for extra precision. (Note the asymmetry: one <, one <=.) If the left inequality fails, y is too large; if the right inequality fails, y is too small."

The following C99 code shows how Tuckerman rounding is successfully utilized to deliver correctly rounded results in a single-precision square root function.

#include <stdio.h>
#include <stdlib.h>
#include <fenv.h>
#include <math.h>

#pragma STDC FENV_ACCESS ON
float mul_fp32_rz (float a, float b)
{
    float r;
    int orig_rnd = fegetround();
    fesetround (FE_TOWARDZERO);
    r = a * b;
    fesetround (orig_rnd);
    return r;
}

float my_sqrtf (float a)
{
    float b, r, v, w, p, s;
    int e, t, f;

     if ((a <= 0.0f) || isinff (a) || isnanf (a)) {
         if (a < 0.0f) {
             r = 0.0f / 0.0f;
         } else {
             r = a + a;
         }
     } else {
         /* compute exponent adjustments */
         b = frexpf (a, &e);
         t = e - 2*512;
         f = t / 2;
         t = t - 2 * f;
         f = f + 512;
         /* map argument into the primary approximation interval [0.25,1) */
         b = ldexpf (b, t);
         /* initial approximation to reciprocal square root */
         r =        -6.10005470e+0f;
         r = r * b + 2.28990124e+1f;
         r = r * b - 3.48110069e+1f;
         r = r * b + 2.76135244e+1f;
         r = r * b - 1.24472151e+1f;
         r = r * b + 3.84509158e+0f;
         /* round rsqrt approximation to 11 bits */
         r = rintf (r * 2048.0f); 
         r = r * (1.0f / 2048.0f);
         /* Use A. Schoenhage's coupled iteration for the square root */
         v = 0.5f * r;
         w = b * r;             
         w = (w * -w + b) * v + w;
         v = (r * -w + 1.0f) * v + v;
         w = (w * -w + b) * v + w;
         /* Tuckerman rounding: mul_rz (w, w-ulp) < b <= mul_rz (w, w+ulp) */
         p = nextafterf (w, 0.0f);
         s = nextafterf (w, 2.0f);
         if (b <= mul_fp32_rz (w, p)) {  
             w = p;
         } else if (b > mul_fp32_rz (w, s)) {
             w = s;
         }
         /* map back from primary approximation interval by jamming exponent */
         r = ldexpf (w, f);
     }
     return r;
 }

 int main (void)
 {
     volatile union {
         float f;
         unsigned int i;
     } arg, res, ref;
     arg.i = 0;
     do {
         res.f = my_sqrtf (arg.f);
         ref.f = sqrtf (arg.f);
         if (res.i != ref.i) {
              printf ("!!!! error @ arg=%08x: res=%08x ref=%08x\n",
                      arg.i, res.i, ref.i);
              break;
         }
         arg.i++;
    } while (arg.i);
    return EXIT_SUCCESS;
}
njuffa
  • 23,970
  • 4
  • 78
  • 130