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 sprintable 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 1e07 is represented as rounded float:0.00000, other:1.0
+0000e07
The Number 1e06 is represented as rounded float:0.00000, other:1.0
+0000e06
The Number 1e05 is represented as rounded float:0.00001, other:1.0
+0000e05
The Number 0.0001 is represented as rounded float:0.00010, other:1.
+00000e04
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 1e007 is represented as rounded float:0.00000, other:0.00
+000e+000
The Number 1e006 is represented as rounded float:0.00000, other:0.00
+000e+000
The Number 1e005 is represented as rounded float:0.00001, other:1.
+00000e005
The Number 0.0001 is represented as rounded float:0.00010, other:1.
+00000e004
Caution: Contents may have been coded under pressure.
 [reply] [d/l] [select] 

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 1e07 is represented as rounded float:0.00000
, here's the rounded num 0.00000
The Number 1e06 is represented as rounded float:0.00000
, here's the rounded num 0.00000
The Number 1e05 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
 [reply] [d/l] 

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.
 [reply] [d/l] 

 [reply] 
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] }
 [reply] [d/l] [select] 

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 09
 Ant
 Some of my
best work  (1 2 3)
 [reply] [d/l] 
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 floatingpoint 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.
 [reply] [d/l] [select] 

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)
 [reply] [d/l] 
Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by roboticus (Chancellor) on Dec 11, 2007 at 18:33 UTC

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.  [reply] [d/l] 

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.
 [reply] 

 [reply] 



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)
 [reply] [d/l] 
Re: Negative zero? There's gotta be a sprintf that undoes that, right?
by FunkyMonk (Chancellor) on Dec 11, 2007 at 23:42 UTC

for ( 0.1, 0.01, 0.001, 0.0001, 10 ) {
printf "%s > %0.1f\n", $_, sprintf "%0.1f", $_
}
 [reply] [d/l] [select] 

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;
}
 [reply] [d/l] 
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
 [reply] [d/l] [select] 
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.  [reply] [d/l] 

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)
 [reply] [d/l] 

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.  [reply] [d/l] 

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 shortcoming. 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.
 [reply] [d/l] [select] 
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;
 [reply] [d/l] 
[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


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...
 [reply] [d/l] [select] 

