Re: Unwanted implicit conversion to float
by merlyn (Sage) on Mar 12, 2001 at 22:37 UTC
|
You've apparently not learned that (1/10) cannot be precisely represented in binary floating point. It's an infinite repeating expansion, which has to be truncated at some point (heh). Multiplying that by 10 does not yield 1.0 any more.
If you want precise pennies, you must count in pennies (using integer values for pennies).
-- Randal L. Schwartz, Perl hacker | [reply] [Watch: Dir/Any] |
|
I'm curious... I recall this being a bigger problem in older versions of Perl and patches being applied such that Perl became smart enough to avoid including "9999999" or "0000001" on the end of floating point numbers.
So is this a case where round-off is making the result "a bit off" from the closest possible value and this prevents the "smarter" code from avoiding the extra trailing digits? I wouldn't think that adding "1" would cause that problem.
Update: Okay, we're doing a running total here so there is round-off involved. Sorry for the confusion.
-
tye
(but my friends call me "Tye")
| [reply] [Watch: Dir/Any] |
|
*cry* sometimes ignorance is bliss...
who wants the bill for the head imprint in my desk? You or turnstep ? :)
| [reply] [Watch: Dir/Any] |
Re: Unwanted implicit conversion to float
by AgentM (Curate) on Mar 12, 2001 at 22:42 UTC
|
This is something very low-level. It has to do with the IEEE representation of a float/double. One thing you might try is multiplying each number by one hundred, using integer, perform your calculation, and dividing back by one hundred. You might also try a different rounding mechanism such as POSIX::ceil or floor. Only use the number of digits of precision you need, "convert" that into an integer, perform the addition, and "convert" back.
What I don't understand is your title. If you start with floats, then you'll end with floats.
AgentM Systems nor Nasca Enterprises nor
Bone::Easy nor Macperl is responsible for the
comments made by
AgentM. Remember, you can build any logical system with NOR.
| [reply] [Watch: Dir/Any] [d/l] |
(Ovid) Re: Unwanted implicit conversion to float
by Ovid (Cardinal) on Mar 12, 2001 at 22:49 UTC
|
I suppose there are a variety of ways to handle this. One of the most straightforward would be the following:
$val += sprintf( "%.2f", $1 );
I think that should take care of that. My only concern would be the potential for rounding errors, if any of the numbers that you are matching against have more than two digits after the decimal point. In that case, you might want to just do the sprintf at the end.
If anyone sees a problem with this approach, speak up! I've used it before and would love to know if I boo-booed.
Cheers,
Ovid
Join the Perlmonks Setiathome Group or just click on the the link and check out our stats. | [reply] [Watch: Dir/Any] [d/l] |
|
I see just a (maybe null) risk in the fact that you still
add 2 floating numbers, even if $1 has been
rounded to 2 digits after the decimal point. So something
weird can still happen with the result. After all turnstep's
original code only added such numbers. So you could end up
every now and then with "long numbers".
Doing $val= sprintf( "%.2f", $val + $1); removes
the problem, $val is _always_ rounded properly. The cleanest way is of course to keep $val
as is and to round it only when printing it.
This may just be nitpicking but as I have no idea of
the conditions that create the problem and it is probably
quite hard to reproduce, I would not take
any chance.
| [reply] [Watch: Dir/Any] |
|
The cleanest way is of course to keep $val as is and to round it only when printing it.
Perhaps I misunderstand what you mean by "clean", but the safest way to deal with money is to round the internal number any time you print something out1, and use that rounded number for all future calculations. If you are doing calculations with only whole-cent amounts (such as adding up a bunch of deposits), then you should round after every operation (or at least frequently) to prevent tiny round-off errors from accumulating such that your final total is off by one cent when it is finally rounded.
If you are doing calculations with fractional cents, then you need to define exactly where the rounding occurs (for example, interest is applied one a month and is always rounded at that time).
1 Update: The reason I say "every time you print something out" is that you (almost always) need to round if the value is being reported to the user. You can (and often should) do internal, intermediate calculations in fractional cents, but whenever you report [or could report] an amount to the user, you need to round your internal representation of that amount the same way you [would] round the amount you [could] report.
-
tye
(but my friends call me "Tye")
| [reply] [Watch: Dir/Any] |
|
Re: Unwanted implicit conversion to float
by larryl (Monk) on Mar 12, 2001 at 23:17 UTC
|
You might want to look at using a module like John Peacock's
Math::Currency,
"Exact Currency Math with Formatting and Rounding".
| [reply] [Watch: Dir/Any] |
Re: Unwanted implicit conversion to float
by mirod (Canon) on Mar 12, 2001 at 22:48 UTC
|
In this case sprintf is your friend (and coming from a C background
an asset). It might slow
you down somehow but if you throw in $val= sprintf( "%f6.2", $val);
before using $val, you will get back the proper precision.
Here is an example:
#!/usr/bin/perl -w
use strict;
my @rows=<DATA>;
my $val=0;
for (@rows) {
chomp;
if (/([\d.]+)/) {
$val += $1;
$val= sprintf( "%6.2f", $val);
warn "Added $1 from $_ and set it to $val\n";
}
}
__DATA__
we get to $6257.65
now we add $33.4099999999999999
we add $37.24
outputs:
Added 6257.65 from we get to $6257.65 and set it to 6257.65
Added 33.4099999999999999 from now we add $33.4099999999999999 and set it to 6291.06
Added 37.24 from we add $37.24 and set it to 6328.30 | [reply] [Watch: Dir/Any] [d/l] |
Re: Unwanted implicit conversion to float
by Trimbach (Curate) on Mar 12, 2001 at 22:39 UTC
|
| [reply] [Watch: Dir/Any] |