Specifically under Solaris (SPARC), but also noted under most OS versions, floating point numbers are known to be, sometimes, just wrong. All timings below run against a Solaris2.10-sun4 system...
Of course, this is system dependent, and boils down to the OS libstdio and it's implementation of printf. On Solaris (C and Perl), for example:
printf("%.5f\n", 0.000035);
The output is 0.00003, which is a really short number for such a rounding error.
printf("%.5f\n", 0.099994999999999999);
Outputs 0.1000, but, with such a long number, it blows past half precision anyway, so this isn't as important... unless it is.
So, there Math::BigFloat, which is awesome, if you think turtles crawl quickly. I'm looking at high volume number crunching here, BigFloat takes over 30 seconds per 100,000 numbers, and that is just calling $bf->ffround(), another chunk of time creating the heavily overloaded objects.
So, there's the tried and usually true exponential way of rounding it. $o = $num**5; $p = int($o); (if these differ by more than 0.5, then { $o++; } return $o/10**5;). This method fixes 0.000035 rounding down issue. However, this has that same problem with numbers that break half-precision. (This method does 100,000 numbers in 2.39 seconds).
So, I wrote something that will do the rounding on the string representation of the number, instead of using math operations.
Counter intuitive as hell, but IF the number does not string to scientific notation, 100,000 in 1.78 seconds, otherwise 2.40 seconds. Which is to say, at least on Solaris, walking the string is as fast or faster than using exponential for rounding. Also, as long as a number is protected by quotes, there is no limit to the possible precision.
I would NOT recommend using this. I've tested it extensively, but - deeply - if you think you need this, then something else is probably wrong.
Yeah, substr abound, and yet, it's really quite fast. I tried it by converting the whole number into an array of characters first, and it was twice as slow.
Of course, this is system dependent, and boils down to the OS libstdio and it's implementation of printf. On Solaris (C and Perl), for example:
printf("%.5f\n", 0.000035);
The output is 0.00003, which is a really short number for such a rounding error.
printf("%.5f\n", 0.099994999999999999);
Outputs 0.1000, but, with such a long number, it blows past half precision anyway, so this isn't as important... unless it is.
So, there Math::BigFloat, which is awesome, if you think turtles crawl quickly. I'm looking at high volume number crunching here, BigFloat takes over 30 seconds per 100,000 numbers, and that is just calling $bf->ffround(), another chunk of time creating the heavily overloaded objects.
So, there's the tried and usually true exponential way of rounding it. $o = $num**5; $p = int($o); (if these differ by more than 0.5, then { $o++; } return $o/10**5;). This method fixes 0.000035 rounding down issue. However, this has that same problem with numbers that break half-precision. (This method does 100,000 numbers in 2.39 seconds).
So, I wrote something that will do the rounding on the string representation of the number, instead of using math operations.
Counter intuitive as hell, but IF the number does not string to scientific notation, 100,000 in 1.78 seconds, otherwise 2.40 seconds. Which is to say, at least on Solaris, walking the string is as fast or faster than using exponential for rounding. Also, as long as a number is protected by quotes, there is no limit to the possible precision.
I would NOT recommend using this. I've tested it extensively, but - deeply - if you think you need this, then something else is probably wrong.
sub string_round { my $num = shift; my $pre = shift; my $dot = index($num, q{.}); my $_e_ = index($num, q{e}); if ( $[ <= $_e_ ) { my $exp = substr($num, $_e_+1); # Capture exp portion. $num = substr($num, 0, $_e_); # Remove the exp portion. if ( $[ > $dot ) { $dot = $_e_; # Has exp, no dot, set it to where the e was } else { # Remove the dot $num = ( substr($num, 0, $dot) . substr($num, $dot+1) ); } if (0!=$exp && ( -1 == ($exp/abs($exp)))) { # Negative expone +nt. # Numbers available (from $[ to $dot) # are within precison, after exponent is applied (exp - $ +dot) if ( (abs($exp)-$dot+$[) <= $pre ) { for ( my $cx = 0; $cx < abs($exp); $cx++ ) { $dot--; if ( $dot < $[ ) { $num = "0$num"; $dot++; } } } elsif ( abs($exp) > abs($dot-$pre+$[) ) { return 0; } else { for ( my $cx = 0; $cx < (abs($exp)-$dot+$[); $cx++ ) { $dot--; } } if ( $[ < $dot ) { $num = ( substr($num, 0, $[+$dot) . "." . substr($num, $[+$dot ) ); } else { $num = "0.$num"; $dot = $[ + 1; } } else { # Positive exponent. my $cx = 0; # Dot already removed from number # If $cx is less than exponent, walk along the number # (starting from where . was). for ( ; $cx < $exp; $cx++ ) { # If , somehow, $[ + $dot + $cx # ... is beyond the length of the string # cath up by adding more zeroes. while (( $[ + $dot + $cx ) >= length($num) ) { $num .= "0"; } } # Maybe I didn't add any zeroes. # $[ + $dot + $cx is still shorter then length # ... so, the number still has a dot, # and maybe still needs rounding. if (( $[ + $dot + $cx ) < (length($num)) ) { $dot = ( $dot + $exp ); $num = ( substr($num, 0, $[+$dot) . "." . substr($num, $[+$dot ) ); } else { # Already at end of string, just return the whole numb +er. $dot = $[ - 1; return $num; } } } # End $_e_ ( exponent has been eliminated ) if ( $[ <= $dot ) { my $aPre = $[ + $dot + $pre; if ( length($num) >= $aPre ) { my $aCho = $aPre + 1; my $cho = substr($num, $aCho, 1); $num = substr($num, 0, $aCho); # Cut off num at expected l +ength. if ( 5 <= $cho ) { SREV: for ( my $cx = $aPre; 0 <= $cx; $cx-- ) { my $noch = substr($num, $cx, 1); if (0==$cx) { if ( q{.} eq $noch) { $num = "1" . $num; } elsif ( q{9} eq $noch) { substr($num, $cx, 1) = "0"; $num = "1" . $num; } else { substr($num, $cx, 1) = ($noch + 1); } last SREV; } elsif ( q{.} eq $noch ) { next SREV; } elsif ( 9 != $noch ) { substr($num, $cx, 1) = ($noch + 1); last SREV; } else { substr($num, $cx, 1) = "0"; } } } } } return $num; }
Yeah, substr abound, and yet, it's really quite fast. I tried it by converting the whole number into an array of characters first, and it was twice as slow.
|
---|
Back to
Meditations