Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Display floating point numbers in compact, fixed-width format

by tye (Sage)
on Sep 24, 2003 at 00:14 UTC ( #293747=snippet: print w/replies, xml ) Need Help??
Description:

I have yet to find a display format for arbitrary floating point numbers that I like much. I wanted something closer to "Engineering notation" (exponents are always a multiple of 3) but this works for now.

  • Number always take up 9 characters
  • Perl doesn't handle any numbers too large or too small to fit (you'd have to go smaller than 1.0e-999 or larger than 9.99e999)
  • Numbers of similar magnitude line up their decimal points

The code is tested but could probably be improved a great deal.

I'd like to be able to make the width a parameter (instead of fixed at 9). Being able to go as low as 7 (or even 6) would be way cool.

#!/usr/bin/perl -w
use strict;

Main();
exit( 0 );

{

    my( %fmt4exp, @exps2fmt, $fullfmt );

    BEGIN {
        %fmt4exp= (
            -100 => '+1.0e-999',
             -10 => '+1.00e-99',
              -5 => '+1.000e-9',
              -1 => '+0.000000',
              +3 => '   +0.000',
              +5 => '     +0.0',
              +7 => '       +0',
              +9 => '+1.000e+9',
             +99 => '+1.000e99',
          +99999 => '+1.00e999',
        );
        @exps2fmt= sort {$a<=>$b} keys %fmt4exp;
        $fullfmt= '%+14.7e'; # %+1.2345678e-99
    }

    sub Num2Str {
        my( $num )= @_;
        my $full= sprintf $fullfmt, $num;
        my( $sign, $one, $rest, $esign, $eabs )=
            $full =~ m<
                ^\s*
                ([-+]?)(\d)\.(\d*)
                [eE]([-+]?)(\d+)
                \s*$
            >x;
        my $exp= $esign . $eabs;
        my $fmt;
        for my $exp2fmt (  @exps2fmt  ) {
            if(  $exp <= $exp2fmt  ) {
                $fmt= $fmt4exp{$exp2fmt};
                last;
            }
        }
        my $str= $fmt;
        if(  $fmt =~ /e/  ) {
            $str =~ s/\+/$sign/;
            $str =~ s/1/$one/;
            $str =~ s{\.(0+)(?=[eE])}{
                (   sprintf( "%14.".length($1)."e", $num )
                        =~ /(\.\d+)/
                )[0]
            }e;
            $str =~ s/(9+)/sprintf "%0".length($1)."d", $eabs/e;
        } else {
            if(  $exp < 0  ) {
                $str =~ s/\+/$sign/;
                $rest= '0'x($eabs-1) . $one . $rest;
            } else {
                $one= substr( $one . $rest, 0, 1+$exp );
                substr( $rest, 0, $exp )= '';
                $str =~ s/([\s+0]+)/sprintf "%+".length($1)."s", $sign
+.$one/e;
            }
            $str =~ s{\.(0+)}{
                (   sprintf( "%14.".length($1)."f", $num )
                        =~ /(\.\d+)$/
                )[0]
            }e;
            $str =~ s{\.(0+)$}{
                (   sprintf( "%14.".length($1)."f", $num )
                        =~ /(\.\d+)/
                )[0]
            }e;
            $str =~ s/(\.?0+)$/' ' x length($1)/e
                if  $fmt =~ /\./;
        }
        return $str;
    }

}

sub Main {
    for my $exp (  -101..-98, -11, -10..11, 98..101  ) {
        for my $sign (  '', '-'  ) {
            my $num= 0 + ( $sign . "5.555555555e" . $exp );
            printf "%-20s (%s)\n", $num, Num2Str($num);
        }
    }
    for my $exp (  -10..11 ) {
        for my $sign (  '', '-'  ) {
            my $num= 0 + ( $sign . "1e" . $exp );
            printf "%-20s (%s)\n", $num, Num2Str($num);
            printf "%-20s (%s)\n", 0, Num2Str(0)
                if  1 == $num;
        }
    }
}
Replies are listed 'Best First'.
Re: Display floating point numbers in compact, fixed-width format
by BrowserUk (Pope) on Sep 24, 2003 at 08:22 UTC

    I'm confused by this transition in the output from your test program

    ... 0.05555555555 (+0.055556) -0.05555555555 (-0.055556) 0.5555555555 ( +0.556) -0.5555555555 ( -0.556) ...

    I realise that the answer could just be, "Because that's what I want!", but why suddenly start throwing away precision in favour of whitespace? And why at this point?


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
    If I understand your problem, I can solve it! Of course, the same can be said for you.

      The motivation is:

      Numbers of similar magnitude line up their decimal points

      But it appears I have an off-by-one error for when to transition to that other format. So I've changed

      -2 => '+0.000000',
      to
      -1 => '+0.000000',
      and added some more test cases (which found another bug that I've fixed -- stripping trailing '0's when I shouldn't). Thanks for catching that.

      At which points to change format are partially a matter of taste. The first three formats have very little room for matters of taste:

      -100 => '+1.0e-999', -10 => '+1.00e-99', -5 => '+1.000e-9',
      (the most likely change would be to change "-5", probably to something a bit closer to zero).

      The last three have only slightly more room:

      +9 => '+1.000e+9', +99 => '+1.000e99', +99999 => '+1.00e999',
      but the middle ground leaves lots of room for trade-offs. In particular, I wanted small integers to line up nicely (I chose -9999..9999 for my definition of "small") and I wanted a +0.0 format so that we can tell near-integers from non-integers for as long as possible. The +0.000000 and +0 formats allow us to delay going to 'e' format for as long as possible.

      Finally, add in a desire for fewer formats so that numbers are more likely to line up at their (perhaps implied) decimal points, and I'm stuck with what I used above. (:

                      - tye
