Discussion:
Rounding of floating point numbers
(too old to reply)
Robert Zielfelder
2004-02-24 15:01:35 UTC
Permalink
Greetings,

I am trying to figure out why the following doesn't work the way I would
expect it to:

$var1 = 1.5;
$var2 = sprintf(".0f", $var1);

$var2 ends up being rounded down to 1 instead of being rounded up to 2 which
is what I would expect to see. Does anyone know why this is happening,
and/or is there a way to get a result of 2 instead of 1?

Any help would be appreciated.

Rob Zielfelder
Tyco Electronics


.-. --..
Charles K. Clarkson
2004-02-24 15:27:13 UTC
Permalink
Zielfelder, Robert <***@tycoelectronics.com> wrote:
:
: I am trying to figure out why the following doesn't work the
: way I would expect it to:
:
: $var1 = 1.5;
: $var2 = sprintf(".0f", $var1);

I think you meant sprintf("%.0f", $var1). Note the added '%'.

printf '%.0f', 1.5; # works as you expect on perl 5.6.1 and up.

Perhaps there is something else is affecting the value of
$var2.


HTH,

Charles K. Clarkson
--
Head Bottle Washer,
Clarkson Energy Homes, Inc.
Mobile Home Specialists
254 968-8328
Robert Zielfelder
2004-02-24 15:30:30 UTC
Permalink
Actually, the % is there in my program, I forgot to type it in the
e-mail....With or without it, the result ends up being ".0f"....
I think I have 5.6.0, that might be the problem....




.-. --..

-----Original Message-----
From: Charles K. Clarkson [mailto:***@htcomp.net]
Sent: Tuesday, February 24, 2004 10:27 AM
To: Zielfelder, Robert; 'Perl Beginners List (E-mail)'
Subject: RE: Rounding of floating point numbers

Zielfelder, Robert <***@tycoelectronics.com> wrote:
:
: I am trying to figure out why the following doesn't work the
: way I would expect it to:
:
: $var1 = 1.5;
: $var2 = sprintf(".0f", $var1);

I think you meant sprintf("%.0f", $var1). Note the added '%'.

printf '%.0f', 1.5; # works as you expect on perl 5.6.1 and up.

Perhaps there is something else is affecting the value of
$var2.


HTH,

Charles K. Clarkson
--
Head Bottle Washer,
Clarkson Energy Homes, Inc.
Mobile Home Specialists
254 968-8328
Charles K. Clarkson
2004-02-24 15:51:05 UTC
Permalink
Zielfelder, Robert <***@tycoelectronics.com> wrote:
:
: Actually, the % is there in my program, I forgot to type it in the
: e-mail....With or without it, the result ends up being ".0f"....
: I think I have 5.6.0, that might be the problem....

You can test for your version with:

printf "perl version: %s\n", $];


I don't have a surviving copy of 5.6.0 for testing. I doubt
it was a problem in 5.6.0 since there is no bug fix mentioned
in perl561delta of a bug fix. Can you show the code you are
using for testing?


HTH,

Charles K. Clarkson
--
Mobile Homes Specialist
254 968-8328
Robert Zielfelder
2004-02-24 16:00:25 UTC
Permalink
#!/opt/perl5/bin/perl

$var = "1.5";
$var2 = sprintf("%.0f\n", $var);

print $var2;

$var3 = 1.5000001;
$var4 = sprintf("%.0f\n", $var3);

print $var4;


The first Print statement yields 1. The second yields 2.
I am using PERL Version 5.006 (result from command below) running under
HP-UX 10.20.




.-. --..

-----Original Message-----
From: Charles K. Clarkson [mailto:***@htcomp.net]
Sent: Tuesday, February 24, 2004 10:51 AM
To: Zielfelder, Robert; 'Perl Beginners List (E-mail)'
Subject: RE: Rounding of floating point numbers

Zielfelder, Robert <***@tycoelectronics.com> wrote:
:
: Actually, the % is there in my program, I forgot to type it in the
: e-mail....With or without it, the result ends up being ".0f"....
: I think I have 5.6.0, that might be the problem....

