dash2 has asked for the wisdom of the Perl Monks concerning the following question:

Well, another strange problem. Presumably this is due to the old "computers count in binary" thing. I have two equal numbers, but Perl doesn't think they are equal.

sub check_price_paid { my $price_paid = param("some_param"); my $price_to_pay = &calculate_price; return $price_to_pay <= $price_paid; # This is where we get pr +oblems!! } sub calculate_price { my $price = &calculate_base_price; $price += &calculate_tax($price); return $price; } sub calculate_base_price { my $price = 0; foreach (@item) { $price += $_ -> price; } return $price; # this will always be to 2 sig figs. } sub calculate_tax { my $price = shift; my $tax; $tax = $price * $CONFIGS->{tax} / 100; # need to be accounting correct $tax *= 10 ** $currency_decimal_places; if ($tax =~ /\.(\d)/ && $1 >= 5) { $tax ++} # a cheat, but sim +ple. This rounds up or down, in accounting-correct manner. $tax = int $tax; $tax /= 10 ** $currency_decimal_places; return $tax; }

Now my problem is that sometimes, the price_paid (a parameter returned to the script) is, e.g. 41.79. And the price_to_pay is calculated as 41.79. So if I do 'warn "$price_to_pay and $price_paid"' I get "41.79 and 41.79". But then, Perl returns 0 from check_price_paid.

Is there any way round this? Do I need, e.g., to somehow make sure both variables are converted to numbers? But Perl has weak typing so that don't apply. Surely there must be some reliable way of checking that two rounded numbers are identical?

dave hj~

Replies are listed 'Best First'.
Re: strange arithmetic results
by Masem (Monsignor) on Sep 25, 2001 at 17:02 UTC
    If you are dealing with currency, it is nearly always better to store the total cents, as opposed to the total dollars (and fractional cents), because floating point math and comparisons are not always consistent. It would also probably make your code much easier to follow, since you don't have to convert back and forth to do rounding.

    If, however, you need to use the math this way, it's better to compare floating points using differences rather than straight operators particularly when values can approach each other.

    -----------------------------------------------------
    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
    It's not what you know, but knowing how to find it if you don't know that's important

Re: strange arithmetic results
by arturo (Vicar) on Sep 25, 2001 at 17:05 UTC

    One workaround is to *impose* precision: here, you want equality up to the first two decimal places, so you could multiply your numbers by 100 and take the int() value of them to compare. Here's a subroutine:

    sub cmp_dollar { my ($c, $d) = map { int( $_ * 100 ) } @_; $c == $d; }

    This oughta deal with the trailing \n issue, too, as that will be ignored by the automagic string-number conversion.

    perl -e 'print "How sweet does a rose smell? "; chomp ($n = <STDIN>); +$rose = "smells sweet to degree $n"; *other_name = *rose; print "$oth +er_name\n"'

      Actually, that's not the way to deal with it: you want to get your data values converted into cents (and error correct at this point) very early, and then work in cents (or tenths of cents) throughout, rather than holding them in dollars and multiplying by 100 at odd moments to do comparisons.

      The reason this is a problem is that, as with the fraction 1/3 in decimal, 1/10, or 1/100 does not have a finite representation in binary. So the end of the value gets chopped off. Then, when it's multiplied back up, that chopped piece is not reinstated. (Exactly the same way that 1/3 * 3 on a calculator can give you the answer 0.99999)

      So if you've got two amounts which differ by very small fractions of a cent, and multiply them both by 100, you multiply the error by 100. That might be enough to make int return different answers.

      sub dollars_to_cents { map { int ($_*100+0.5) } @_; } $dollars=<STDIN> $cents=dollars_to_cents($dollars)
      would be better way of dealing with the conversion.

      Change the multiplier in the function, if you wish more accuracy (so 1000 if you wish to deal in tenths of a cent). The addition of 0.5 brings the answer to the closest cent, to allow for the possible rounding error. It doesn't need changing

Re: strange arithmetic results
by perrin (Chancellor) on Sep 25, 2001 at 18:53 UTC
Re: strange arithmetic results
by Caillte (Friar) on Sep 25, 2001 at 16:56 UTC

    You don't show what param() does... If you are reading from a file or something similar, you may be comparing '41.79' with '41.79\n'

    $japh->{'Caillte'} = $me;

      perl -e 'print "41.79\n"<="41.79"' 1

      And the same if you reverse the operands. <= is numerical, so the trailing \n is ignored.