Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Display floating point numbers in compact, fixed-width format

by tye (Sage)
on Sep 24, 2003 at 00:14 UTC ( [id://293747]=CUFP: print w/replies, xml ) Need Help??

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 (Patriarch) 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
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://293747]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (5)
As of 2024-04-24 19:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found