I am developing a program which has to add Value Added Tax to an order at
17.5%. I do this roughly as follows:
$tax=17.5;
$value=57; # (for example )
$value+=$value*$tax*0.01; # tax is a percentage
$value = sprintf(%.2f,$value); # to convert $value to the nearest penny
My problem is that sprintf doesn't round the numbers correctly. E.g. for
this instance, $value is 66.975 after line 3. This should round to 66.98,
but in fact the fourth line outputs 66.97.
Could anyone tell me what I am doing wrong and how to put it right? I have
looked at the sprintf() entry in the perlfunc man page, and also the sprintf
man(3) page, but with no success.
Thanks very much. Replies to dav...@mail.com will be gratefully accepted!
Dave
>Could anyone tell me what I am doing wrong and how to put it right? I have
>looked at the sprintf() entry in the perlfunc man page, and also the
sprintf
>man(3) page, but with no success.
I'm pretty shaky and willing to be corrected, but isn't your sprintf
function just a means of formatting text, rather than of doing maths
calculations? So perhaps it just lops your digit string after the given
number of decimal places? In which case you might have to write yourself a
short rounding routine?
I guess this would read down to the third digit after the decimal place in
your VAT result, test whether this was greater than five, increment the
second digit after the decimal if the test proved true, and lop off the
rest. Hard to believe it doesn't already exist, though.
On the other hand, see Tim Conrow's reply to "Perl substraction weird
result", posted on the 12th.
Elisa Francesca Roselli
I came to the conclusion that sprintf() wouldn't work because it sometimes
rounds .5 down, for very good mathematical reasons (as a kid I wondered why 0.5
"had to round up" - encouraging to know it doesn't). I want my rounding to round
0.5 up all the time, which I assume is the accounting convention.
So I wrote something like this:
$total=57;
$tax=17.5;
$total+=$total*$tax$0.01;
$total*=100; #these 3 lines to round to 2 sig digits
$total+=0.5;
$total=int($total);
$total/=100;
But I still get a problem. $total is 6697.5 after multiplying by 100, and 6698
after adding 0.5. But then int($total) puts it down to 6697!
I assume the problem is that weird floating point arithmetic stuff is
making 6698 be really a bit less than 6698 before it gets integerized. But I
don't know how to work around this. One option that comes up is "do everything
in cents so you are working with integers internally". Unfortunately, I can't
do that, because my program takes external input in the form of dollars and
cents (or pounds and pence). It would be too difficult to make users calculate
everything in cents. So, I have to use floating point numbers. Is there a way
of doing rounding while ensuring that 0.5 always gets rounded up?
Thanks again, and apologies again if I have missed something obvious.
David.
On Thu, 14 Sep 2000, David Hugh-Jones wrote:
>Hi,
>
>I am developing a program which has to add Value Added Tax to an order at
>17.5%. I do this roughly as follows:
>
>$tax=17.5;
>$value=57; # (for example )
>$value+=$value*$tax*0.01; # tax is a percentage
>$value = sprintf(%.2f,$value); # to convert $value to the nearest penny
>
>My problem is that sprintf doesn't round the numbers correctly. E.g. for
>this instance, $value is 66.975 after line 3. This should round to 66.98,
>but in fact the fourth line outputs 66.97.
>
>Could anyone tell me what I am doing wrong and how to put it right? I have
>looked at the sprintf() entry in the perlfunc man page, and also the sprintf
>man(3) page, but with no success.
>
>I came to the conclusion that sprintf() wouldn't work because it sometimes
>rounds .5 down, for very good mathematical reasons (as a kid I wondered why 0.5
>"had to round up" - encouraging to know it doesn't). I want my rounding to round
>0.5 up all the time, which I assume is the accounting convention.
>
>So I wrote something like this:
>$total=57;
>$tax=17.5;
>$total+=$total*$tax$0.01;
>$total*=100; #these 3 lines to round to 2 sig digits
>$total+=0.5;
>$total=int($total);
>$total/=100;
>
>But I still get a problem. $total is 6697.5 after multiplying by 100, and 6698
>after adding 0.5. But then int($total) puts it down to 6697!
If you change the sixth line "$total=int($total);" to:
$total = sprintf "%f.0", $total;
Then it will do what you want (I think. It works for the example you
gave, but it's not tested really). This is because although perl says
$total is 6698 it's really more like 6697.9999999999999999998765 so when
int() chops off the integer portion ugly things happen (did you read
"perldoc -f int"). Anyway glad to see you read the advice and made an
effort.
--
Gwyn Judd (print `echo 'tj...@guvfybir.qlaqaf.bet' | rot13`)
Whatever it is, I fear Greeks even when they bring gifts.
-- Publius Vergilius Maro (Virgil)
I took your advice and tried sprintf("%.0f",...) instead of int(). This works
fine for $total=57. However, for $total=38, it goes too high, giving a value
with tax of 44.66 instead of 44.65. I assume that in this case, adding 0.5 to
4465 gives 4465.5, and sprintf() then rounds this up to 4466. In other words,
sprintf() will not work if my result is an integer (in cents).
My fundamental problem is, I am trying to do floating point arithmetic and then
need to round the results correctly (by accounting standards). Int isn't
accurate enough, and sprintf() rounds for mathematicians but not
accountants. Timeo mathematicos et equationes ferentes;-)
Is there anyone out there who has found a way to work this?
thanks
David
Arg, the whole jeopardy thing has confused me....
How's this:
$rounded_up = int($value)==$value? $value : sprintf("%.0f",$value+0.5);
Or have I got the wrong end of the stick?
You want an always round up right?
Surely this was too trivial for a newsgroup?
Seems to fail for:
$value=4.0000000000000001;
Where it rounds down again.
I guess that's about the limit of the precision of the compare.
--
Jon
perl -e 'print map {chr(ord($_)-3)} split //, "MrqEdunhuClqdph1frp";'
Sent via Deja.com http://www.deja.com/
Before you buy.
...
> I assume the problem is that weird floating point arithmetic stuff is
> making 6698 be really a bit less than 6698 before it gets integerized. But I
> don't know how to work around this. One option that comes up is "do everything
> in cents so you are working with integers internally". Unfortunately, I can't
> do that, because my program takes external input in the form of dollars and
> cents (or pounds and pence). It would be too difficult to make users calculate
> everything in cents. So, I have to use floating point numbers.
Why do the users have to calculate everything in cents? Multiply their
input by 100 immediately, work with cents internally, then convert back
on output.
--
(Just Another Larry) Rosler
Hewlett-Packard Laboratories
http://www.hpl.hp.com/personal/Larry_Rosler/
l...@hpl.hp.com
Users want to calculate in dollars (and hundredths of a dollar). So,
like you say, multiply dollars by 100 and you're working in cents. Of
course, if you want to divide your ill-gotten gains three ways, or
want to compute seven percent interest, compounded daily, you're still
going to have rounding.
One of the earliest computer banking scams involved some careful
truncation made to look like rounding. It can add up.
--
Russ Jones - HP OpenView IT/Operatons support
Raytheon Aircraft Company, Wichita KS
russ_...@rac.ray.com 316-676-0747
Quae narravi, nullo modo negabo. - Catullus
It's highly, highly unlikely your users are inputting the numbers as
floating point numbers and are not willing to switch to character based
input. In fact, I'm quite certain your users are using characters to
enter the numbers.
Abigail
--
srand 123456;$-=rand$_--=>@[[$-,$_]=@[[$_,$-]for(reverse+1..(@[=split
//=>"IGrACVGQ\x02GJCWVhP\x02PL\x02jNMP"));print+(map{$_^q^"^}@[),"\n"
__END__
A rabbit watches near // the forest. A beetle over // a pool. A Bishop.
Multipying by 100 forces numerical conversion. At that moment, the bad
roundoff might already happen.
The input comes as characters. Remove the decimal point with a regex
and only then start using it as a number.
Abigail
--
map{${+chr}=chr}map{$_=>$_^ord$"}$=+$]..3*$=/2;
print "$J$u$s$t $a$n$o$t$h$e$r $P$e$r$l $H$a$c$k$e$r\n";
>I am developing a program which has to add Value Added Tax to an order at
>17.5%. I do this roughly as follows:
>
>$tax=17.5;
>$value=57; # (for example )
>$value+=$value*$tax*0.01; # tax is a percentage
>$value = sprintf(%.2f,$value); # to convert $value to the nearest penny
>
>My problem is that sprintf doesn't round the numbers correctly. E.g. for
>this instance, $value is 66.975 after line 3. This should round to 66.98,
>but in fact the fourth line outputs 66.97.
Yes... that's this annoying "round halves to nearest even" behaviour.
Statistically, it's "nice", but boy I sure hate the guts of it.
First of all: 0.01 is an exact number in floating point. Divide by 100
instead, which is an exact number in FP, and you might get nearer to
what you want.
Instead of what you've been trying so far, I'd attempt a multiplication
with 2. Round off, divide, and there you decide what direction you want
to go. You can work in cents, if you like, but still multiply by 2.
$twice_cents = sprintf '%.0f', 2 * $tax * $value; # integer
$twice_cents += 1 & $twice_cents; # next even
$value += $twice_cents / 200;
I have not thoroughly tested this, but seems to work for this example.
I do have the gut feeling that I'm rounding off twice, i.e. rounding off
a rounded off number. That's a nono, because if you round in the same
direction twice, you'll round up in the wrong direction. Say I round
0.3 to 0.5, and then round 0.5 to 1? Anyway, the tax estimation will
always be rounded up, you'll never get a too low figure. I think.
HTH,
Bart.
>It's highly, highly unlikely your users are inputting the numbers as
>floating point numbers and are not willing to switch to character based
>input. In fact, I'm quite certain your users are using characters to
>enter the numbers.
>
hi Abigail.
The user input actually comes in the form of a text config file which gets
read. But, anyway, I don't think the multiply by 100 idea solves it: I
still have to add 17.5% Value Added Tax, which is going to give floating point
results even with integer input.
dave
>
>Arg, the whole jeopardy thing has confused me....
>
>How's this:
>
>$rounded_up = int($value)==$value? $value : sprintf("%.0f",$value+0.5);
>
>Or have I got the wrong end of the stick?
>You want an always round up right?
>Surely this was too trivial for a newsgroup?
no, I don't always want to round up, I want to round up on 0.5 or more. And it
probably is very trivial, but I am so inept that I haven't yet found a
solution: feel free to offer one!
cheers
dave
I'm sure that if you buy something in the store that as 17.5% value tax,
you don't end up paying an amount that isn't expressable as an integer
number of pennies.
Why don't you apply the same rules?
Abigail
--
perl -wle'print"Кхуф бопфиет Ретм Ибглет"^"\x80"x24'
# A pair of old men
# under an elm. A kingfisher
# above a stream.
It's possible to do fixed place math in Perl. One way is to use
Math::BigInt. Multiply incoming values by the right adjuster, Do
what you want, then stuff a decimal in on output. For rounding
money you need to pick a method that preserves pennies. One method
that is common is round up if thousandths is 5 or greater and down
otherwise. Since half of the transactions are credits and half
are debits the pennies will balance out.
#!/usr/bin/perl
use warnings;
use strict;
use Math::BigInt;
use constant Places=>3;
my $balance = Math::BigInt->new( '123456784'.'0'x Places );
my $rate = Math::BigInt->new( 17.5 * 10**Places );
my $newbal = $balance + $balance*$rate/12;
my $dist = $newbal/45;
my $other = $newbal/7;
print join(" ", $newbal, $dist, $other), "\n";
print join(" ", map {displaycents($_)} $newbal, $dist, $other), "\n";
sub displaycents
{
my $amt = shift;
$amt += substr($amt, -1, 1) < 5 ? 0 : 10;
$amt = $amt->bnorm;
substr($amt, -1*Places, 0) = ".";
substr($amt, -1, 1) = '';
return $amt;
}
chris
--
This space intentionally left blank
>I'm sure that if you buy something in the store that as 17.5% value tax,
>you don't end up paying an amount that isn't expressable as an integer
>number of pennies.
>
>Why don't you apply the same rules?
In the good old days in England fractional pennies weren't un-heard of
(... of course in those days a penny was worth something, so a farthing
was useful...) and even after the dumbing down of the currency at the
beginning of the '70s there were 1/2 new penny coins, and old sixpenny
pieces could still be used (worth 2 1/2 "new pence").
Mike
--
mi...@stok.co.uk | The "`Stok' disclaimers" apply.
http://www.stok.co.uk/~mike/ |
GPG PGP Key 1024D/059913DA | Fingerprint 0570 71CD 6790 7C28 3D60
st...@colltech.com (CT - work) | 75D2 9EC4 C1C0 0599 13DA
No it's not. Read the original post. He's complaining of sprintf()
rounding down to an _odd_ digit.
The problem is that his value is _slightly less_ than 66.975, so any
sensible rounding rule is going to give 66.97.
Working in integral cents is one solution. Math::Fraction is another.
--
Ilmari Karonen - http://www.sci.fi/~iltzu/
Please ignore Godzilla | "By promoting postconditions to
and its pseudonyms - | preconditions, algorithms become
do not feed the troll. | remarkably simple." -- Abigail
Apologies for the throw away remark, about triviality, reading the
rest of the thread it appears that it isn't.
Round up if the fraction is greater than or equal to 0.5 (and the
value is positive):
$rounded = ($value-int($value))>=0.5? int($value)+1 : int($value);
There's apparently another issue with correctly calculating the
percentage in the first place, I'll leave that to people who know
their maths.
$rounded_up = int(int($value * 2.0 + 1.0) / 2);
<mexicanm...@my-deja.com> wrote in message
news:8q4mm4$roj$1...@nnrp1.deja.com...
>Round up if the fraction is greater than or equal to 0.5 (and the
>value is positive):
>
>$rounded = ($value-int($value))>=0.5? int($value)+1 : int($value);
Doesn't that still leave aproblem with the hundredths? I.e. we want a
round-up at the second decimal place, not the first - 6.75 rounding up to
6.76 as it were. So adding one to a whole value isn't going to help, if I'm
understanding you correctly.
Elisa Francesca Roselli
I think you're understanding me (and the spec) better than I am.
To, hopefully, rescue me from looking like a 'right tool', I've got a
version based on text except for one 0.001 addition coupled with a
little test to highlight differences with sprintf, the (mostly)
textual rounding is done by textround():
Please excuse the rather grim formatting.
#!/usr/bin/perl -w
use strict;
my @v;
my $different=0;
while(!$different) {
print chr(27),"[2J",chr(27),"[H"; # vt100 clear screen sequence
for (0..20) {
$v[$_] = int(rand(10000))/(10**int(rand(5)+1));
}
foreach my $v (@v) {
my $v3=0+textround($v);
my $v2=0+sprintf("%.2f",$v);
print "$v\tText= ",$v3,"\tSprint= $v2\t",(($v2 ne
$v3)?'Different':''),"\n";
$different = ($different || ($v2 ne $v3)) ? 1:0;
}
}
exit;
sub textround {
my $v = shift;
if($v=~m/^\d*\.\d\d\d+$/) {
my ($prefix, $keydigit)=$v=~m/^(\d*\.\d\d)(\d)\d*/;
if($keydigit>=5) {
return($prefix+0.01);
}else{
return($prefix);
}
}else{
return $v;
if you need to round the result of a floating point calculation, with 0.5
always being rounded up - e.g. tax calculations etc.
- round your result to 4 sig figs, to avoid evil 0.49999999s
- multiply by 100 to put things into cents/pennies
- if ($result - int($result) >= 0.5) { $result = int($result+1)} else {$result
= int($result)}
- divide by 100 again
wfm
dave
On Fri, 15 Sep 2000, Chris Fedde wrote:
>In article <969030633...@news.uklinux.net>,
HEY, whats that? One-line blocks???
$result = int ($result) + (($result - int($result) >= 0.5) ? 1 : 0);
$result = int ($result) + ($result - int($result) >= 0.5);
$result = int ($result + 0.5)
Choose!
> - divide by 100 again
$result = int (100 * $result + 0.5001) / 100
should be enough... if you do not have to calculate with 0.0001 pence. If,
add as many 0s as long as it still worx. A standard function...