You can test for your version with:

printf "perl version: %s\n", $];


I don't have a surviving copy of 5.6.0 for testing. I doubt
it was a problem in 5.6.0 since there is no bug fix mentioned
in perl561delta of a bug fix. Can you show the code you are
using for testing?


HTH,

Charles K. Clarkson
--
Mobile Homes Specialist
254 968-8328
Charles K. Clarkson
2004-02-24 16:35:14 UTC
Permalink
Zielfelder, Robert <***@tycoelectronics.com> wrote:
:
: #!/opt/perl5/bin/perl
:
: $var = "1.5";
: $var2 = sprintf("%.0f\n", $var);
:
: print $var2;
:
: $var3 = 1.5000001;
: $var4 = sprintf("%.0f\n", $var3);
:
: print $var4;
:
:
: The first Print statement yields 1. The second yields 2.
: I am using PERL Version 5.006 (result from command below)
: running under HP-UX 10.20.

That's not what I'm getting in 5.6.1 and 5.8.3 on winXP,
you'll probably have to roll your own rounding function.

sprintf '%.*f', $precision, $value + .5 * 10 ** -$precision;

where $precision is the number of places after the decimal
you need for precision. In your case:

sprintf '%.*f', 0, $value + .5 * 10 ** -0;

or just:

sprintf '%.0f', $value + .5;


You would be safer with one of the Math:: modules.
Math::FixedPrecision or Math::Financial. I doubt my solution is
as flexible or as reliable.


Here is an untested sub if your not writing a precision
critical application, but it won't port easily.

print round( 1.4999 );

sub round {
# should work on all positive numbers
my $value = shift;

# default to 0 if precision omitted
my $precision = $_[0] || 0;

return sprintf '%.*f', $precision, $value + .5 * 10 ** -$precision;
}


HTH,

Charles K. Clarkson
--
Head Bottle Washer,
Clarkson Energy Homes, Inc.
Mobile Home Specialists
254 968-8328
Wc -Sx- Jones
2004-02-24 16:52:28 UTC
Permalink
Post by Robert Zielfelder
The first Print statement yields 1. The second yields 2.
I am using PERL Version 5.006 (result from command below) running under
HP-UX 10.20.
Interesting...

Is there an extra CPU parity bit on that system?

-Sx-
Wc -Sx- Jones
2004-02-24 16:49:09 UTC
Permalink
Post by Robert Zielfelder
$var1 = 1.5;
$var2 = sprintf(".0f", $var1);
What does this code tell you?

#!/usr/bin/perl

$var1 = 1.50000;
$var2 = sprintf("%.3f", $var1);

for ($x; $x < 5; $x++) {
printf("%.".$x."f %.".$x."f\n", $var1, $var2);
}

__END__
-Sx-
Rob Dixon
2004-02-24 17:09:50 UTC
Permalink
Post by Robert Zielfelder
I am trying to figure out why the following doesn't work the way I would
$var1 = 1.5;
$var2 = sprintf(".0f", $var1);
$var2 ends up being rounded down to 1 instead of being rounded up to 2 which
is what I would expect to see. Does anyone know why this is happening,
and/or is there a way to get a result of 2 instead of 1?
Hi Robert.

Please establish your Perl version number as Charles said:

printf "perl version: %s\n", $];

Thanks,

Rob
Robert Zielfelder
2004-02-24 17:21:26 UTC
Permalink
Charles,

I really appreciate your help, however I decided to use something I found in
the camel book:

Use POSIX;
$var = 1.5;
$var2 = $floor($var);
$var3 = $ceil($var);

print $var $var2 $var3;
The floor and ceil functions return the lower and upper round off values and
seem to work fine for what I am doing.
One other odd thing I noticed using the sprintf method:
If $var is odd, $var2 rounds low. If $var is even, then $var2 rounds high.
Being a programmer by necessity rather than by choice forces me to take the
easy way out and use the POSIX solution to get the program written.
Although, it would be interesting to know why the sprintf method behaves the
way it does...
Thanks again for the direction,
Rob




