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
| [reply] [d/l] |
|
|
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.
| [reply] |
|
|
$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
| [reply] [d/l] |
|
|
|
|
|
|
#! /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' | [reply] [d/l] [select] |
|
|
|
|
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. | [reply] [d/l] [select] |
|
|
|
|
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.
| [reply] |
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.
| [reply] [d/l] |
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
| [reply] [d/l] [select] |
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 | [reply] |
|
|
$_ = 1298.844667;
my $clem = sprintf("%.7g", $_);
print "$clem\n";
Result:
1298.845
OK? | [reply] [d/l] [select] |
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.
| [reply] |