vindaloo has asked for the wisdom of the Perl Monks concerning the following question:

I want to round off all the values in a given hash to 2 decimal places. I do not want to mess with the original, given hash. This is what I came up with:

use strict; use warnings; use diagnostics; use Data::Dumper::Simple; my %stats = ( 'stdev' => '42.5371995604396', 'pct_10' => 0, 'variance' => '668.660986666667', 'sterr' => '17.3657390016697', 'median' => '-130.925', 'pct_90' => '94.4', 'average' => '131.503333333333' ); print Dumper(%stats); my %rounded_stats = (); foreach my $stat (keys %stats){ #print "$stat $stats{$stat}\n"; my $round = sprintf("%.2f", $stats{$stat}); $rounded_stats{$stat} = $round; } print Dumper(%stats); print Dumper(%rounded_stats);

Is this the best way? Any advice or tips?

Replies are listed 'Best First'.
Re: rounding off all members of a hash
by Zaxo (Archbishop) on May 04, 2005 at 03:51 UTC

    That way is fine. I might have mapped instead:

    my %rounded_stats = map { $_ => sprintf '%.2f', $stats{$_} } keys %stats;
    That eliminates some temporary lexical variables which aren't really needed. (They could be eliminated in the explicit loop, too.)

    After Compline,
    Zaxo

      Another way to do the actual rounding is to use Math::Round. The nearest() function can round to a whatever acuracy you want. This does the same as the sprintf() version, except that the results are numbers, so you don't get trailing '0' characters in the output:
      my %rounded_stats = map { $_ => nearest(0.01, $stats{$_}) } keys %stats;
      It seemed long. I need to think about using map more. thanks
Re: rounding off all members of a hash (for values)
by tye (Sage) on May 04, 2005 at 05:09 UTC
    $_= sprintf "%.2f", $_ for values( my %rounded= %stats );

    But that won't work in old Perls where values doesn't return a list of aliases.

    - tye        

Re: rounding off all members of a hash
by eibwen (Friar) on May 04, 2005 at 05:03 UTC

    While I must second the use of map and sprintf, you may be interested to know that rounding constitutes several Categorized Questions and Answers, is found in perlfaq4, and a fairly common question. In the interest of completness (and TimToady), there are other methods to round numbers as shown in a variety of threads (eg Re: Rounding function and Re^2: Splitting String???), which include the following:

    int ($num + .5); ($num += 5 / (10 ** ($decimal_places + 1))) =~ s/(?<=\.)(\d{1,$decimal +_places}).*$/$1/;

    In addition to Zaxo's use of map, you may also be interested in using $, (or $" if interpolated; see perlvar), and printing directly, eg:

    $, = $/; print map { sprintf("%.2f", $_) } values %stats;

Re: rounding off all members of a hash
by Animator (Hermit) on May 04, 2005 at 08:39 UTC

    Here's another idea: use tie.

    To explain myself some more:

    Create a module, in there you create a hash, and make the tie method accept a param called 'rounded', which has a true/false value.

    Then tie it to two hashes, in your example, you tie the hash %stats to the module, with the rounded param set to false, and tie the hash %rounded_stats to the same module, but with the rounded param set to true.

    Then implement the FETCH, and VALUES method to check the value of the rounded-param...

    This would have the advantage that your data is only stored once (this also means it is in sync at all time), but it would also mean that fetching a value might be a bit slower/less efficient (since it has to do the sprintf)...

    I can't tell what would be best, creating a real hash vs tieing it to two hashes, since that really depends on how you are planning on using it...

    (References: `perldoc -f tie`, `perldoc perltie`, and `perldoc Tie::Hash`)

      Animator's text gave me inspiration to go create a tied hash module just for the learning experience.
      Included below.