xrmb has asked for the
wisdom of the Perl Monks concerning the following question:
I was debugging a missing penny today and I ended up with this disappointment:
print int(4.39 * 100);
says:
438
is that true? so imprecise with such low numbers? I guess 4.39 * 100 becomes 438.9999999999, but:
print 4.39 * 100;
says:
439
What can I do to get the int be right? I'm sure there are other numbers it has problems with.
Re: shocking imprecision
by davido (Archbishop) on Oct 17, 2011 at 22:23 UTC

It's only shocking because you grew up with baseten and are so accustomed to seeing the problem that you don't notice it until it turns up in another base system.
...and because you haven't read perlfaq4: Why am I getting long decimals....., and perlfaq4: Why is int() broken?
Some explanation: Rational numbers do not always have a terminating expansion for a given base. In base ten, we are so accustomed to seeing 1/3rd expressed as .33 (maybe with more significant digits) that we hardly even think about it. But the fact is that .33333333333 is not equal to 1/3rd. You cannot represent 1/3rd in a finite number of digits using a decimal (base ten) representation. You could say that 1/3rd has an infinite expansion, or a nonterminating expansion in base ten. In base ten, any rational number that can be represented as k/((2^n)*(5^m)) would be a number that has a terminating expansion. 4.39 is 439/((2^2)*(5^2)), which fits that formula nicely, and so we get a terminating expansion from 439/100 to 4.39.
In base two (binary), the same applies, but with a different formula; some numbers cannot be represented in a finite number of binary digits. It turns out that in base two, any rational number with a denominator that is a power of two will have a terminal expansion. But all other rational numbers will not. So n/2, n/4, n/8, n/16, n/32, n/64... those could all be represented in binary format in a finite number of binary digits. Or to put it in terms normalized to the previous formula, k/2^n would represent a number that has a terminating expansion in base 2. But 4.39 is 439/100's, which is a rational number that can not be represented in binary with a finite number of digits.
That being the case, computer does what we do when we see 1/3rd in base ten; it approximates. It stores the number as some binary value that is as close as possible to 4.39 in binary format. Just as .33 is slightly less than 1/3rd, 4.39 stores as slightly less than 4.39.
So the computer stores 4.39 as 4.38999999999999. What happens when you ask for 4.38999999999999 * 100? You get 438.99999999999. And when you ask for the integer value of 438.9999999999, you get 438.
This is not a Perl problem. You would be equally shocked by the "imprecision" of Basic, Pascal, C, ModulaII, C++, and just about every other language that doesn't attempt to hide the truth from you.
An illustration: Try this little exercise:
perl MO=Deparse e 'print 4.39 * 100'
perl MO=Deparse e 'print int( 4.39 * 100 )'
The output should be something like:
print 438.99999999999994;
...and...
print 438;
So in the first example, the multiplication is indeed storing the number as something slightly less than 439, but because print quietly rounds that to 439 you don't notice. However, int wouldn't be doing a very good job if it quietly rounded when you asked for truncation (which is what int is supposed to do). So it's taking that 438.9999999999999994 and truncating the floating point portion, just like it's supposed to.
Other reading:
 [reply] [d/l] [select] 
Re: shocking imprecision
by ikegami (Pope) on Oct 17, 2011 at 21:51 UTC

print rounds (to 6 positions, I think?), but int truncates. You can use sprintf('%.6f', EXPR) to round without printing.
By the way, the impression is not related to the magnitude of the numbers. The problem is that 38/100 is a periodic number in binary like 1/3 is a periodic number in decimal. It would take infinite storage to store it as a floating point number.
 [reply] [d/l] [select] 
Re: shocking imprecision
by Marshall (Abbot) on Oct 17, 2011 at 23:19 UTC

Perl has implemented the POSIX functions of ceil() and floor() for a long time. ceil() and floor(). This link is more than a decade old.
This behavior is shocking to you because you think in base 10. The computer thinks in base 2 and a number that may appear to you to only take a finite amount of digits in base 10, but it may take an infinite number of digits in base 2.
This issue exists in C, Perl, Java and all languages that do computations in base 2, native binary machine code. For business applications (e.g. accounting), special hardware and software packages exist that do math in BCD, Binary Coded Decimal  that is an encoding where 4 binary bits only represents 09 instead of 015 like in binary.
If you take an accounting class and we do straight line depreciation of 1/3 per year over 3 years. At "the end of day" these "round down" things come back to haunt us and one way to handle this is that the last year gets just slightly more depreciation so that the total depreciation works out to the initial value of the asset. These are "real world" considerations and they matter  heck anything having to do with money matters!  [reply] 
Re: shocking imprecision
by CountZero (Bishop) on Oct 17, 2011 at 22:09 UTC

If you want int to roundoff, just add 0.5 before taking the integer part.
CountZero A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity."  The Tao of Programming, 4.1  Geoffrey James
 [reply] [d/l] 

Actually, that's why sprintf and printf are recommended:
perl E 'say int(1.2 + .5); printf("%d\n",1.2)'
One of those is going to send the Monk back here with more questions :)
 [reply] [d/l] 
Re: shocking imprecision
by sundialsvc4 (Abbot) on Oct 18, 2011 at 00:32 UTC

The foregoing is one of the reasons why COBOL (and other languages, as well) implemented binary coded decimal (BCD) arithmetic. When numbers are expressed in this way, a base10 number is literally base10, with both the digits and the sign being expressed by groups of (usually) 4 or 8 bits. The arithmetic is performed in base10 to a definite (and fixed) precision. Every modern microprocessor has hardwarelevel support for it.
In fact, in COBOL, the default is always to perform decimal arithmetic, unless the COMP[UTATIONAL] directive is specified.
Another strategy, used e.g. by Microsoft Access and its JET database engine, is scaled integers. The Currency data type in that system is implemented by a true binary integer whose value is assumed to be multiplied by 10,000. This gives accurate, base10float compatible operations with exactly 4 digits of fixed precision, without actually using floatingpoint.
 [reply] 
Re: shocking imprecision
by moritz (Cardinal) on Oct 18, 2011 at 06:07 UTC

$ ./perl6 e 'say (4.39 * 100).Int'
439
$ ./perl6 e 'say 4.39.perl'
439/100
 [reply] [d/l] 
Re: shocking imprecision
by JavaFan (Canon) on Oct 18, 2011 at 06:20 UTC

I guess 4.39 * 100 becomes 438.9999999999... What can I do to get the int be right?
What do you mean, "get it right"? Do you want int(438.9999999999) to return something else than 438?
If you're using int to round to the nearest integer, you're doing it wrong. That's not what int does  int rounds towards 0. Use (s)printf instead.
 [reply] [d/l] [select] 
Re: shocking imprecision
by rgiskard (Hermit) on Oct 23, 2011 at 14:04 UTC

xrmb sprintf is the way to go, but if you're feeling lazy; just add "use bigrat;" (aka. big rational, cpan: bigrat) to the top of your proggie. It is not optimal (neither is bignum), but it does increases precision.
Example: imprecise.pl
use bigrat;
print int(4.39 * 100) . "\n";
my $a = 4.39, $b = 100;
print int ($a*$b) ."\n";
my $ans = $a*$b;
print $a*$b ."\n";
print int ( $ans ) ."\n";
Output:
perl imprecise.pl
439
439
439
439
 [reply] [d/l] [select] 

