tags:

views:

89

answers:

5

Consider the following program:

$x=12345678901.234567000;
$y=($x-int($x))*1000000000;
printf("%f:%f\n",$x,$y);

Here's what is prints:

12345678901.234568:234567642.211914

I was expecting:

12345678901.234567:234567000

This appears to be some sort of rounding issue in Perl.
How could I change it to get 234567000 instead?
Did I do something wrong?

+2  A: 

Homework (Googlework?) for you: How are floating point numbers represented by computers?

You can only have a limited number of precise digits, everything beyond that is just the noise from base conversion (binary to decimal). That is also why the last digit of your $x appears to be 8.

$x - (int($x) is 0.23456linenoise, which is also a floating point number. Multiplied by 1000000000, it gives another floating point number, with more random digits pulled from the incommensurability of the bases.

Svante
+1  A: 

Perl does not do arbitrary precision arithmetic for its built-in floating point types. So your initial variable $x is an approximation. You can see this by doing:

$ perl -e 'printf "%.10f", 12345678901.234567000'
12345678901.2345676422
Greg Hewgill
Perl does not *by default* do arbitrary precision arithmetic.
ysth
If it's not on by default, how do I turn it on?
User1
+4  A: 

Make "use bignum;" the first line of your program.

Other answers explain what to expect when using floating point arithmetic -- that some digits towards the end are not really part of the answer. This is to make the computations do-able in a reasonable amount of time and space. If you are willing to use unbounded time and space to work with numbers, then you can use arbitrary-precision numbers and math, which is what "use bignum" enables. It's slower and uses more memory, but it works like math you learned in elementary school.

In general, it's best to learn more about how floating point math works before converting your program to arbitrary-precision math. It's only needed in very strange situations.

jrockway
By "It's only needed in very strange situations" you're referring to money as a very strange situation right?
Evan Plaice
@Evan Plaice: Money isn't strange; it's (usually) integer numbers of cents. If you're using fractional values for money you're probably doing it wrong.
Greg Hewgill
@Evan Plaice no. For money you use integers. Money is *fixed* precision :)
hobbs
Ri'ight, insert foot in mouth please
Evan Plaice
@Greg, @hobbs - Evan was actually 100% right. I am guessing neither of you work in finance? ALL sorts of things related to money are fractional - from Accrued Interest to fees to coupons to FX conversions to... The nice rounding to whole cents you see on your bank statement is an (im)polite fiction
DVK
@Greg, @hobbs - ... matter of fact, IIRC, Japanese regulations **require** reporting of much higher-precision numbers.
DVK
@Greg, @hobbs - ... which is why you **never** store money as an int (e.g. # of cents) but always as a floating point. Also, for a thorough technical reference on fractional money and programming, go rent "Superman" or "Office Space" :)
DVK
Financial calculations use customized algorithms that are tailored to the precision needed, the size of the mantissa and base, etc etc. There's a whole profession dedicated to stuff like this. It's possible to reduce the error in floating point calculations if you know what ranges you're dealing with.
Ether
I think it's fair to say that my original comment, "it's only needed in very strange situations", exactly describes the situation where you need to report random data to random governments.
jrockway
@jrockway - i'd disagree with you even on the foreign government bit (financial industry is one of the major IT users, and of Perl as well; and pretty much all of it is globalized nowadays); but you may notice there are other, a lot more frequently relevant examples in my comments.
DVK
bignum gives me `12345678901.234568:234567000.000000`. I was hoping for `12345678901.234567:234567000.000000`. Why does it round that last digit up?
User1
@DVK fractions of a cent are all well and fine, but floats are still the wrong tool. Use an exact fractional (rational) representation, or a fixed-point representation with more digits (and well-known bounds), or arbitrary precision if it's really necessary (but you can represent any value up to $18bn and beyond to the millionth of a cent with 64-bit ints :)
hobbs
@hobbs - I have yet to see a single piece of code in my many years in financial industry which uses exact rational/fractional representation, or uses ints to represent floats to high precision. It might be done in some obscure corner cases, but a VAST majority stores regular floating point #s in the database and represent them as a double in C++ or regular floating point #s in Perl etc...
DVK
Yeah, we do this in the finance world, but that's because we're not dealing with money. When you are dealing with grandma's $100 rent payment, every cent counts. When you are shorting $100 billion worth of treasury bonds, the 9th decimal place on the interest rate doesn't really matter. Hence, we use doubles.
jrockway
+6  A: 

This is a frequently-asked question.

Why am I getting long decimals (eg, 19.9499999999999) instead of the numbers I should be getting (eg, 19.95)?

Internally, your computer represents floating-point numbers in binary. Digital (as in powers of two) computers cannot store all numbers exactly. Some real numbers lose precision in the process. This is a problem with how computers store numbers and affects all computer languages, not just Perl.

perlnumber shows the gory details of number representations and conversions. To limit the number of decimal places in your numbers, you can use the printf or sprintf function. See the Floating Point Arithmetic for more details.

printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;
Greg Bacon
+3  A: 

The whole issue of floating point precision has been answered, but you're still seeing the problem despite bignum. Why? The culprit is printf. bignum is a shallow pragma. It only affects how numbers are represented in variables and math operations. Even though bignum makes Perl do the math right, printf is still implemented in C. %f takes your precise number and turns it right back into an imprecise floating point number.

Print your numbers with just print and they should do fine. You'll have to format them manually.

The other thing you can do is to recompile Perl with -Duse64bitint -Duselongdouble which will force Perl to internally use 64 bit integers and long double floating point numbers. This will give you a lot more accuracy, more consistently and almost no performance cost (bignum is a bit of a performance hog for math intensive code). Its not 100% accurate like bignum, but it will affect things like printf. However, recompiling Perl this way makes it binary incompatible, so you're going to have to recompile all your extensions. If you do this, I suggest installing a fresh Perl in a different location (/usr/local/perl/64bit or something) rather than trying to manage parallel Perl installs sharing the same library.

Schwern
`printf "%s"` would also get the job done by forcing the bignums to stringify. Not that there's any particular *need* for printf here, but it's worth knowing.
hobbs