Hello,

last weekend I got some strange results from a C-program doing some
comparisons on double-values. I know, usually it's no good idea to
compare two doubles with "==". But when they are calculated from the
same values with the same operations, both doubles should have the
same value, shouldn't they?

I tracked this down to the attached sourcecode (fault1.c). I don't
know if this shows a compiler bug or if it's just my fault. I think
the interesting part is:

| void foobar(int *num, int *den); /* modifies at least one value */
|
| main(){
|   int x, y, i, j, m, n;
|   double d, e, f;
| 
| /* ... give x and y some values ... */
| 
|   d=(double)(x)/(double)(y);
|       
|   i=x; 
|   j=y; 
|   foobar(&i,&j);
|   e=(d - (double)(i)/(double)(j));
|
|   m=x;
|   n=y;
|   foobar(&m,&n);
|   f=(d - (double)(m)/(double)(n));
|
|   if (f != e) printf("NEVER REACHED?\n");

The integers "i" and "m" are equal, so are "j" and "n". Double "d"
doesn't change, so "e" and "f" should be equal as well. Compiled with
"-O0" printf isn't called (as expected). But with "-O1" or "-O2"
printf is called! What's happening? I found this behaviour on any
32-bit x86 I tested and with any installed version of gcc. 

My desktop PC (Celeron Northwood "P4-alike"):
  2.95.4
  3.0.4
  gcc-3.2 (GCC) 3.2.3 (Debian)
  gcc-3.3 (GCC) 3.3.5 (Debian 1:3.3.5-13)
  gcc-3.4 (GCC) 3.4.4 20050314 (prerelease) (Debian 3.4.3-13)
on an Athlon-XP with:
  2.95.4
  gcc-3.2 (GCC) 3.2.3 (Debian)
  gcc-3.3 (GCC) 3.3.5 (Debian 1:3.3.5-12)
  gcc-3.4 (GCC) 3.4.4 20050314 (prerelease) (Debian 3.4.3-12)
an Athlon with:
  gcc (GCC) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
an old Pentium-1 with Debian-Woody: gcc-2.95.4

Systems where this "bug" doesn't seem to happen:
- Dual Opteron with:          "gcc (GCC) 3.2.2 (SuSE Linux)"
- Powerstack II Pro4000 with: "gcc.real (GCC) 3.3.2 (Debian)"


On my desktop-PC the "bug" disappears when I use "-O3", "-O1
-finline-functions" or "-O1 -march=pentium4 -mfpmath=sse" (printf
isn't called then, didn't test on other systems). "fault2.c" does the
same calculation like "fault1.c" does, but has some macros added to
play around with.


Bye,

Christian Bodenstedt
#include <stdio.h>

void foobar(int *num, int *den)
{
    int n1 = (*num);
    int d1 = (*den);
    /* just change (at least) one value */
    (*num)=(n1+1);
    (*den)=d1;
}


int main(int argc, char **argv){
  int x, y, i, j, m, n;
  double d, e, f;
  int min, max;
  
  min = 1;
  max = 10;

  for (x=min; x <= max; x++){
    for (y=min; y <= max; y++){
      d=(double)(x)/(double)(y);
      
      i=x; 
      j=y; 
      foobar(&i,&j);
      e=(d - (double)(i)/(double)(j));
      
      m=x; 
      n=y;
      foobar(&m,&n);
      f=(d - (double)(m)/(double)(n));

      if ( e != f ){
        /*(IMHO) this should NEVER be reached, since "e" and "f" are
          calculated from the same values and thus should be equal. So
          this program shouldn't generate any output, but compiled
          with "-O1" or "-O2" it does!
        */
        printf("NEVER REACHED? x=%d, y=%d, ", x, y);
        if (i==m) printf("i=m=%d, ", i);
        else printf("really never reached. %d=i != m=%d, ", i, m);
        if (j==n) printf("j=n=%d.\n", j);
        else printf("really never reached. %d=j != n=%d.\n", j, n);
        return 1;
      }
    }
  }
  return 0;
}
#include <stdio.h>

/** "FUNNY" changes the comparison from  (e != f) to (e - f != 0). If it's   */
/**   defined, "e-f" will be printed as non-zero value, otherwise zero.      */
/* #define FUNNY */

/** If "VERBOSE" is not defined, "main()" will print the combinations of x   */
/**   and y each time (e!=f). If it is defined, the programs stops after the */
/**   first time (e!=f) but prints more details.                             */
/* #define VERBOSE */

/** "HEXADECIMAL": print out doubles in hex-format. (How can this be done    */
/**   without producing a compiler warning?)                                 */
/* #define HEXADECIMAL */

/** Moving the line marked with (#1) after the second call to "foobar()"     */
/*    [marked (#2)], the "bug" will disappear                                */
/* #define SWITCH_OFF_BUG */

#ifdef FUNNY
#ifndef VERBOSE
#define VERBOSE
#endif
#endif

void foobar(int *num, int *den)
{
    int n1 = (*num);
    int d1 = (*den);
    /* just change (at least) one value */
    (*num)=(n1+1);
    (*den)=d1;
}


int main(int argc, char **argv){
  int x, y, i, j, m, n;
  double d, e, f;
  int min, max;
  int bug=0;
  
  min = 1;
  max = 10;

  for (x=min; x <= max; x++){
    for (y=min; y <= max; y++){
      d=(double)(x)/(double)(y);
      
      i=x; 
      j=y; 
      foobar(&i,&j);
#ifndef SWITCH_OFF_BUG
      e=(d - (double)(i)/(double)(j)); /*(#1)*/
#endif      
      m=x; 
      n=y;
      foobar(&m,&n);
#ifdef SWITCH_OFF_BUG
      e=(d - (double)(i)/(double)(j)); /*(#2)*/
#endif      
      f=(d - (double)(m)/(double)(n));

      /*
        if ( e != f) // shouldn't happen IMHO. If it does, produce some output.
      */

      if (
#ifndef FUNNY      
          e != f
#else
          e - f != 0
#endif
          )
      {
        /*(IMHO) this should NEVER be reached, since "e" and "f" are
          calculated from the same values and thus should be equal. So
          this program shouldn't generate any output, but compiled
          with "-O1" or "-O2" it does!
        */
#ifdef VERBOSE
        printf("\n                      (e!=f)? How could this happen?\n\n");
        printf("d = (double)(x)/(double)(y)       = %d/%d\n  =%66.60f\n",
               x, y, d);
        printf("e = (d - (double)(i)/(double)(j)) =  d  - %d/%d\n  =%66.60f\n",
               i, j, e);
        printf("f = (d - (double)(m)/(double)(n)) =  d  - %d/%d\n  =%66.60f\n",
               m, n, f);
#ifdef HEXADECIMAL
        printf("e = 0x%16llx\nf = 0x%16llx\n", e, f);
#endif
        printf("e-f = %63.60f\n    = %g\n", e - f, e - f);
        return 1;
#else
        printf("%3d/%-3d ", x, y);
        bug = 2;
#endif
      }
    }
#ifndef VERBOSE
    if (bug == 2){
      printf("\n");
      bug = 1;
    }
#endif
  }
  return bug;
}

Reply via email to