.-. --..

-----Original Message-----
From: Zielfelder, Robert
Sent: Tuesday, February 24, 2004 12:17 PM
To: 'Charles K. Clarkson'
Subject: RE: Rounding of floating point numbers

Charles,

I really appreciate your help, however I decided to use something I found in
the camel book:

Use POSIX;

$var = 1.5;
$var2 = $floor($var);
$var3 = $ceil($var);

print $var $var2 $var3;

The floor and ceil functions return the lower and upper round off values and
seem to work fine for what I am doing.

One other odd thing I noticed using the sprintf method:
If $var is odd, $var2 rounds low. If $var is even, then $var2 rounds high.

Being a programmer by necessity rather than by choice forces me to take the
easy way out and use the POSIX solution to get the program written.
Although, it would be interesting to know why the sprintf method behaves the
way it does...

Thanks again for the direction,

Rob



.-. --..

-----Original Message-----
From: Charles K. Clarkson [mailto:***@htcomp.net]
Sent: Tuesday, February 24, 2004 11:35 AM
To: Zielfelder, Robert; 'Perl Beginners List (E-mail)'
Subject: RE: Rounding of floating point numbers

Zielfelder, Robert <***@tycoelectronics.com> wrote:
:
: #!/opt/perl5/bin/perl
:
: $var = "1.5";
: $var2 = sprintf("%.0f\n", $var);
:
: print $var2;
:
: $var3 = 1.5000001;
: $var4 = sprintf("%.0f\n", $var3);
:
: print $var4;
:
:
: The first Print statement yields 1. The second yields 2.
: I am using PERL Version 5.006 (result from command below)
: running under HP-UX 10.20.

That's not what I'm getting in 5.6.1 and 5.8.3 on winXP,
you'll probably have to roll your own rounding function.

sprintf '%.*f', $precision, $value + .5 * 10 ** -$precision;

where $precision is the number of places after the decimal
you need for precision. In your case:

sprintf '%.*f', 0, $value + .5 * 10 ** -0;

or just:

sprintf '%.0f', $value + .5;


You would be safer with one of the Math:: modules.
Math::FixedPrecision or Math::Financial. I doubt my solution is
as flexible or as reliable.


Here is an untested sub if your not writing a precision
critical application, but it won't port easily.

print round( 1.4999 );

sub round {
# should work on all positive numbers
my $value = shift;

# default to 0 if precision omitted
my $precision = $_[0] || 0;

return sprintf '%.*f', $precision, $value + .5 * 10 ** -$precision;
}


HTH,

Charles K. Clarkson
--
Head Bottle Washer,
Clarkson Energy Homes, Inc.
Mobile Home Specialists
254 968-8328
Wc -Sx- Jones
2004-02-24 17:38:32 UTC
Permalink
Post by Robert Zielfelder
If $var is odd, $var2 rounds low. If $var is even, then $var2 rounds high.
Being a programmer by necessity rather than by choice forces me to take the
easy way out and use the POSIX solution to get the program written.
Although, it would be interesting to know why the sprintf method behaves the
way it does...
That could be caused by a parity correcting CPU which was NOT accounted
for during the build/compliation phase of Perl.

65 bits as opposed to 64 is meaningful.

I would still like to know what version of Perl you have as well.

:)
-Sx-
Zsdc
2004-02-24 19:29:28 UTC
Permalink
Post by Robert Zielfelder
If $var is odd, $var2 rounds low. If $var is even, then $var2 rounds high.
Being a programmer by necessity rather than by choice forces me to take the
easy way out and use the POSIX solution to get the program written.
Although, it would be interesting to know why the sprintf method behaves the
way it does...
Didn't you mean the other way around, i.e. rounding to the nearest even
integer, instead of odd as you wrote above?

It's a banker rounding, an old way of rounding numbers used even in
times when bankers were doing it manually. It's a way to ensure that
your results are not skewed in any particular direction, to minimize the
accumulation of rounding errors after adding numbers together.

