Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Rounding issue with defined outcome

42 views
Skip to first unread message

Bart Van der Donck

unread,
May 13, 2011, 7:19:39 AM5/13/11
to
Hello,

I have to deal with a rounding issue which basically goes back to high-
precision calculations. The issue is that the outcome is already known
and should match.

----------------------------------------------------------------
Code:
----------------------------------------------------------------

#!/usr/bin/perl
use strict;
use warnings;
my $TOTEXC = 0;
my $TOTINC = 0;
my $TOTTAX = 0;
my @products = ( # product name, price, tax%, quantity
'Screen|506.21|21|4',
'Cables|425.50|21|5',
'Server|585.26|21|1',
'Battery|58.62|21|3',
'Keyb.|44.46|21|2',
'Mouse|15.25|21|6',
'Battery|66.03|21|5'
);
my $percent = 1.2958684232587526; # commission dealer
my $factor = 0.8567; # some fixed factor

print qq{\nPROD\tPRICE\tQUANT\tNET\tTAX\tBRUT\n};
for (@products)
{
my ($prodname, $price, $tax, $quantity) = split /\|/, $_;
$price = ( $price + ($price * $factor / 100) ) * $percent;
my $net = $price * $quantity;
my $brut = $net * ( 1 + ($tax / 100));
$TOTEXC+= $net;
$TOTINC+= $brut;
$TOTTAX+= $brut - $net;
$_ = sprintf("%.2f", $_) for ($price, $net, $brut);
print qq{$prodname\t$price\t$quantity\t$net\t$tax%\t$brut\n};
}
$_ = sprintf("%.2f", $_) for ($TOTEXC, $TOTINC, $TOTTAX);
print qq{\nNet: $TOTEXC\nTax: $TOTTAX\nBrut: $TOTINC\n};

----------------------------------------------------------------
Output:
----------------------------------------------------------------

PROD PRICE QUANT NET TAX BRUT
Screen 661.60 4 2646.41 21% 3202.15
Cables 556.12 5 2780.58 21% 3364.50
Server 764.92 1 764.92 21% 925.55
Battery 76.61 3 229.84 21% 278.11
Keyb. 58.11 2 116.22 21% 140.62
Mouse 19.93 6 119.59 21% 144.70
Battery 86.30 5 431.50 21% 522.11

Net: 7089.05
Tax: 1488.70
Brut: 8577.74

----------------------------------------------------------------
Questions:
----------------------------------------------------------------

- Is there any way to perform the calculations at extreme
high precision, so that all caculated alphanumerics match ?

