This is the link to the question on UVa online judge.
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=29&page=show_problem&problem=1078
My C code is
#include <stdio.h>
double avg(double * arr,int students)
{
int i;
double average=0;
for(i=0;i<students;i++){
average=average+(*(arr+i));
}
average=average/students;
int temp=average*100;
average=temp/100.0;
return average;
}
double mon(double * arr,int students,double average)
{
int i;
double count=0;
for(i=0;i<students;i++){
if(*(arr+i)<average){
double temp=average-*(arr+i);
int a=temp*100;
temp=a/100.0;
count=count+temp;
}
}
return count;
}
int main(void)
{
// your code goes here
int students;
scanf("%d",&students);
while(students!=0){
double arr[students];
int i;
for(i=0;i<students;i++){
scanf("%lf",&arr[i]);
}
double average=avg(arr,students);
//printf("%lf\n",average);
double money=mon(arr,students,average);
printf("$%.2lf\n",money);
scanf("%d",&students);
}
return 0;
}
One of the input and outputs are
Input
3
0.01
0.03
0.03
0
Output
$0.01
My output is
$0.00.
However if I uncomment the line printf("%lf",average);
The Output is as follows
0.02 //This is the average
$0.01
I am running the code on ideone.com
Please explain why is this happening.
I believe I've found the culprit and a reasonable explanation.
On x86 processors, the FPU operates internally with extended precision, which is an 80-bit format. All of the floating point instructions operate with this precision. If a double
is actually required, the compiler will generate code to convert the extended precision value down to a double precision value. Crucially, the commented-out printf
forces such a conversion because the FPU registers must be saved and restored across that function call, and they will be saved as double
s (note that avg
and mon
are both inlined so no save/restore happens there).
In fact, instead of printf
we can use the line static double dummy = average;
to force the double
conversion to occur, which also causes the bug to disappear: http://ideone.com/a1wadn
Your value of average
is close to but not exactly 0.02
because of floating-point inaccuracies. When I do all the calculations explicitly with long double
, and I print out the value of average
, it is the following:
long double: 0.01999999999999999999959342418532
double: 0.02000000000000000041633363423443
Now you can see the problem. When you add the printf
, average
is forced to a double
which pushes it above 0.02. But, without the printf
, average
will be a little less than 0.02 in its native extended precision format.
When you do int a=temp*100;
the bug appears. Without the conversion, this makes a = 1
. With the conversion, this makes a = 2
.
To fix this, simply use int a=round(temp*100);
- all your weird errors should vanish.
Of note, this bug is extremely sensitive to changes in the code. Anything that causes the registers to be saved (such as a printf
pretty much anywhere) will actually cause the bug to vanish. Hence, this is an extremely good example of a heisenbug: a bug that vanishes when you try to investigate it.
@nneonneo well answered most of the issue: Slightly variant compilations result in slightly different floating-point code that result in nearly the same double
answer, except one answer is just below 2.0 and the the other at or just about 2.0.
Like to add about the importance on not using conversion to int
for floating point rounding.
Code like double temp; ... int a=temp*100;
accentuate this difference resulting in a
with a value of 1 or 2 as conversion to int
is effective "truncate toward zero" - drop the fraction.
Rather than round to near 0.01 with code like:
double temp;
...
int a = temp*100; // Problem is here
temp = a/100.0;
Do not use int
at all. Use
double temp;
...
temp = round(temp*100.0)/100.0;
Not only does this provide more consistent answers (as temp
is unlikely to have values near a half-cent), it also allows temp
values outside the int
range. temp = 1e13; int a = temp/100;
certainly results in undefined behavior.
Do not use conversion to
int
to round floating-point numbers: useround()
roundf()
, roundl()
, floor()
, ceil()
, etc. may also be useful. @jeff
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With