|
Posted by Peter J. Holzer on March 1, 2008, 10:19 am
Please log in for more thread options
> Rounding in financial applications can have serious implications, and
> the rounding method used should be specified precisely. In these cases,
> it probably pays not to trust whichever system rounding is being used by
> Perl, but to instead implement the rounding function you need yourself.
>
> To see why, notice how you'll still have an issue on half-way-point
> alternation:
>
> for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
>
> 0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
> 0.8 0.8 0.9 0.9 1.0 1.0
>
> Don't blame Perl. It's the same as in C. IEEE says we have to do this.
That example conflates two different issues:
1) 0.05 is not exactly representable as a binary floating point number,
so $i doesn't get the values 0.00, 0.05, 0.10, 0.15, 0.20, 0.25, ...
as one might naively expect. Instead it gets the values
0.00000000000000000000
0.05000000000000000278
0.10000000000000000555
0.15000000000000002220
0.20000000000000001110
0.25000000000000000000
...
(rounded to 20 digits after the comma). So the second value
(0.05000000000000000278) is actually a bit above 0.05 and therefore
has to be rounded up according to the "round to nearest" rule.
So most of these numbers are not at the "half-way-point", and can be
clearly and anambiguosly rounded (but the result is not what you
expect when you think they are at the half-way-point.
2) The one exception is 0.25 which just happens to be exact because all
the rounding errors cancel out at that point[1]. 0.25 is exactly
half-way between 0.2 and 0.3 so "round to nearest" cannot decide
between these two. In school we all learned to round up in this case.
The IEEE rules, however, mandate that one must round to the nearest
*even* number, i.e., 0.2 in this case.
I'm not sure which of these issues the example should demonstrate, but
showing both in a single example without adequate explanation is
confusing. The "half-way-point" problem can be demonstrated correctly
with:
for ($i = 0; $i <= 10; $i += 0.5) { printf "%.0f ",$i}
0 0 1 2 2 2 3 4 4 4 5 6 6 6 7 8 8 8 9 10 10
The fact that decimal fractions are not generally representable in
binary fp and that adding them is an especially bad idea can be
demonstrated better with:
for ($i = 0, $j = 0; $i < 1.01; $i += 0.05, $j += 5) {
print $i * 100 - $j, " ";
}
0 0 0 1.77635683940025e-15 0 0 0 0 0 -7.105427357601e-15
-7.105427357601e-15 -7.105427357601e-15 0 0 0
1.4210854715202e-14 1.4210854715202e-14 1.4210854715202e-14
2.8421709430404e-14 2.8421709430404e-14 2.8421709430404e-14
(Although there are still too many 0s in the output - maybe somebody can
find a better example).
> Perl numbers whose absolute values are integers under 2**31 (on 32 bit
2**53 actually, if the machine uses IEEE FP arithmetic.
hp
[1] I didn't expect that and I even repeated the calculation with pencil
and paper. Adding a number which is slightly larger than 0.05 5 times
gives exactly 0.25 in IEEE double arithmetic.
|