Rounding a float in Perl?

J

jon rogers

Hi

Is there any good way to round a float into n decimals in Perl?

I'd like to see

round($float,5); # rounds $float to (at most) 5 decimal digits

which would turn
1.234446732653623
into
1.23445

(or some equivalent functionality)?

Thanks for your time,

JR
 
A

Anno Siegel

Bernard El-Hagin said:
jon rogers said:
Hi

Is there any good way to round a float into n decimals in Perl?

I'd like to see

round($float,5); # rounds $float to (at most) 5 decimal digits

[...]


perldoc -q round

The FAQ answer is "use sprintf()", which is fine in most cases. It
must be said, however, that sprintf() is a slow function, and if a
lot of rounding is going on it can easily dominate the calculation.
Even a pure Perl rounding function, along the lines of

sub round {
my $x = shift;
my $y = 0.5 + abs $x;
my $abs = int $y;
$abs -= $abs % 2 if $y == $abs;
($x <=> 0) * $abs;
}

is twice as fast, and a compiled rounding function can be ten times
as fast.

Anno
 
B

Bart Lateur

jon said:
Is there any good way to round a float into n decimals in Perl?

I'd like to see

round($float,5); # rounds $float to (at most) 5 decimal digits

which would turn
1.234446732653623
into
1.23445

sprintf

$float = 1.234446732653623;
$rounded = sprintf "%.5f", $float;
print $rounded;

However, that doesn't remove unnecessary trailing zeroes, or the decimal
point for integers.
 
P

Philip Newton

The FAQ answer is "use sprintf()", which is fine in most cases. It
must be said, however, that sprintf() is a slow function, and if a
lot of rounding is going on it can easily dominate the calculation.
Even a pure Perl rounding function, along the lines of

sub round {
my $x = shift;
my $y = 0.5 + abs $x;
my $abs = int $y;
$abs -= $abs % 2 if $y == $abs;
($x <=> 0) * $abs;
}

is twice as fast, and a compiled rounding function can be ten times
as fast.

But that doesn't allow you to specify the number of decimal places to
round to, does it?

And I wonder whether, once you add the scaling necessary to support
that, it's still faster than sprintf.

Cheers,
Philip
 
A

Anno Siegel

Philip Newton said:
But that doesn't allow you to specify the number of decimal places to
round to, does it?

No, it doesn't. I don't remember the last time I wanted to round to
anything but the nearest integer, but the objection is valid.
And I wonder whether, once you add the scaling necessary to support
that, it's still faster than sprintf.

It (i.e. my implementation on my machine) is still 44% faster than sprintf,
as opposed to 113% for the non-scaling version. Then again, it can also
round 1234 to 1000 for a negative "number of decimal places", something
sprintf doesn't do.

But we're approaching bean-counting territory here...

Anno
 
B

Bart Lateur

Anno said:
No, it doesn't. I don't remember the last time I wanted to round to
anything but the nearest integer, but the objection is valid.

Think "currency".
 
A

Anno Siegel

Bart Lateur said:
Think "currency".

Oh, sure, it happens. Though, particularly with financial calculations,
an accepted technique is to first convert everything to cents (or whatever).

Anno
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
Anno Siegel
It (i.e. my implementation on my machine) is still 44% faster than sprintf,
as opposed to 113% for the non-scaling version. Then again, it can also
round 1234 to 1000 for a negative "number of decimal places", something
sprintf doesn't do.

But we're approaching bean-counting territory here...

On my machine (EMX on 850MHz Athlon) your version takes 5.03 us per
iteration (when non-scaling). sprintf takes 0.15 us per iteration.
Apparently your CRT implementation is completely broken speedwise...
Enough said.

Hope this helps,
Ilya
 
A

Anno Siegel