When the fraction part is exactly 0.5, i.e. it's equally far from both
integers it is between, then it rounds to the even one. Why even and not
odd? Only because it's easier to divide by 2 without introducing more
errors.

Run this program:

#!/usr/bin/perl -w

for $f (.49, .50, .51) {
for $i (0..5) {
printf "%.2f -> %.0f\n", $i + $f, $i + $f;
}
}
__END__

It will print:

0.49 -> 0
1.49 -> 1
2.49 -> 2
3.49 -> 3
4.49 -> 4
5.49 -> 5
0.50 -> 0
1.50 -> 2
2.50 -> 2
3.50 -> 4
4.50 -> 4
5.50 -> 6
0.51 -> 1
1.51 -> 2
2.51 -> 3
3.51 -> 4
4.51 -> 5
5.51 -> 6

Numbers with .49 are always rounded down (like with floor) to get the
nearest integer, numbers with .51 are always rounded up (like with ceil)
-- still, no problem with finding the nearest integer -- but numbers
with .50 not having a nearest integer, are rounded to the nerest even
integer.

This not Perl-specific at all. This C program:

#include <stdio.h>

int main() {
float i, f;
for (f = 0.49; f <= 0.51; f += 0.01)
for (i = 0; i <= 5; i++)
printf("%.2f -> %.0f\n", i + f, i + f);
return 0;
}
/* END */

prints exactly the same output as the above Perl program.

I haven't read everything in this thread so I don't know what results
are you exactly expecting, but I think that you probably should use
Math:: modules, like Charles suggested.

Do you want numbers with decimal part .5 to always round up? Keep in
mind that it will skew your results. See this program:

#!/usr/bin/perl -w

use POSIX qw(floor ceil);

sub round1 { sprintf '%.0f', @_ }
sub round2 {
$x = shift;
$r = floor($x);
if ($x - $r >= 0.5) { $r = ceil($x) }
return $r;
}

for (1..10000) {
$x = 0.1 * int rand 100;
$sum += $x;
$rsum1 += round1($x);
$rsum2 += round2($x);
}

printf "Real:\t%.1f\n", $sum;
print "Banker:\t$rsum1\n";
print "5up:\t$rsum2\n";

__END__

The function round1() rounds just like printf and the function round2()
rounds .5 always up. The program adds random numbers between 0.0 and 9.9
and prints the real sum and the sums of numbers rounded with both
methods. See that the second one is always considerably larger. Every
rounded number is skewed up by 5% on average. This is exactly why we use
banker rounding.
--
ZSDC
Robert Zielfelder
2004-02-24 17:47:32 UTC
Permalink
In an earlier post, I mentioned the "PERL version" command returned "5.006"
Here are the results from the other bit of code you asked me to try. BTW,
I'm not getting your e-mails in the right order. I think our internal
e-mail server is having a hard time today...

0 1
0.0 1.5
0.00 1.50
0.000 1.500
0.0000 1.5000

I also believe the PA8500 is a 32 bit processor....



.-. --..

-----Original Message-----
From: WC -Sx- Jones
[mailto:***@insecurity.org]
Sent: Tuesday, February 24, 2004 12:39 PM
To: Zielfelder, Robert
Cc: Perl Beginners List (E-mail)
Subject: Re: Rounding of floating point numbers
Post by Robert Zielfelder
If $var is odd, $var2 rounds low. If $var is even, then $var2 rounds
high.
Post by Robert Zielfelder
Being a programmer by necessity rather than by choice forces me to take
the
Post by Robert Zielfelder
easy way out and use the POSIX solution to get the program written.
Although, it would be interesting to know why the sprintf method behaves
the
Post by Robert Zielfelder
way it does...
That could be caused by a parity correcting CPU which was NOT accounted
for during the build/compliation phase of Perl.

65 bits as opposed to 64 is meaningful.

I would still like to know what version of Perl you have as well.

