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

sprintf() rounding problem

465 views
Skip to first unread message

David Hugh-Jones

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to
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.

Thanks very much. Replies to dav...@mail.com will be gratefully accepted!

Dave


Elisa Roselli

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to

David Hugh-Jones a écrit dans le message <39c08734$1...@news.telinco.net>...

>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

David Hugh-Jones

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to

I have been told in no uncertain terms that this has been covered in the FAQ
and previous posts. I apologize for not checking this before. Mea culpa. I have
now read the 114 posts I could find on deja, and the FAQ on sprintf and round.

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.
>

Gwyn Judd

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to
I was shocked! How could David Hugh-Jones <dav...@mail.com>
say such a terrible thing:

>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)

David Hugh-Jones

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to Gwyn Judd

Hi Gwyn,

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

mexicanm...@my-deja.com

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to
In article <968949519....@news.uklinux.net>,
David Hugh-Jones <dav...@mail.com> wrote:
>
<shockingly inept de-jeopardification>

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.

Larry Rosler

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to
In article <968934567...@news.uklinux.net> on Thu, 14 Sep 2000
13:15:42 +0100, David Hugh-Jones <dav...@mail.com> says...

...

> 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

Russ Jones

unread,
Sep 14, 2000, 3:00:00 AM9/14/00
to
Larry Rosler wrote:
>
> 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.
>

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

Abigail

unread,
Sep 14, 2000, 10:41:10 PM9/14/00
to
David Hugh-Jones (dav...@mail.com) wrote on MMDLXXI September MCMXCIII
in <URL:news:968934567...@news.uklinux.net>:
''
'' 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 everythin
'' 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 calculat
'' 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?

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.

Abigail

unread,
Sep 14, 2000, 10:43:34 PM9/14/00
to
Larry Rosler (l...@hpl.hp.com) wrote on MMDLXXI September MCMXCIII in
<URL:news:MPG.142ac95c2...@nntp.hpl.hp.com>:
//
// 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.


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";

Bart Lateur

unread,
Sep 15, 2000, 3:00:00 AM9/15/00
to
David Hugh-Jones wrote:

>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.

David Hugh-Jones

unread,
Sep 15, 2000, 3:00:00 AM9/15/00
to
On Fri, 15 Sep 2000, Abigail wrote:

>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

David Hugh-Jones

unread,
Sep 15, 2000, 3:00:00 AM9/15/00
to
On Thu, 14 Sep 2000, mexicanm...@my-deja.com wrote:

>
>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

Abigail

unread,
Sep 15, 2000, 3:00:00 AM9/15/00
to
David Hugh-Jones (dav...@mail.com) wrote on MMDLXXII September MCMXCIII
in <URL:news:969030633...@news.uklinux.net>:
`'
`' 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 poin
`' results even with integer input.


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.

Chris Fedde

unread,
Sep 15, 2000, 3:00:00 AM9/15/00
to
In article <969030633...@news.uklinux.net>,

David Hugh-Jones <dav...@mail.com> wrote:
>
>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

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

Mike Stok

unread,
Sep 16, 2000, 3:00:00 AM9/16/00
to
In article <slrn8s4s7k....@alexandra.foad.org>,
Abigail <abi...@foad.org> wrote:

>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

Ilmari Karonen

unread,
Sep 16, 2000, 3:00:00 AM9/16/00
to
In article <jjk3sss9589r5tc7v...@4ax.com>, Bart Lateur wrote:

>David Hugh-Jones wrote:
>>
>>$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.

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


mexicanm...@my-deja.com

unread,
Sep 18, 2000, 3:00:00 AM9/18/00
to
In article <969038332....@news.uklinux.net>,

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.

Frode Bjerkholt

unread,
Sep 18, 2000, 3:00:00 AM9/18/00
to
I'm not sure if I have completly understood the problem...but this might be
a solution:

$rounded_up = int(int($value * 2.0 + 1.0) / 2);


<mexicanm...@my-deja.com> wrote in message
news:8q4mm4$roj$1...@nnrp1.deja.com...

Elisa Roselli

unread,
Sep 18, 2000, 3:00:00 AM9/18/00
to

mexicanm...@my-deja.com a écrit dans le message
<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

mexicanm...@my-deja.com

unread,
Sep 18, 2000, 3:00:00 AM9/18/00
to
In article <8q517h$9d0$1...@wanadoo.fr>,

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;

David Hugh-Jones

unread,
Sep 19, 2000, 3:00:00 AM9/19/00
to

Thanks to all who answered this: for future reference for anyone with my
problem:

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>,

Seiberdragan

unread,
Sep 22, 2000, 3:00:00 AM9/22/00
to

"David Hugh-Jones" <dav...@mail.com> schrieb im Newsbeitrag
news:969375053...@news.uklinux.net...

>
> Thanks to all who answered this: for future reference for anyone with my
> problem:
>
> 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)}

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...

0 new messages