Ilya Zakharevich said:
[A complimentary Cc of this posting was sent to
Anno Siegel
It (i.e. my implementation on my machine) is still 44% faster than sprintf,
as opposed to 113% for the non-scaling version. Then again, it can also
round 1234 to 1000 for a negative "number of decimal places", something
sprintf doesn't do.

But we're approaching bean-counting territory here...

On my machine (EMX on 850MHz Athlon) your version takes 5.03 us per
iteration (when non-scaling). sprintf takes 0.15 us per iteration.
Apparently your CRT implementation is completely broken speedwise...
Enough said.

If so, that appears to be the case on more than one machine. I'm
getting consistent results (i.e. Perl rounding beats sprintf rounding)
on several machines. I'm appending the benchmarks i used for reference.

Anno


#!/usr/local/bin/perl
use strict; use warnings; $| = 1;
use Benchmark qw( :all);

goto bench;

for ( -10 .. 10 ) {
my $x = $_ * 0.1;
my $sp = sround( $x);
my $nin = nround( $x);
my $iin = cround( $x);
print "$x -> $sp, $nin, $iin\n";
}
exit;

bench:

cmpthese( -5, {
sround => 'sround( rand( 100))',
nround => 'nround( rand( 100))',
iround => 'iround( rand( 100))',
});

###################################################################

# Perl, scaling
sub nround {
my ( $x, $n) = @_;
my $pow10 = 10**($n || 0);
$x *= $pow10;
my $y = 0.5 + abs $x;
my $abs = int $y;
$abs -= $abs % 2 if $y == $abs;
( $x <=> 0) * $abs/$pow10;
}

# Perl, non-scaling
sub iround {
my $x = shift;
my $y = 0.5 + abs $x;
my $abs = int $y;
$abs -= $abs % 2 if $y == $abs;
( $x <=> 0) * $abs;
}

# sprintf
sub sround {
my $x = shift;
0 + sprintf '%.0f', $x;
}
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
Anno Siegel
If so, that appears to be the case on more than one machine. I'm
getting consistent results (i.e. Perl rounding beats sprintf rounding)
on several machines. I'm appending the benchmarks i used for reference.

My bad. My test had sprintf() constant-folded. After modifying it, I
get results similar to what you describe.

Which means: it makes sense to special-case several formats before
they are handled to

perl -V:d_Gconvert

Any takers?

Ilya
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
Anno Siegel
The FAQ answer is "use sprintf()", which is fine in most cases. It
must be said, however, that sprintf() is a slow function, and if a
lot of rounding is going on it can easily dominate the calculation.
Even a pure Perl rounding function, along the lines of

sub round {
my $x = shift;
my $y = 0.5 + abs $x;
my $abs = int $y;
$abs -= $abs % 2 if $y == $abs;
($x <=> 0) * $abs;
}

Today I sent a patch which sped up sprint "%.0f" 15x.

Hope this slightly compensates my goof with having a constant-folded
benchmark ;-),
Ilya
 
A

Anno Siegel

Ilya Zakharevich said:
[A complimentary Cc of this posting was sent to
Anno Siegel
The FAQ answer is "use sprintf()", which is fine in most cases. It
must be said, however, that sprintf() is a slow function, and if a
lot of rounding is going on it can easily dominate the calculation.
Even a pure Perl rounding function, along the lines of

sub round {
my $x = shift;
my $y = 0.5 + abs $x;
my $abs = int $y;
$abs -= $abs % 2 if $y == $abs;
($x <=> 0) * $abs;
}

Today I sent a patch which sped up sprint "%.0f" 15x.

Ah... that's about as fast as it gets. Now we can promote the FAQ answer
to rounding in good conscience.
Hope this slightly compensates my goof with having a constant-folded
benchmark ;-),

Well, it happens. When Perl 5 was new, I presented a benchmark to p5p,
proving that an extensive patch to the % operator didn't cost any time
at all. Both sides of the benchmark where constant-folded. Larry had
to set me straight.

Anno
 
R

Roy Johnson

(e-mail address removed)-berlin.de (Anno Siegel) wrote in message Your rounding is faster, but it doesn't quite work, in the sense of
always getting the same result as sprintf *or* the mathematically
accurate result.

nround(0.05, 1) yields 0, whereas sprintf('%.1f', 0.05) yields 0.1. Of
course, sprintf('%.1f', 3.05) yields 3.0, and so does Nround.

Here's one that works (in the sense of being mathematically accurate)
for all the values I've tried, but it is only about half as fast as
nround, or 3/4 as fast as sprintf.

sub stround {
my ($n, $places) = (@_,0);
my $sign = ($n < 0) ? '-' : '';
my $abs = abs($n);
$sign . substr($abs+('0.'.'0'x$places.'5'),
0, $places+length(int($abs))+1);
}
 
