[PLUG] integer-decode-float equivalent in C (gcc bug at -O2 and higher?)

Russell Senior seniorr at aracnet.com
Fri Feb 22 09:36:16 UTC 2008


In Common Lisp and Scheme you often find a reliable compatibility
between print and read for floating point values.  That is, for any
float you can print it to a string and then parse that string back
into the same float from an equivalence point of view.

   Printing Floating-Point Numbers Quickly and Accurately
   http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf

   How to Read Floating Point Numbers Accurately
   ftp://ftp.ccs.neu.edu/pub/people/will/howtoread.ps

In Common Lisp there is a function that "decodes" a float into the
various integer parts (sign, mantissa and exponent) in such a way that
you can reconstruct the float from the integer components, thusly:

   [1]> (integer-decode-float 1.0)
   4503599627370496 ;
   -52 ;
   1

   [2]> (multiple-value-bind (m e s) 
          (integer-decode-float 1.0) 
            (* s m (expt 2.0 e)))
   1.0

Note, the second command is binding the multiple values to (m)antissa,
(e)xponent and (s)ign and then recomposing them by multiplication:
sign * mantissa * 2^exponent, and returning the same value you fed to
integer-decode-float (1.0 in this case, but it will work for any
floating point number).

I have an application in which a C program wants to pass floating
point values (doubles, as it happens) accurately over a network socket
to a lisp program without a lot of messy float printing and parsing.
Integers are so much nicer.

So, I tried to write an equivalent to integer-decode-float in C.  I
did.  It worked.  For a while.  Maybe my C is rusty.  Here it is in
testable isolation:

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

   struct decoded_float {
     unsigned sign;
     signed exponent;
     unsigned long long mantissa;
   };

   struct decoded_float*
   decode_float(double value) 
   {
     static static decoded_float d;

     unsigned long long *p,v;

     p = (unsigned long long *) &value;
     v = *p;

     d.sign = v >> 63;
     d.exponent = v << 1 >> 53;
     d.mantissa = v << 12 >> 12;

     printf("s e m = %u %d %llu\n",d.sign,d.exponent,d.mantissa);

     if(d.exponent)
       d.mantissa |= (unsigned long long) 1 << 52;

     printf("s e m = %u %d %llu\n",d.sign,d.exponent,d.mantissa);

     d.exponent -= 1023 + 52;

     printf("s e m = %u %d %llu\n",d.sign,d.exponent,d.mantissa);

     return &d;
   }

   int 
   main(int argc, char** argv) 
   {
     double v = atof(argv[1]);
     struct decoded_float* f = decode_float(v);  

     printf("s e m = %u %d %llu\n",f->sign,f->exponent,f->mantissa);

     return 0;
   }        

[Note: s here is 0 or 1 rather than -1 or +1 in the lisp equivalent
... that's okay, I can deal with that.]

Again, this seemed to be working fine.  Then, after about 4 years I
wanted to run this stuff again and I noticed some errors, particularly
when I ramped up the optimization.  

   $ for i in $(seq 0 6) ; do echo Using -O$i ; gcc -O$i decoded-float.c -o decoded-float ; ./decoded-float 1.0 ; done
   Using -O0
   s e m = 0 1023 0
   s e m = 0 1023 4503599627370496
   s e m = 0 -52 4503599627370496
   s e m = 0 -52 4503599627370496
   Using -O1
   s e m = 0 1023 0
   s e m = 0 1023 4503599627370496
   s e m = 0 -52 4503599627370496
   s e m = 0 -52 4503599627370496
   Using -O2
   s e m = 1 894 1618429576478720
   s e m = 1 894 6122029203849216
   s e m = 1 -181 6122029203849216
   s e m = 1 -181 6122029203849216
   Using -O3
   s e m = 0 128 1294403149217721
   s e m = 0 128 5798002776588217
   s e m = 0 -947 5798002776588217
   s e m = 0 -947 5798002776588217
   Using -O4
   s e m = 0 128 1294403149725625
   s e m = 0 128 5798002777096121
   s e m = 0 -947 5798002777096121
   s e m = 0 -947 5798002777096121
   Using -O5
   s e m = 0 128 1294403149512633
   s e m = 0 128 5798002776883129
   s e m = 0 -947 5798002776883129
   s e m = 0 -947 5798002776883129
   Using -O6
   s e m = 0 128 1294403149254585
   s e m = 0 128 5798002776625081
   s e m = 0 -947 5798002776625081
   s e m = 0 -947 5798002776625081

Note, with -O0 and -O1, I get the same decodes as the Lisp
integer-decode-float.  In -O2 it goes wrong and it goes wrong
differently in -O3 and above.

   $ gcc --version
   gcc (GCC) 4.2.3 (Debian 4.2.3-1)

Any clues what I am doing wrong?


-- 
Russell Senior         ``I have nine fingers; you have ten.''
seniorr at aracnet.com



More information about the PLUG mailing list