Re: different rounding behavior with float and double
On 9/1/14 5:46 pm, Raymond Li wrote:
I have encountered a problem related to floating point rounding. I
googled a lot and there are many clear and helpful information. e.g.
http://www.learncpp.com/cpp-tutorial/25-floating-point-numbers/
http://support.microsoft.com/kb/125056/en-hk
Although the urls have explained the cause, I need to find a practical
way to solve a rounding problem. My program has calculated a weighted
accumulation as 3.5. When the figure is rounded to nearest number, it
became 3 (but I want it to round up to 4). I understood it would be due
to approximation value of 3.5 as 3.49999...
I found a simple fix by using float instead of double. I list the
program below and wish someone could explain why using double would
incur the rounding problem while float would not. In the code below,
fun1() use float and the calculation is 'correct'. In fun2(), it uses
double and the figure 3.5 is rounded as 3.
Raymond
//######################
#include <cmath>
#include <iostream>
//using namespace std;
using std::cout;
using std::endl;
int fun1();
int fun2();
int main(int argc, char ** argv)
{
fun1();
fun2();
return 0;
}
int fun1()
{
float weighted=10.0;
float average=100.0;
float z[]=
{
4.0,
4.0,
4.0,
4.0,
4.0,
3.0,
3.0,
3.0,
2.0,
4.0
};
float total=0.0;
int i=0;
for (i=0;i<10;i++)
{
float item=z[i]*weighted/average;
total=total+item;
cout << i << " accumulate is " << total << endl;
// NSLog(@"z[%d] is %f, total is %f", i, z[i], total);
}
float answer=round(total);
// NSLog(@"rounded is %f", answer);
cout << "rounded is " << answer << endl;
return 0;
}
int fun2()
{
double weighted=10.0;
double average=100.0;
double z[]=
{
4.0,
4.0,
4.0,
4.0,
4.0,
3.0,
3.0,
3.0,
2.0,
4.0
};
double total=0.0;
int i=0;
for (i=0;i<10;i++)
{
double item=z[i]*weighted/average;
total=total+item;
cout << i << " accumulate is " << total << endl;
// NSLog(@"z[%d] is %f, total is %f", i, z[i], total);
}
double answer=round(total);
// NSLog(@"rounded is %f", answer);
cout << "rounded is " << answer << endl;
return 0;
}
0 accumulate is 0.4
1 accumulate is 0.8
2 accumulate is 1.2
3 accumulate is 1.6
4 accumulate is 2
5 accumulate is 2.3
6 accumulate is 2.6
7 accumulate is 2.9
8 accumulate is 3.1
9 accumulate is 3.5
rounded is 4
***(above is the version using float, 3.5 is rounded as 4) ***
0 accumulate is 0.4
1 accumulate is 0.8
2 accumulate is 1.2
3 accumulate is 1.6
4 accumulate is 2
5 accumulate is 2.3
6 accumulate is 2.6
7 accumulate is 2.9
8 accumulate is 3.1
9 accumulate is 3.5
rounded is 3
***(this version use double, 3.5 is rounded as 3) ***
--- news://freenews.netfront.net/ - complaints: news@netfront.net ---
Thanks for your replies. I hope to stick to double too. But the users
have implemented the logic in legacy system and I need to convince them
if I do something different from them. They claimed that the interim
calculations (z[i]*weighted/average) are used too and they would feel
uncomfortable if I make any adjustment. The worst problem I faced is
that they claimed that the legacy system (which is not really legacy, it
is running Oracle pl/sql) does not have the rounding error.
So I investigated and found it weird that the rounding problem could be
avoided by using float. I am uncomfortable to this workaround (using
float), as I am afraid there would be cases that the rounding issue
recur in other scenarios. So I really want someone could explain why the
float datatype would round correctly in the above case, while using
double rounded 'incorrectly'.
If I am free to rewrite the code, after learning from you, I would
rewrite the code as follow. The problem is that I have to convince my
users, and their legacy system was already implemented.
Regards,
Raymond
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <iomanip>
using std::setprecision;
using std::cout;
using std::endl;
int fun3();
int main(int argc, const char * argv[])
{
// insert code here...
// std::cout << "Hello, World!\n";
fun3();
return 0;
}
int fun3()
{
cout << setprecision(17);
double weighted=10.0;
double average=100.0;
double z[]=
{
4.0,
4.0,
4.0,
4.0,
4.0,
3.0,
3.0,
3.0,
2.0,
4.0
};
double total=0.0;
int i=0;
for (i=0;i<10;i++)
{
//double item=z[i]*weighted/average;
double item=z[i]*weighted; // defer the division
total=total+item;
cout << "in loop " << i << ", accumulate is " << total << endl;
// NSLog(@"z[%d] is %f, total is %f", i, z[i], total);
}
total=total/average; // division done at last to avoid truncation error
double answer=round(total);
cout << "rounded is " << answer << " and original is " << total <<
endl;
return 0;
}
output:
in loop 0, accumulate is 40
in loop 1, accumulate is 80
in loop 2, accumulate is 120
in loop 3, accumulate is 160
in loop 4, accumulate is 200
in loop 5, accumulate is 230
in loop 6, accumulate is 260
in loop 7, accumulate is 290
in loop 8, accumulate is 310
in loop 9, accumulate is 350
rounded is 4 and original is 3.5
--- news://freenews.netfront.net/ - complaints: news@netfront.net ---