Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Negative zero? There's gotta be a sprintf that undoes that, right?

by rgiskard (Hermit)
on Dec 11, 2007 at 17:00 UTC ( [id://656432]=perlquestion: print w/replies, xml ) Need Help??

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

Trying to find a way to sprint out to 5 decimal places (not exponential) and if a number rounds up from the negative side to zero... well I want zero, NOT "negative zero".

I would hate to think there isn't a sprint-able answer to this. Otherwise, I'm off to making a very simple subroutine to look for some amount of precision and make positive if necessary.

I feel I've done all the necessary research, so chime in if it's not possible. perldoc.perl.org 's sprintf page is what's been guiding me, and after reading + tinkering; I'm giving up and asking the monks.

An example proggie to exemplify!
print "print an example of negative zero\n"; @range = (-0.0000001, -0.000001, -0.00001, -0.0001); foreach $example (@range) { $tret = sprintf("rounded float:%7.5f, other:%7.5e",$example,$examp +le); print "The Number $example is represented as $tret\n"; }
Output:
print an example of negative zero The Number -1e-07 is represented as rounded float:-0.00000, other:-1.0 +0000e-07 The Number -1e-06 is represented as rounded float:-0.00000, other:-1.0 +0000e-06 The Number -1e-05 is represented as rounded float:-0.00001, other:-1.0 +0000e-05 The Number -0.0001 is represented as rounded float:-0.00010, other:-1. +00000e-04

Replies are listed 'Best First'.
Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by Roy Johnson (Monsignor) on Dec 11, 2007 at 18:49 UTC
    Revised: using %g doesn't work as expected
    Round, then format. Here, I use sprintf with "%.5f" to round to five decimal places, and then use your "%7.5f" to format it.
    print "print an example of negative zero\n"; my @range = (-0.0000001, -0.000001, -0.00001, -0.0001); foreach my $example (@range) { my $twice = sprintf "%.5f", $example; my $tret = sprintf("rounded float:%7.5f, other:%7.5e",$twice,$twic +e); print "The Number $example is represented as $tret\n"; }
    Output:
    print an example of negative zero The Number -1e-007 is represented as rounded float:0.00000, other:0.00 +000e+000 The Number -1e-006 is represented as rounded float:0.00000, other:0.00 +000e+000 The Number -1e-005 is represented as rounded float:-0.00001, other:-1. +00000e-005 The Number -0.0001 is represented as rounded float:-0.00010, other:-1. +00000e-004

    Caution: Contents may have been coded under pressure.
      No wait, I'm not getting the same output using the rounding twice solution. Roy Johnson... what version of perl are you using? I'm on 5.8.5, RHLinux
      print "print an example of negative zero\n"; @range = (-0.0000001, -0.000001, -0.00001, -0.0001); foreach $example (@range) { my $preround = sprintf ("%.5f",$example); my $tret = sprintf("rounded float:%7.5f \n",$preround); print "The Number $example is represented as $tret, here's the rou +nded num $preround\n"; }
      Output:
      print an example of negative zero
      The Number -1e-07 is represented as rounded float:-0.00000 
      , here's the rounded num -0.00000
      The Number -1e-06 is represented as rounded float:-0.00000 
      , here's the rounded num -0.00000
      The Number -1e-05 is represented as rounded float:-0.00001 
      , here's the rounded num -0.00001
      The Number -0.0001 is represented as rounded float:-0.00010 
      , here's the rounded num -0.00010
      
        That's weird. I'm using ActivePerl 5.8.1. But I just tried it on 5.8.6 for Solaris, and got your result there. The fix is to add zero:
        my $preround = 0 + sprintf ("%.5f",$example);

        Caution: Contents may have been coded under pressure.

      Schweet! This solves my sprintf dilemma without additional subroutines (and then having to package + implement + distribute + maintain said subroutines).



      Thanks!
Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by gamache (Friar) on Dec 11, 2007 at 17:12 UTC
    I know of no way to do this within sprintf, but how about this:
    sub remove_sign_on_zero { my $num_str = shift; if ($num_str =~ /^-([0\.]+)$/) { return " $1"; } else { return $num_str; } } my $num = remove_sign_on_zero( sprintf('%7.5f', $example) );
    The sub can be written golfier, if you like:
    sub remove_sign_on_zero { $_[0]=~/^-([0\.]+)$/ ? " $1" : $_[0] }
      Now... here's a really sick method that uses the regexp, but only to remove the leading - when needed. -0 == 0 but also -0 lt 0 so...
      $num=sprintf("%7.5f",$num); $num=~s/^-// if $num == 0 && $num lt 0;
      minimal regexp work and works for any precision.

      Update Caveat... only works in locales where "-" is lower than 0-9

                      - Ant
                      - Some of my best work - (1 2 3)

Re: Negative zero? There's gotta be a sprintf that undoes that, right? (0+"$x")
by tye (Sage) on Dec 12, 2007 at 04:14 UTC

    Well, it took a while for my subconscious to come up with the most trivial solution to this. Since "-0" gets interpretted as 0 and negative zero gives the string "-0", the following should work:

    my $nz= -0.0; printf "%f\n", $nz; # Will print "-0.00000" on some platforms printf "%f\n", "$nz"; # Should give "0.00000"

    But grabbing a quick platform to test showed this output:

    -0.000000 -0.000000

    So adding a slight pinch of voodoo gave me a solution that worked on the system that I tested it on, 0+"$nz":

    #!/usr/bin/perl -w use strict; my $nz= -0.0; printf "%f\n", $nz; # May give -0.00000 printf "%f\n", "$nz"; # May give -0.00000, surprisingly printf "%f\n", 0+"$nz"; # Doesn't give -0.00000! __END__ -0.000000 -0.000000 0.000000

    Go figure.

    It is rather twisted that printf "%f", "-0" gives negative zero while printf "%f", -0 gives just zero (while eval "-0" and 0+"-0" all also just give zero). I'd be interested in more details on why that is so, but not enough to go source diving at the moment.

    Update: Also note that you may not want to do $x= 0+"$x";, as that will slightly change the value of $x, perhaps increasing the accumulated error in this result (if $x is the result of some calculations). On the other hand, doing that can also have advantages in some situations. For example, if working with monetary values represented as dollars (or any other "cent"-based currency), then $x= 0+"$x" every so often is likely to remove accumulated error (well, reduce the accumulated error as much as is possible). The string representation of "14.28" is a more accurate representation of "14 dollars and 28 cents" than the floating-point value 14.28. So be careful.

    Update: Oops, I should have read the specification more carefully. I thought the "problem" was negative zero, not negative numbers being displayed as negative zero (which was clearly indicated in the original node). Mea culpa.

    - tye        

      Ahh... but further testing shows that
      printf "%f\n", 0+"-0.0"; 0.000000 printf "%f\n", 0+"-0.00000009"; -0.000000
      I have a feeling this has to do with something lower than actual negative zero being numerified as a negative number, THEN going through printf, which undoes all that extra work.

                      - Ant
                      - Some of my best work - (1 2 3)

Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by roboticus (Chancellor) on Dec 11, 2007 at 18:33 UTC
    alternatively...

    my $sign = ""; if ($example < 0) { $example = -$example; $sign = "-"; } $tret = sprintf("rounded float: %s%7.5f", $sign, $example);
    Note: Untested....

    ...roboticus

    Update: I thought about it some more. It'll still have the same problem. If you were to use this approach, you'd also need an if statement that would clear the sign if the value is less than the smallest value represented as a nonzero number.

    Clearly not worth the effort.

      I can see where you're going with this; pull out the sign and then do something based on the sign. But in all honesty, negative numbers are fine; unless it's negative zero, be it -0.0 or -0.00000000.
      An unsigned float would've nipped this problem in the bud.

        rgiskard:

        I had assumed that negatives were fine. I was just showing an alternative solution to the problem.

        ...roboticus

Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by suaveant (Parson) on Dec 11, 2007 at 20:31 UTC
    There is, of course, the putrid and horrible, yet sublimely simple additive solution...
    print "print an example of negative zero\n"; @range = (-0.0000001, -0.000001, -0.00001, -0.0001); foreach $example (@range) { $tret = sprintf("rounded float:%7.5f, other:%7.5e",$example+.00000 +4,$example); print "The Number $example is represented as $tret\n"; }
    Update: .000004 not .0000009 actually seems to work

                    - Ant
                    - Some of my best work - (1 2 3)

Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by FunkyMonk (Chancellor) on Dec 11, 2007 at 23:42 UTC
    printf a sprintf'ed number:
    for ( -0.1, -0.01, -0.001, -0.0001, -10 ) { printf "%s --> %0.1f\n", $_, sprintf "%0.1f", $_ }

      This doesn't work for me, perl 5.8.6 on OS X, however with a little change it does:
      for ( -0.1, -0.01, -0.001, -0.0001, -0.00001, -10, ) { my $num = sprintf "%0.1f", $_; $num = abs( $num ) if $num == 0; printf "%s --> %0.1f\n", $_, $num; }
Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by dwm042 (Priest) on Dec 12, 2007 at 14:47 UTC
    This is a solution that works in ActiveState Perl v5.8.8
    #!/usr/bin/perl use warnings; use strict; my $nz = -0.000000000001; $nz = sprintf("%.5f",$nz); print $nz, " ", zerofy($nz), "\n"; sub zerofy { my $num = shift; if ( $num == 0.0 ) { $num = 0.0; } return $num; }
    The output is:

    C:\Code>perl zerofy.pl -0.00000 0
Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by erik (Sexton) on Dec 13, 2007 at 12:50 UTC
    Hey!
    In the original post I could read this : "rounded float". The problem is that sprintf does not really "round" from a mathematical point of view. You can achieve a quick and dirty rounding simply by using:
    int($example*100000)/100000
    ...in your sprintf

    It is not strictly a rounding operation from a math point of view but it should do the trick.
      Oh, but it does...

      perl -e 'printf "%.1f %.1f\n",0.05,0.049' 0.1 0.0
      your method is good for truncating... course, with a suitable addition of .5 it rounds.

                      - Ant
                      - Some of my best work - (1 2 3)

      I would love to read what you mean by "sprintf does not really 'round' from a mathematical point of view". I have observed that sprintf will round up from the number right outside of the requested precision. For example, from my perspective, the following list when rounded to 5 decimal places should end in 70 (i.e. 1.777695 rounds to 1.77770), do note that it *does not* always work that way.
      @nums= ( 1.777695, 1.77769501, 1.667695, 1.66769501, 1.557695, 1.55769501, 1.000695, 1.00069500001, 1.000696 ); foreach (@nums) { print sprintf "%7.5f\n", $_; }
      Output on Perl 5.6.1: note: it usually rounds if the next significant digit is 5 or greater
      1.77770
      1.77770
      1.66769
      1.66770
      1.55770
      1.55770
      1.00069
      1.00070
      1.00070
      
      Output on Perl 5.8.5: note: it usually rounds if the next significant digit is 5 or greater
      1.77770
      1.77770
      1.66770
      1.66770
      1.55770
      1.55770
      1.00069
      1.00070
      1.00070
      
      Given the example, I would love for you to elaborate on your sprintf opinion.

        Yet another person who needs to be reminded that floating point can't represent most decimal values exactly? Note the following about the two numbers that exhibit your problem:

        printf "%.20f\n", 1.667695; printf "%.20f\n", 1.000695; __END__ 1.66769499999999990000 1.00069499999999990000

        So, I'm not surprised that those don't get rounded up to end in "70", since they are each less than 1.___695. They are as close to 1.___695 as the chosen floating point representation can get, but they are almost always going to be just above or just below and so how they round has to do with whether they can be more closer approximated by an n/2**p that is just above the desired value or just below the desired value.

        Now, Perl has been playing more and more tricks about ignoring the last bit or two if it would result in "0000001" or "9999999" to more effectively hide this fact from more people (in part because they tend to complain when they first discover it). It appears that perhaps your Perl 5.008_005 has added one more such trick (or gotten a bit more aggressive with an existing trick), though my Perl 5.008_008 agrees with both of our Perls 5.006_001.

        An interesting pattern is that pretty consistently about 72.5% of the numbers round as one would expect and only about 27.5% round lower due to this floating point short-coming. Update: I finally varied enough parts to find a bunch of ranges with 53% "normal" and 47% "low" or 85% "normal" and 15% "low"...

        Perl's being "smart" is a bit annoying here because Perl used to be more easily more useful for getting an understanding of this stuff. For example, perl4's output for my sample program is:

        1.66769499999999993000 1.00069499999999989000

        which gives us more information about how close the floating point values are able to get to our desired values. This also gives us a clue as to why the second number has "the problem" for both of your Perls while the first doesn't; the second is not as close. Update: And, I suspect that my sample program run on your Perl 5.008_005 will give 5 trailing zeros in the output unlike the 4 trailing zeros I got with my Perls v5 and the 3 trailing zeros I got with Perl4.

        - tye        

Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by bass_warrior (Beadle) on Dec 13, 2007 at 14:45 UTC
    use Math::BigFloat to properly round your number, then printf with work correctly.
    #!/usr/bin/perl use warnings; use strict; use Math::BigFloat; my $num = Math::BigFloat->new("-0.000004"); $num->bfround(5); printf "%f\n", $num;
[Humour] Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by blazar (Canon) on Dec 13, 2007 at 15:19 UTC

    I have the following entry amongst my .sig's:

    -- Zero was invented by primitive man, after a successful intercourse wit +h his first female companion. - "Ioannis" in sci.math, "Re: When was zero invented?"

    I personally believe that negative zero was invented after the first unsuccessful intercourse with his female companion. Of course, we all would like to undo that...

    --
    If you can't understand the incipit, then please check the IPB Campaign.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (1)
As of 2024-04-15 15:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found