Re: Display floating point numbers in compact, fixed-width format
by particle (Vicar) on Sep 26, 2003 at 14:01 UTC
    I wanted something closer to "Engineering notation".... I'd like to be able to make the width a parameter (instead of fixed at 9). Being able to go as low as 7 (or even 6) would be way cool.

    this code should meet those requirements. at least, the mantissa width is fixed, the exponent width varies. it should be easy enough to modify this code to suit your needs, if you wish.

    i used your test suite, too.

    #!/usr/bin/perl use strict; use warnings; $|++; Main(); exit; ## adapted from code found at: http://www.cs.tut.fi/~jkorpela/c/eng.ht +ml sub eng { my( $num, $digits )= @_; ## default to smallest number of digits allowing fixed width manti +ssa (4) $digits= defined $digits && 3 < $digits ? $digits : 4; my $neg; if( 0 > $num ) { $neg= 'true'; $num= -$num; } 0 == $num and return sprintf '+%.*fe+%s' => $digits - 1, $num, 0; my $exp= 0 != $num ## perl's log() is natural log, convert to common log ? int( log($num) / log(10) ) ## short-circuit: can't do log(0) : 0; ## tricky integer casting ahead... $exp= 0 < $exp ? int( ( int( $exp / 3 ) ) * 3 ) : int( int( ( -$exp + 3 ) / 3 ) * -3 ); $num *= 10 ** -$exp; if( 1000 <= $num ) { $num /= 1000; $exp += 3; } elsif( 100 <= $num ) { $digits -= 2; } elsif( 10 <= $num ) { $digits -= 1; } 0 <= $exp and $exp= '+' . $exp; return ( $neg ? '-' : '+' ) . sprintf '%.*fe%s' => $digits - 1, $num, $exp; } sub Main { my $digits= 2; for my $exp ( -101..-98, -11, -10..11, 98..101 ) { for my $sign ( '', '-' ) { my $num= 0 + ( $sign . "5.555555555e" . $exp ); printf "%-20s (%s)\n", $num, eng( $num, $digits ); } } for my $exp ( -10..11 ) { for my $sign ( '', '-' ) { my $num= 0 + ( $sign . "1e" . $exp ); printf "%-20s (%s)\n", $num, eng( $num, $digits ); printf "%-20s (%s)\n", 0, eng( 0, $digits ) if 1 == $num; } } }

    ~Particle *accelerates*

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: snippet [id://293747]
help
Chatterbox?
[perldigious]: That stuff can hit fast... like what we just got where I'm at. Saw a few people who had slid OUT of the roundabout I have to drive through to get to work, not used to the ice yet, take awhile to get the hang of it again and make vehicle adjustments.
[stevieb]: man, when the roads are glare ice, I don't even bother going to work or out... unless I absolutely have to, or was already out in the first place
[choroba]: Tire chains are mandatory here in mountains, and the only unprepared each year are gritters
[stevieb]: I have a 3 day winter survival kit in my vehicle in the event I get snowed in in the mountains (which has happened before due to avalanches closing the roads (and once in the summer due to a massive forest fire that trapped us
[perldigious]: a handful of people in my work area did not make it, but I live pretty close and it's flat the whole way, so I didn't have any trouble. The roundabout is the worst thing I have to negotiate.
[thezip]: G'day all!
[perldigious]: Well, that and dodging the people who forget how ice works right away. :-)
[stevieb]: hey, thezip
[perldigious]: I don't miss having to drive up and down in elevation on roads that are iced over, I white knuckled gripping the steering wheel every time I had to where I used to live.
[perldigious]: So I don't envy you mountain guys if you have to drive in the winter, even with tire chains.

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (10)
As of 2016-12-06 16:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    On a regular basis, I'm most likely to spy upon:













    Results (112 votes). Check out past polls.