:)
-Sx-
Wc -Sx- Jones
2004-02-24 17:56:06 UTC
Permalink
Post by Robert Zielfelder
In an earlier post, I mentioned the "PERL version" command returned "5.006"
Here are the results from the other bit of code you asked me to try. BTW,
I'm not getting your e-mails in the right order. I think our internal
e-mail server is having a hard time today...
0 1
0.0 1.5
0.00 1.50
0.000 1.500
0.0000 1.5000
I also believe the PA8500 is a 32 bit processor....
IIRC tat is Perl 5.6.0.

Then using ceil() as you mentioned in the other post is prolly your best
bet.

However - it is still strange that odd rounding and even rounding are
different. I *strongly* suggest that you upgrade to a more recent Perl
release soonest - if at all possible.

-Sx-
Zsdc
2004-02-24 19:35:59 UTC
Permalink
Post by Wc -Sx- Jones
However - it is still strange that odd rounding and even rounding are
different. I *strongly* suggest that you upgrade to a more recent Perl
release soonest - if at all possible.
Doesn't printf/sprintf in your version od Perl (or C for that matter)
use banker rounding by default? Could you please tell me what platform
and version of Perl as well as libc are you using? How does sprintf
round on your system?
--
ZSDC
Wc -Sx- Jones
2004-02-24 19:50:26 UTC
Permalink
Post by Zsdc
Post by Wc -Sx- Jones
However - it is still strange that odd rounding and even rounding are
different. I *strongly* suggest that you upgrade to a more recent
Perl release soonest - if at all possible.
Doesn't printf/sprintf in your version od Perl (or C for that matter)
use banker rounding by default? Could you please tell me what platform
and version of Perl as well as libc are you using? How does sprintf
round on your system?
I wasn't the orginal poster :)

My system is Solaris 5.8.0 Ultrasparc and it rounds correctly with all
tests I can think up

Including printing the expected results from your other post. Which I
did like the C POSIX tests as well.

The problem from the other poster is:

1) I have 1.5 which is 1 on my system AFTER I run the following code.
Q) What is it on yours after you run it?

$val = 1.5;
$result = sprintf("%.0f", $val);

# The error is that it is 1 on his =/


It is 2 on my system.

HTH
-Sx- :)
Zsdc
2004-02-24 20:08:55 UTC
Permalink
Post by Wc -Sx- Jones
Post by Zsdc
Post by Wc -Sx- Jones
However - it is still strange that odd rounding and even rounding are
different. I *strongly* suggest that you upgrade to a more recent
Perl release soonest - if at all possible.
Doesn't printf/sprintf in your version od Perl (or C for that matter)
use banker rounding by default? Could you please tell me what platform
and version of Perl as well as libc are you using? How does sprintf
round on your system?
I wasn't the orginal poster :)
Sorry, I only started to skim through this thread...
Post by Wc -Sx- Jones
My system is Solaris 5.8.0 Ultrasparc and it rounds correctly with all
tests I can think up
Including printing the expected results from your other post. Which I
did like the C POSIX tests as well.
Very good, because that's the IEEE standard way of rounding numbers.
--
ZSDC
Robert Zielfelder
2004-02-24 20:25:08 UTC
Permalink
I suspected that this was the intentional behavior of the function. For
what I am trying to do, I don't think "banker's rounding" will work. Not
being a "real" programmer, perhaps I am approaching my problem from the
wrong angle.

I am writing a script that works with a piece of CAM software to create a
drawn pattern. The total number of patterns I need will vary each time the
program is run, but the number will always be an integer. The trouble is
that I have to alternate the patterns from the left side of the design to
the right side of the design. When I have an even number of patterns, this
isn't a problem. $ patterns means two patterns on the left and two on the
right. The rule I must follow for odd numbers is to divide by two, round up
to the next highest number, and put the higher number of patterns on the
left. So for 5 patterns, I would have 3 on the left and two on the right.
What I am trying to do is to get the number of patterns, divide by two,
round up, then subtract this value from the total to obtain the right and
left numbers. So in my case I always want to round up. Unless there is a
better way to approach a problem like this....

Thanks for the help and the info. It helps a novice programmer such as
myself to understand things a bit better.




.-. --..

