On Fri, 10 Jan 2014 11:51:54 +0800, Raymond Li <
fai...@gmail.com>
wrote:
You shouldn't depend on that, it's just a coincidence of how the
rounding error happened to accumulate.
I've modified you program to display a bit more precision (attached
below). With the better display of precision, you can see the
roundoff errors accumulating differently:
(float) 0 item is 0.40000000596046448 accumulate is
0.40000000596046448
(float) 1 item is 0.40000000596046448 accumulate is
0.80000001192092896
(float) 2 item is 0.40000000596046448 accumulate is 1.2000000476837158
(float) 3 item is 0.40000000596046448 accumulate is 1.6000000238418579
(float) 4 item is 0.40000000596046448 accumulate is 2
(float) 5 item is 0.30000001192092896 accumulate is 2.2999999523162842
(float) 6 item is 0.30000001192092896 accumulate is 2.5999999046325684
(float) 7 item is 0.30000001192092896 accumulate is 2.8999998569488525
(float) 8 item is 0.20000000298023224 accumulate is 3.0999999046325684
(float) 9 item is 0.40000000596046448 accumulate is 3.5
rounded is 4
(double) 0 item is 0.40000000000000002 accumulate is
0.40000000000000002
(double) 1 item is 0.40000000000000002 accumulate is
0.80000000000000004
(double) 2 item is 0.40000000000000002 accumulate is
1.2000000000000002
(double) 3 item is 0.40000000000000002 accumulate is
1.6000000000000001
(double) 4 item is 0.40000000000000002 accumulate is 2
(double) 5 item is 0.29999999999999999 accumulate is
2.2999999999999998
(double) 6 item is 0.29999999999999999 accumulate is
2.5999999999999996
(double) 7 item is 0.29999999999999999 accumulate is
2.8999999999999995
(double) 8 item is 0.20000000000000001 accumulate is
3.0999999999999996
(double) 9 item is 0.40000000000000002 accumulate is
3.4999999999999996
rounded is 3
But as I said, you can depend on that. For example, changing the
series to:
{
9.0,
9.0,
9.0,
8.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
};
Will cause the float version to round to 3 as well:
(float) 0 item is 0.89999997615814209 accumulate is
0.89999997615814209
(float) 1 item is 0.89999997615814209 accumulate is 1.7999999523162842
(float) 2 item is 0.89999997615814209 accumulate is 2.6999998092651367
(float) 3 item is 0.80000001192092896 accumulate is 3.4999997615814209
(float) 4 item is 0 accumulate is 3.4999997615814209
(float) 5 item is 0 accumulate is 3.4999997615814209
(float) 6 item is 0 accumulate is 3.4999997615814209
(float) 7 item is 0 accumulate is 3.4999997615814209
(float) 8 item is 0 accumulate is 3.4999997615814209
(float) 9 item is 0 accumulate is 3.4999997615814209
rounded is 3
IOW, this will vary with the exact set of inputs. So don't do that.
Even worse, you can get this to wander around based on whether or not
you tell the compiler to produce strict IEEE compliant math, and the
requested optimization level (on x86 machines you often see
intermediate results with a higher precision than you'd expect if the
code is using the x87 FPU).
Changing the values (as has been suggested) so that all of them have
exact representations can reduce the roundoff error, but cannot
eliminate it (you'll still get roundoff error on that final division,
even if you get none on the individual terms). OTOH, you probably
will get away with this so long as the only case you care about is
"xxx5.0 / 10.0", since that will have an exact result (.5 being
exactly representable in a binary FP number). This is obviously
fragile, and will go to pot the first time someone tosses in a number
with more than a single decimal place.
If this is important, I'd generally advise avoiding floating point
entirely, and use a package that allows you to do this all with scaled
integers or rationals. I'm not sure what the PL/SQL code is doing,
but it may be using a scaled type, or if it's using a floating type,
they might just be hitting one set of rounding errors that happens to
generate the expected result. And that may just be the worst scenario
- trying to duplicate the existing behavior when the existing behavior
is not what anyone is actually expecting.
/* ----- */
#include <cmath>
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
int fun1();
int fun2();
inline int round(double x) { return (floor(x + 0.5)); }
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 << "(float) " << i << " item is " <<
std::setprecision(20) << item
<< " accumulate is " << std::setprecision(20) << total
<< endl;
}
float answer=round(total);
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 << "(double) " << i << " item is " <<
std::setprecision(20) << item
<< " accumulate is " << std::setprecision(20) << total
<< endl;
}
double answer=round(total);
cout << "rounded is " << answer << endl;
return 0;
}