oakbox has asked for the wisdom of the Perl Monks concerning the following question:
I'm a little confused. Okay, I know that int does not round a number, it just lops off everything after the decimal. So, after reading some other posts here, I go with sprintf.
Specifically, I need an integer and I want to correctly lop everything off after the decimal. Here's my formula:
$rounded=sprintf("%.0f",$unrounded);
This works for most cases, but I'm getting some funny output from my script when the decimal is .5. Here is the verbose output from my script where it performs a calculation, tells me the unrounded number, and then tells me what the rounded number is: (script output)
SO the equation to figure out sub trait A1 looks like this 7 + (2 - (0.5 * 9)) and comes up as 4.5 that is rounded to 4 SO the equation to figure out sub trait A2 looks like this 7 + (2 - (0.5 * 9)) and comes up as 4.5 that is rounded to 4 SO the equation to figure out sub trait A3 looks like this 7 + (5 - (0.5 * 9)) and comes up as 7.5 that is rounded to 8
So, my question is, why did it round DOWN for two cases, and round UP for one case?
-oakbox
Re: Rounding With sprintf anomaly?
by dash2 (Hermit) on Feb 19, 2002 at 12:28 UTC
|
It's not an anomaly.
Perl rounds scientifically. This is not the same way of rounding as accountants use. Accountants always round .5 up. That's probably what you were taught at school. It's predictable and good.
Scientists don't always round .5 up. .5 is exactly half way between two numbers. If they always rounded up, you would get statistical bias in large collections of rounded data. .5 is rounded sometimes up and sometimes down. (I think the rule is something like even numbers (2.5, 4.5, 6.5 etc.) round down while odd numbers (1.5, 3.5 etc.) round up.) This method is scientifically correct, and good.
Of course, there are also problems with INTs and FLOATs. For accounting correctness, one thing you can do is cheat:
# untested
if ($unrounded =~ /\.(\d)/ and $1 > 4) {$rounded = int ($unrounded + 1
+)}
else {$rounded = int $unrounded}
dave hj~ | [reply] [d/l] |
|
In base 10, or any base equal to 2 modulo 4, round to even is preferred.
In a base equal to 0 modulo 4, round to odd would be preferred.
| [reply] |
Re: Rounding With sprintf anomaly?
by guha (Priest) on Feb 19, 2002 at 09:28 UTC
|
What HW/OS/Perl version are you on ?
On my Activestate 631 both variations in your example is rounded up.
What I'm thinking of is that a floating point value not always kan be represented exactly and that your first and second case might be represented as 4.499999999999999..... in your box, which would explain the rounddown.
You could look into the Posix routines for a possibly alternate route to achieve your aim.
--- I would like to change the world but God won't let me have the source code.
| [reply] |
|
Intel chip, Linux (cobalt's hacked Redhat version), and Perl 5.6.1. POSIX 'floor' and 'ceiling' are mentioned in a couple of posts elsewhere on perlmonks. I'll look into those.
I think the suggestion that the 5 isn't really a '5' holds the most water. There are actually about 28 separate calculations being performed in that section and it is only in THAT PARTICULAR section that the round doesn't perform as expected.
Okay, I'm happy again. I just wanted an explanation for the (apparently) freaky behaviour.
-oakbox
| [reply] |
|
I think if you want a proper rounding, you do this:
$x=int($num+0.5);
This gets rid of all those oddities, like a number looking like 5.5, but actually being 4.9999999....
Sorry, typo, should be 5.499999...
| [reply] [d/l] |
(crazyinsomniac) Re: Rounding With sprintf anomaly?
by crazyinsomniac (Prior) on Feb 19, 2002 at 09:42 UTC
|
I dunno what to tell you, except you're dealing with floats... deal with ints instead ... (I have no insight, only code, well a little insight - perl has internal ways of distinguishing numbers from strings .... (s)printf are c functions, do stuff similar to (un)pack in fsking with the numbers ... that kinda it, vague ain't it )
#!/usr/bin/perl
my $one = 7 + (2 - (0.5 * 9));
my $two = 7 + (2 - (0.5 * 9));
my $thr = 7 + (5 - (0.5 * 9));
for($one,$two,$thr) {
printf "no_ROUND(%s) yes_ROUND(%d)
foy_FLOAT(%f) foy_ROUND(%.0f)\n\n",
$_,$_,$_,$_;
=head2 UPDATE: uncomment this to add to the mistery
print "pack i ".unpack('i*', pack('i*', $_))."\n";
print "pack l ".unpack('l*', pack('l*', $_))."\n";
print "pack f ".unpack('f*', pack('f*', $_))."\n";
print "pack d ".unpack('d*', pack('d*', $_))."\n";
=cut and don't forget cut#
}
__END__
=pod
C:\>perl round.txt
no_ROUND(4.5) yes_ROUND(4)
foy_FLOAT(4.500000) foy_ROUND(5)
no_ROUND(4.5) yes_ROUND(4)
foy_FLOAT(4.500000) foy_ROUND(5)
no_ROUND(7.5) yes_ROUND(7)
foy_FLOAT(7.500000) foy_ROUND(8)
=head1 that is an interesting way to get an int
%% a percent sign
%c a character with the given number
%s a string
%d a signed integer, in decimal
%u an unsigned integer, in decimal
%o an unsigned integer, in octal
%x an unsigned integer, in hexadecimal
%e a floating-point number, in scientific notation
%f a floating-point number, in fixed decimal notation
%g a floating-point number, in %e or %f notation
=cut
update: guha brings up a good point, ints/floats will still vary from system 2 system
| [reply] [d/l] |
Re: Rounding With sprintf anomaly?
by jujubee (Acolyte) on Feb 19, 2002 at 18:47 UTC
|
the latest math::round's nearest function does check for big-endian or little-endian then does the right thing. Otherwise, a fix in another module uses 0.500000001 to do rounding. Of course this also exists in javascript where the simple solution is to use 0.500000001. | [reply] |
|
|