-----Original Message-----
From: zsdc [mailto:***@wp.pl]
Sent: Tuesday, February 24, 2004 2:29 PM
To: Zielfelder, Robert
Cc: Perl Beginners List (E-mail)
Subject: Re: Rounding of floating point numbers
Post by Robert Zielfelder
If $var is odd, $var2 rounds low. If $var is even, then $var2 rounds
high.
Post by Robert Zielfelder
Being a programmer by necessity rather than by choice forces me to take
the
Post by Robert Zielfelder
easy way out and use the POSIX solution to get the program written.
Although, it would be interesting to know why the sprintf method behaves
the
Post by Robert Zielfelder
way it does...
Didn't you mean the other way around, i.e. rounding to the nearest even
integer, instead of odd as you wrote above?

It's a banker rounding, an old way of rounding numbers used even in
times when bankers were doing it manually. It's a way to ensure that
your results are not skewed in any particular direction, to minimize the
accumulation of rounding errors after adding numbers together.

When the fraction part is exactly 0.5, i.e. it's equally far from both
integers it is between, then it rounds to the even one. Why even and not
odd? Only because it's easier to divide by 2 without introducing more
errors.

Run this program:

#!/usr/bin/perl -w

for $f (.49, .50, .51) {
for $i (0..5) {
printf "%.2f -> %.0f\n", $i + $f, $i + $f;
}
}
__END__

It will print:

0.49 -> 0
1.49 -> 1
2.49 -> 2
3.49 -> 3
4.49 -> 4
5.49 -> 5
0.50 -> 0
1.50 -> 2
2.50 -> 2
3.50 -> 4
4.50 -> 4
5.50 -> 6
0.51 -> 1
1.51 -> 2
2.51 -> 3
3.51 -> 4
4.51 -> 5
5.51 -> 6

Numbers with .49 are always rounded down (like with floor) to get the
nearest integer, numbers with .51 are always rounded up (like with ceil)
-- still, no problem with finding the nearest integer -- but numbers
with .50 not having a nearest integer, are rounded to the nerest even
integer.

This not Perl-specific at all. This C program:

#include <stdio.h>

int main() {
float i, f;
for (f = 0.49; f <= 0.51; f += 0.01)
for (i = 0; i <= 5; i++)
printf("%.2f -> %.0f\n", i + f, i + f);
return 0;
}
/* END */

prints exactly the same output as the above Perl program.

I haven't read everything in this thread so I don't know what results
are you exactly expecting, but I think that you probably should use
Math:: modules, like Charles suggested.

Do you want numbers with decimal part .5 to always round up? Keep in
mind that it will skew your results. See this program:

#!/usr/bin/perl -w

use POSIX qw(floor ceil);

sub round1 { sprintf '%.0f', @_ }
sub round2 {
$x = shift;
$r = floor($x);
if ($x - $r >= 0.5) { $r = ceil($x) }
return $r;
}

for (1..10000) {
$x = 0.1 * int rand 100;
$sum += $x;
$rsum1 += round1($x);
$rsum2 += round2($x);
}

printf "Real:\t%.1f\n", $sum;
print "Banker:\t$rsum1\n";
print "5up:\t$rsum2\n";

__END__

The function round1() rounds just like printf and the function round2()
rounds .5 always up. The program adds random numbers between 0.0 and 9.9
and prints the real sum and the sums of numbers rounded with both
methods. See that the second one is always considerably larger. Every
rounded number is skewed up by 5% on average. This is exactly why we use
banker rounding.
--
ZSDC
Zsdc
2004-02-25 10:59:04 UTC
Permalink
Post by Robert Zielfelder
I suspected that this was the intentional behavior of the function. For
what I am trying to do, I don't think "banker's rounding" will work. Not
being a "real" programmer, perhaps I am approaching my problem from the
wrong angle. [...]
From what you've described, using POSIX::floor or POSIX::ceil seems to
be exactly the right solution. Now I can see why the banker's rounding
was unacceptable in this case.
Post by Robert Zielfelder
Thanks for the help and the info. It helps a novice programmer such as
myself to understand things a bit better.
I'm glad you found it useful.
--
ZSDC Perl Consulting
Loading...