Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations bkrike on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Problem with perl sprintf() funciton 2

Status
Not open for further replies.

BStopp

Programmer
Dec 29, 2003
29
US
THis one is for all you Computer Science majors. Part of a script i'm writing loops through fields of an array that are known to be numeric and rounds them to the nearest tenth using the sprintf() function. The problem is, some of the items are rounded correctly, others are not. Here's an example

@num_fields is an array containing the positions in the array that are numeric and need to be rounded
@line is the array of information to fix.

foreach (@num_fiels)
{
$line[$_] = $line[$_]=~ /^$/ ? $line[$_] : sprintf("%.1f",$line[$_]);
}

THis should basically run through the array and if it's not null, round the value to the nearest tenth of whatever is in it. However, as shown below i get variable outputs based on inputs.

Input: Output:
-33.05 -33.0
-23.05 -23.1
5.15 5.2
4.65 4.7
33.05 33.0


Can anyone explain to me why Perl would round it down one time and then up the next? It seems slightly random to me.

-BStopp
 
please don't play with colors:
red on blue, can YOU read that?
or try: blue on blue!
 
Obnoxious colors (use the code tag rather than colors) aside, it sounds like this isn't something that's real simple to fix.

From perlfaq4 (scroll about half way down.)
In these cases, it probably pays not to trust whichever system rounding is being used by Perl, but to instead implement the rounding function you need yourself... you'll still have an issue on half-way-point alternation.

I didn't test this much, nor try to shorten it at all, but this will give you a place to start. (There very well may be a better way to go about it, but I'm not aware of it.)

Code:
my @line = (-33.05, "Text 1", -23.05, "Text 2", 5.15, "Text 3", 4.65, "Text 4", 33.05, "Text 5");
my @num_fields = (0,2,4,6,8);

foreach (@num_fields) {
    my $print = &round(1, $line[$_]);
    print $print, "\n";
}

sub round ($@) {
    my $precision = shift;
    my @list = @_;

    foreach (@list) {
        my $decimal = index($_, '.');
        next if ($decimal == -1);
        my $round = substr($_, $decimal, length($_) - $decimal);
        next if (length($round)-1 <= $precision);
        $round = substr($round, 0, $precision + 2);
        my $check_digits= substr($round, $precision, 2, "");
        if (substr($check_digits,1,1, "") >= 5) {
            $check_digits++;
        }
        substr($_, $decimal, length($_) - $decimal, "$round$check_digits");
    }
    return wantarray ? @list : $list[0];
}
 
As for the colors.. Yes i can read 'em.

But anyway, rharsh, i appreciate the code, but the point wasn't for someone to help me write that code.. The point is in the O'Reilly (or however you spell it) books on Perl, it specifically states that

when you format a number using the f flag, (as in the %.1f in my code) if the number being formatted is more precise than this specified format: it will be rounded.

Unfortunately i'm writing this from home and don't have my reference book with me at this moment to actually quote from it, but you get my point.

If it's rounding for precision, and the value is in between the precision required (ie: 5) then there should at least be some sort of consitency in the result of the round, not some random/arbitrary thing.

Anyone able to explain the behaviour of the sprintf() function in this regard?

BStopp
 
Thats because you are thinking in decimal, whilst your computer is thinking in binary. The number -33.05 cannot be stored exactly using binary. Try the following experiment:
Code:
#!/usr/bin/perl -w
@nums =  ( -33.05, -23.05, 5.15, 4.65, 33.05 );
foreach $num (@nums) {
    printf "%.100f\n", $num;
}
For my platform this prints:
Code:
-33.0499999999999971578290569595992565155029296875000000000000000000000000000000000000000000000000000000
-23.0500000000000007105427357601001858711242675781250000000000000000000000000000000000000000000000000000
5.1500000000000003552713678800500929355621337890625000000000000000000000000000000000000000000000000000
4.6500000000000003552713678800500929355621337890625000000000000000000000000000000000000000000000000000
33.0499999999999971578290569595992565155029296875000000000000000000000000000000000000000000000000000000
This explains the 'inconsistent rounding' you are seeing. Cheers, Neil
 
If you really need this precision, then try:
Code:
#!/usr/bin/perl -w
use Math::BigFloat;
@numStrings =  qw( -33.05 -23.05 5.15 4.65 33.05 );
foreach $numString (@numStrings) {
    $num = Math::BigFloat->new($numString);
    printf "%.1f\n", $num->ffround(-1);
}
Cheers, Neil
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top