A

Anno Siegel

Roy Johnson said:
(e-mail address removed)-berlin.de (Anno Siegel) wrote in message
Your rounding is faster, but it doesn't quite work, in the sense of
always getting the same result as sprintf *or* the mathematically
accurate result.

What is the mathematically accurate result of rounding 0.05 to one
decimal place? The problem is, there are two equally likely candidates,
and mathematics doesn't presume to make a decision. Mathematically,
the result isn't defined.
nround(0.05, 1) yields 0, whereas sprintf('%.1f', 0.05) yields 0.1. Of
course, sprintf('%.1f', 3.05) yields 3.0, and so does Nround.

Algorithms have to make a decision, and standards define how to make
it. I am a little surprised at what IEEE does here (assuming it *is*
IEEE), or rather, I would be surprised if I hadn't long ago given up
being surprised by numeric results.
Here's one that works (in the sense of being mathematically accurate)
for all the values I've tried, but it is only about half as fast as
nround, or 3/4 as fast as sprintf.

Again, there is no "mathematically accurate" solution in the critical
case (where both possible values are equally far off). However, your
solution now differs from sprintf when rounding 0.5 to an integer:
sprintf says 0, stround says 1.

The point of speed is probably moot now. The next Perl will include
Ilya's patch, if the creeks don't rise, and that'll be it for Perl
implementations of rounding.
sub stround {
my ($n, $places) = (@_,0);
my $sign = ($n < 0) ? '-' : '';
my $abs = abs($n);
$sign . substr($abs+('0.'.'0'x$places.'5'),
0, $places+length(int($abs))+1);
}

Hmm... I can't say that I like the mixture of arithmetic and text-
processing, though it may be said to be typical of Perl. If I had to
*prove* an implementation conforms to some standard, I'd prefer a numeric
solution, if possible one that corresponds step by step to the description
in the standard.

Anno
 
R

Roy Johnson

What is the mathematically accurate result of rounding 0.05 to one
decimal place?

Convention says it rounds up. Maybe that's not mathematics, per se.
Maybe it's not even a universal convention, but it's the only one I
ever heard.
However, your solution now differs from sprintf when rounding 0.5 to
an integer: sprintf says 0, stround says 1.

I'm actually happy with that, because it's consistent and
conventional. sprintf's inconsistent rounding is lamented in the FAQ.
If I had to *prove* an implementation conforms to some standard, I'd
prefer a numeric solution

Except for the fact that numeric representation is the problem. Using
the strings is the workaround. As an internal number, there may be no
such thing as exactly 3.005, but as a string, there is.
 
C

ctcgag

(e-mail address removed)-berlin.de (Anno Siegel) wrote in message


Convention says it rounds up. Maybe that's not mathematics, per se.
Maybe it's not even a universal convention, but it's the only one I
ever heard.

Another popular (and better) convention is to round up if the preceding
digit is odd and down if the preceding digit is even, i.e. 0.05 -> 0.0,
while 0.15 -> 0.2.

Xho
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
Anno Siegel
Well, it happens.

The real problem is that I know that it happens, so this is the first
thing I check when *somebody else* posts a benchmark. Sigh....

Ilya
 
A

Anno Siegel

Roy Johnson said:
(e-mail address removed)-berlin.de (Anno Siegel) wrote in message


Convention says it rounds up. Maybe that's not mathematics, per se.
Maybe it's not even a universal convention, but it's the only one I
ever heard.

The convention has the disadvantage that it's biased. Positive numbers
will on average become larger and negatives become smaller.
I'm actually happy with that, because it's consistent and
conventional. sprintf's inconsistent rounding is lamented in the FAQ.

It may look inconsistent, but there are reasons for that. Compilers
of mathematical tables have used it for centuries.
Except for the fact that numeric representation is the problem. Using
the strings is the workaround. As an internal number, there may be no
such thing as exactly 3.005, but as a string, there is.

Numeric representation is a problem, but the purpose of rounding isn't
to show you the string you expect from looking at the (already rounded)
standard representation of the number. It must come up with a repre-
sentable approximation to the true rounded value.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,905
Latest member
Kristy_Poole

Latest Threads

Top