Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Rounding to a Given Number of Significant Figures Rather Than Decimal Places

by Clem (Sexton)
on Jan 13, 2003 at 13:32 UTC ( [id://226449]=perlquestion: print w/replies, xml ) Need Help??

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

I am very new to Perl and very new to programming in general and would therefore be grateful for very, very simple answers. How do I use Perl to round a number to a given number of significant figures rather than to a given number of decimal places? Thank you for any help.
  • Comment on Rounding to a Given Number of Significant Figures Rather Than Decimal Places

Replies are listed 'Best First'.
Re: Rounding to a Given Number of Significant Figures Rather Than Decimal Places
by davis (Vicar) on Jan 13, 2003 at 13:58 UTC
    Hi, welcome to Perl, and welcome to PerlMonks
    If you haven't yet heard of it, now's the time to check out the CPAN, where you can get pre-built and pre-tested1 modules to do common tasks. A quick trawl through the Math:: namespace revealed the Math::SigFigs module, which, once installed, can be used like this:
    #!/usr/bin/perl use warnings; use strict; use Math::SigFigs; my $num = 123.456; print FormatSigFigs($num,1), "\n";

    1. Don't underestimate the importance of the tested status. The modules will (usually) have been used by many people, on many platforms, and problems will hopefully have been reported back to the module author and fixed. You'll hear many people saying "Don't re-invent the wheel"; testing is just one reason why not.

    Is this going out live?
    No, Homer, very few cartoons are broadcast live - it's a terrible strain on the animator's wrist
      Thank you very much for your advice. I have tried your suggestion and it does indeed work very well. However, it has made me realise that my question was ill considered. My problem is actually as follows: I have an 8 character wide field into which I must round a number (possibly with a decimal point, possibly not). I need to be able to say, "Take this number, and round it so that, including it's decimal point, if it has one, and any zeros, is only 8 characters long". I have tried using $num = sprintf "%8g", $num; but this seems to give me a 7 character long number with a space at the front. Thank you once again for your advice and any further help you can offer.
        $num = sprintf "%8g", $num;
        Almost. shell$> perldoc -f sprintf reveals this little snippet:
        # Format number with up to 8 leading zeroes $result = sprintf("%08d", $number);
        Change the "d" to a "g" (which you had), and you'll be there. You'll probably find you want to do the SigFig rounding first, then format it for display with the sprintf.
        cheers
        davis
        Is this going out live?
        No, Homer, very few cartoons are broadcast live - it's a terrible strain on the animator's wrist

        In that case, to a first approximation you could do something like the following:

        #! /usr/bin/perl -w use strict; while( <DATA> ) { chomp; my $nr = squeeze($_); print "[$nr] $_\n"; } sub squeeze { my $n = sprintf( '%0.8f', $_[0] ); $n =~ s/^0//; substr( $n, 0, 8 ); } __DATA__ 12345678901234567890 1234567890 1234567.0 1234567.9 123456.789012345 12345 123.4567 123.4 .1 .1234567890 0.00000999999

        Then again, the few test cases here reveal a certain number of bugs. I contend that the results to squeeze() contain a bug, in that 1234567.0 and 1234567.9 return the same results, but in truth the latter should return 1234568. I don't know whether you will hit these borderline cases or not.

        Maybe there's yet another module that deals with your problem.

        <update>In response to gjb's remark about whether this code is a good idea or not, I was trying to point out the folly of storing numbers in fixed-width fields. The idea being that when you see what this outputs, maybe you better start thinking about overflow conditions. As I don't know the domain. I can't really deal with in a satisfactory matter (at least to my standards). I prefer to let the code stand as it is.</update>


        print@_{sort keys %_},$/if%_=split//,'= & *a?b:e\f/h^h!j+n,o@o;r$s-t%t#u'
        Heh. Try doing something like:
        my $num = 12345.678; # a total of 9 characters if (length $num > 8) { if ($num =~ /\./) # If there's a decimal point { # Put it to seven significant figures } else { # Put it to eight significant figures } }
        Another way to test for the decimal point, if you wanted to be purely math-based, could be if ($num == round($num)).

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Rounding to a Given Number of Significant Figures Rather Than Decimal Places
by gjb (Vicar) on Jan 13, 2003 at 13:57 UTC

    The number of significant digits depends on the calculations you're doing with a set of input numbers. Essentially you'll have to keep track of the errors (relative and/or absolute) that accumulate during a calculation.

    Two simple examples:

    • 5.02 (+/- 0.01) + 3.1 (+/- 0.1) = 5.1 (+/- 0.2)
    • 5.02 (+/- 0.01, 0.2%) * 3.1 (+/- 0.1, 4%) = 15.6 (+/- 0.8, 5%)
    where the (+/- 0.01) is the absolute error and the 0.5% the relative error (expressed in %). Note that the sum is given as 5.1 rather than 5.12 since with an error of 0.2 there's no point showing that many decimals (they're not reliable). Similar for the product that is 15.562, but again, the last two decimals can't be trusted given an error margin of 5%.
    • The absolute error of the sum of two numbers is the sum of the absolute errors of the terms.
    • The relative error of the product of two numbers is the sum of the relative errors of the factors.
    These two simple rules allow a complete analysis for all simple cases. Note that this implies that errors propagate, i.e. the number of significant digits can never increase. For cases involving mathematical functions such as the sqrt or trigoniometic functions, the platform specific docs should be consulted (or the appropriate IEEE specs on numbers).

    You'll find a treatment of these concepts in any good book on numerical methods, Numerical Recipes in C is available online and not too bad. (Specifically, check out this chapter

    Hope this helps, -gjb-

    Update: Excellent pointer by davis in the node below.

Re: Rounding to a Given Number of Significant Figures Rather Than Decimal Places
by AcidHawk (Vicar) on Jan 13, 2003 at 13:45 UTC

    Have a look at Does Perl Have A round() function? in the Perl FAQ Number 4 perlfaq4

    E.G. The POSIX module (part of the standard Perl distribution) implements ceil(), floor(), and a number of other mathematical and trigonometric functions.

    use POSIX; $ceil = ceil(3.5); # 4 $floor = floor(3.5); # 3

    -----
    Of all the things I've lost in my life, its my mind I miss the most.
Re: Rounding to a Given Number of Significant Figures Rather Than Decimal Places
by aging acolyte (Pilgrim) on Jan 13, 2003 at 14:03 UTC
    use the printf format. Where "%.2f" effectively means print to 2 decimal places - and perl rounds things nicely for you
    my $number = 0.9999; printf "number = %.2f \n", $number;
    Or if you just want to store it and then use it later uses sprintf
    my $number = 0.9999; $rounded = sprintf "%.2f", $number; print "rounded = $number\n";

    A.A.

    Update:

    A.A. is having a bad day and begs your indulgence for his stupidity

Re: Rounding to a Given Number of Significant Figures Rather Than Decimal Places
by Clem (Sexton) on Jan 13, 2003 at 17:21 UTC
    Sorry to be dim but, with the following lines of code: $_ = FormatSigFigs($_,8); my $clem = sprintf("%08g", $_); print "$clem\n"; and starting with the number 1298.844667, I get the result 01298.84 and not 1298.845, as desired. Any ideas? Thanks again for your help
      Drop the zero in the format.

      Update: Urm... that will replace the leading zero with a space, which is only part of your problem... Apparently, the default precision offered by "%g" is smaller than the precision you desire.

      OK, try this:

      $_ = 1298.844667; my $clem = sprintf("%.7g", $_); print "$clem\n";
      Result:
      1298.845
      OK?
Re: Rounding to a Given Number of Significant Figures Rather Than Decimal Places
by Clem (Sexton) on Jan 14, 2003 at 15:42 UTC
    Thank you all very much for the advice. After much playing about, I have found the following to work (including with negative numbers):<CODE> $_ = FormatSigFigs($_,7); $_ = sprintf("%8.8s", $_); print $_,"\n";<CODE> There will no doubt be numbers for which it doesn't work but it seems to be OK at the moment.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://226449]
Approved by davis
Front-paged by rob_au
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (4)
As of 2024-04-21 14:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found