- I have a known outcome (let's say eg. 8577.77) which was
done already in javascript by another application; is there
a way to guarantee that both outcomes are identical ?

- I have the feeling that this is maybe a common situation,
are there 'classical' approaches for this kind of things ?

- If I want to manually check the order, the calculations
must match (e.g. 'Net'+'Tax' must equal to 'Brut')

Thank you,

--
Bart

Wolf Behrenhoff

unread,
May 13, 2011, 7:39:56 AM5/13/11
to
On 13.05.2011 13:19, Bart Van der Donck wrote:
> Hello,
>
> I have to deal with a rounding issue which basically goes back to high-
> precision calculations. The issue is that the outcome is already known
> and should match.
>
> ----------------------------------------------------------------
> Questions:
> ----------------------------------------------------------------
>
> - Is there any way to perform the calculations at extreme
> high precision, so that all caculated alphanumerics match ?
>
> - I have a known outcome (let's say eg. 8577.77) which was
> done already in javascript by another application; is there
> a way to guarantee that both outcomes are identical ?
>
> - I have the feeling that this is maybe a common situation,
> are there 'classical' approaches for this kind of things ?
>
> - If I want to manually check the order, the calculations
> must match (e.g. 'Net'+'Tax' must equal to 'Brut')

Obviously you are doing something with money. In that case you usually
do NOT need extreme high precision because there are defined rules how
and when to round to how many digits.

It might be that you need to round to 2 (or 4) digits behind the decimal
point after each step.

So I can only give the advice to really look into the law how to add the
tax correctly and to find out what the contract with the commission
dealer really says.

Wolf

Tad McClellan

unread,
May 13, 2011, 7:48:25 AM5/13/11
to
Bart Van der Donck <ba...@nijlen.com> wrote:

> my $percent = 1.2958684232587526; # commission dealer


Add this, and look carefully at the output it makes:

printf "%30.20f\n", $percent;

So things are wrong even before you start making any calculations...


> - I have the feeling that this is maybe a common situation,


Yes it is, and it is not restricted to Perl.

This problem applies to how numbers are stored in computers.


> are there 'classical' approaches for this kind of things ?


perldoc perlnumber

See also: What Every Computer Scientist Should Know About Floating-Point
Arithmetic

http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html


--
Tad McClellan
email: perl -le "print scalar reverse qq/moc.liamg\100cm.j.dat/"
The above message is a Usenet post.
I don't recall having given anyone permission to use it on a Web site.

Jürgen Exner

unread,
May 20, 2011, 11:53:44 PM5/20/11
to
Bart Van der Donck <ba...@nijlen.com> wrote:
>I have to deal with a rounding issue which basically goes back to high-
>precision calculations.

Actually it doesn't. It goes back to understanding how calculations
work, e.g. basic computer numeric as well as basic rules for accounting.

>The issue is that the outcome is already known
>and should match.

Then you should follow the same processes and rules that resulted in the
already known outcome.

[...]


>- Is there any way to perform the calculations at extreme
> high precision,

Yes, it is called symbolic calculations and there are more or less
expensive computer programs out there that do those. But that doesn't
help you one bit, because ...

>so that all caculated alphanumerics match ?

... the already known outcome was not computed using extreme high
precision, either.

>- I have a known outcome (let's say eg. 8577.77) which was
> done already in javascript by another application; is there
> a way to guarantee that both outcomes are identical ?

Yes. Use the same rules as have been used in the other application.

>- I have the feeling that this is maybe a common situation,

Yes. It is the misguided believe that higher precision equals better
results. THAT IS NOT THE CASE.
You should do your computations using the same method that was used in
the other application using the same precision and the same mathematical
operators with the same behaviour and the same algorithm.
In your case you should specifically check with accountants about how
sums are computed. Trivial example: if the commision is computed for
each individual transaction, listed in e.g. Euro and Cent, and then
added, then you will usually get a different result compared to adding
all transactions and then computing the commision on the total sales.

> are there 'classical' approaches for this kind of things ?

Yes, see Introduction into Basic Numerics for Computer Scientists

>- If I want to manually check the order, the calculations
> must match (e.g. 'Net'+'Tax' must equal to 'Brut')

Then you need to calculate them the same way as they were calculated in
the other application..

jue

cha...@pulsenet.com

unread,
May 21, 2011, 5:52:37 PM5/21/11
to
On May 13, 7:19 am, Bart Van der Donck <b...@nijlen.com> wrote:
> Hello,
>
> I have to deal with a rounding issue which basically goes back to high-
> precision calculations. The issue is that the outcome is already known
> and should match.


[snip code]

> Questions:
> ----------------------------------------------------------------
>
> - Is there any way to perform the calculations at extreme
>   high precision, so that all caculated alphanumerics match ?

convert to cents I think.
Then when ready to output, convert back to dollars (do not use sprintf
"%.2f").
Instead, use the substitution operator:

# Convert to pennies from dollars.
# $price is now an integer
$price =~ s/\.(?=\d\d$)// or die "Cannot expand $price to integer.
$!";

# convert from pennies to dollars, (insert decimal point), for
output.
s/(\d\d)$/.$1/ for $price, $net, $brut;

>
> - I have a known outcome (let's say eg. 8577.77) which was
>   done already in javascript by another application; is there
>   a way to guarantee that both outcomes are identical ?
>
> - I have the feeling that this is maybe a common situation,
>   are there 'classical' approaches for this kind of things ?
>
> - If I want to manually check the order, the calculations
>   must match (e.g. 'Net'+'Tax' must equal to 'Brut')
>
> Thank you,
>
> --
>  Bart

Hi Bart,

FWIW, I believe my code (below) exhibits Pennsylvania's sales tax.

#!/usr/bin/perl
use strict;
use warnings;

use 5.012;
use POSIX qw/ ceil /;

for my $cents (1 .. 111) {
my $tax = ceil($cents * .06);
my $scale = $cents % 100;
$tax-- if $scale >= 1 && $scale <= 10;
say "$cents $tax";
}

Note that this state (perhaps others too) take the 'ceil(ing)' of the
cents. (Use cents because I am now dealing with integers). Then it is
necessary to convert back to dollars for output purposes.

Chris

Peter J. Holzer

unread,
May 21, 2011, 6:15:43 PM5/21/11
to
On 2011-05-21 21:52, cha...@pulsenet.com <cha...@pulsenet.com> wrote:
> convert to cents I think.
> Then when ready to output, convert back to dollars (do not use sprintf
> "%.2f").

Why not? Can you show a case where it produces the wrong result?

OTOH:

> # convert from pennies to dollars, (insert decimal point), for
> output.
> s/(\d\d)$/.$1/ for $price, $net, $brut;

This will not work for amount <= 99 cents.


> my $tax = ceil($cents * .06);

my $tax = ceil($cents * 6 / 100);

otherwise you multiply with
0.059999999999999997779553950749686919152736663818359375 which may or
may not make a difference.

hp

cha...@pulsenet.com

unread,
May 21, 2011, 7:55:27 PM5/21/11
to
On May 21, 6:15 pm, "Peter J. Holzer" <hjp-usen...@hjp.at> wrote:

> On 2011-05-21 21:52, char...@pulsenet.com <char...@pulsenet.com> wrote:
>
> > convert to cents I think.
> > Then when ready to output, convert back to dollars (do not use sprintf
> > "%.2f").
>
> Why not? Can you show a case where it produces the wrong result?

No, I was assuming that by avoiding the sprintf I could avoid a
rounding error.

>
> OTOH:
>
> >         # convert from pennies to dollars, (insert decimal point), for
> > output.
> >    s/(\d\d)$/.$1/ for $price, $net, $brut;
>
> This will not work for amount <= 99 cents.

Good catch. I think you meant it will not work for <= 9 cents. No
match and no placement of the decimal point. I should have been more
careful in uncharted (for me) territory. s/(\d\d?)$/.$1/ would be the
correct answer I believe?

>
> >    my $tax = ceil($cents * .06);
>
>         my $tax = ceil($cents * 6 / 100);
>
> otherwise you multiply with
> 0.059999999999999997779553950749686919152736663818359375 which may or
> may not make a difference.

0 for 3. Not very good.

>
>         hp

Peter J. Holzer

unread,
May 22, 2011, 10:51:08 AM5/22/11
to
On 2011-05-21 23:55, cha...@pulsenet.com <cha...@pulsenet.com> wrote:
> On May 21, 6:15 pm, "Peter J. Holzer" <hjp-usen...@hjp.at> wrote:
>> On 2011-05-21 21:52, char...@pulsenet.com <char...@pulsenet.com> wrote:
>>
>> > convert to cents I think.
>> > Then when ready to output, convert back to dollars (do not use sprintf
>> > "%.2f").
>>
>> Why not? Can you show a case where it produces the wrong result?
>
> No, I was assuming that by avoiding the sprintf I could avoid a
> rounding error.

If you use sprintf("%.2f", $x/100) there will indeed be a rounding error
in the computation of $x/100 and possibly another one in rounding two
digits. Those rounding errors will be somewhere in the magnitude of
$x/100/2**53 (assuming IEEE double precision floating point arithmetic),
and as long as that error is less than 0.005, it won't affect the
result. So $x/100/2**53 <= 0.005, therefore $x <= 0.005 * 2**53 * 100
or $x <= 4.5E15. Lets be conservative and say you're safe if $x is less
than 1E14.


>> OTOH:
>>
>> >         # convert from pennies to dollars, (insert decimal point), for
>> > output.
>> >    s/(\d\d)$/.$1/ for $price, $net, $brut;
>>
>> This will not work for amount <= 99 cents.
>
> Good catch. I think you meant it will not work for <= 9 cents. No
> match and no placement of the decimal point.

Actually I meant <= 99 cents, but I realize that there are locales where
".99" (instead of "0.99") is correct. So if your bean counter doesn't
throw a hissy fit over the missing zero, that's ok. For <= 9 cents it's
always incorrect.


> I should have been more careful in uncharted (for me) territory.
> s/(\d\d?)$/.$1/ would be the correct answer I believe?

No that converts 9 to ".9" instead of ".09".

This would work:

for my $cents (9, 99, 999, 999_999_999_999) {
my $dollars = sprintf("%03.0f", $cents);
$dollars =~ s/(\d\d)$/.$1/;
print "$cents -> $dollars\n";
}

hp

s...@netherlands.com

unread,
May 22, 2011, 2:24:07 PM5/22/11
to
On Fri, 13 May 2011 04:19:39 -0700 (PDT), Bart Van der Donck <ba...@nijlen.com> wrote:

[snip code]


There could be a typo in your numbers/code.

If the Brut total were to be 8577.77 , the Net and Tax totals
would be significantly alterred. So, I asume you are not giving
the exact input/output from the code where you state the known
outcome of the Brut total of 8577.77

The reason I know this is that :

1) Net/Tax/Brut intermediate values are all interrelated.

2) Applying arithmetic significance of measured values do not
alter the results.
ie.. percent ~ 1.2958685 does not alter the outcome (a measured value)
factor ~ 0.8567 is the least significant (also a measured value)

3) Applying sprintf "%.4f" to $brut intermediate raises the Brut total
at the expence of Tax and Net. Even then, it only raises Brut to 8577.75

4) Rounding percent and factor to make Brut fit in the 8577.77 range,
significantly alters Tax and Net.

I think its probable there is some other non-mathmatical error going on here.

-sln

0 new messages