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