why isn't it stored as 0.150000000000000 internally?
This is explains why 0.149 doesn't round to 0.2, but it doesn't explain
why 1.5 works different than 0.15 wuth just one floating point digit
difference.
Floating point numbers are stored as binary numbers. And, just
like you can't write a lot of numbers in a 10 based system
(e.g. one third) with a limited number of digits, you can't
store a lot of numbers in a binary system with a limited
number of bits. Thus the overwhelming majority of floating
point numbers are only approximations. For example 0.1 looks
like a simple number when written in a 10 based number system,
but when you would try to write it in a 2 based system you
would need an infinite number of bits. On the other hand,
a number like 1.0/3.0 would require an infinite number of
digits when written in a 10 based system but would be a
very "simple" number when you would write in base 3. (In
base 10 all rational numbers that are a fraction with the
denominator being the product of a power of 2 and a power
of 5 can be written with a limited number of digits, in
base 2 only those that have a power of 2 as the denominator.)
Thus, 1.5 can be stored precisely in base 2 with the limited
number of bits you have for a floating point number while
0.15 can't. Thus 0.15 can only be represented by an appro-
ximation which then leads to the problem you observed.
In this specific case I hardcoded it into the source. But in the real
application it is coming from a database and then some tax calculations
are done and in accounting every cent difference causes headaches.
perl -e 'print sprintf("%.1f", 0.10 + 0.05). "\n"'
Here a further problem with using floating point numbers
is at work. Neither 0.1 nor 0.05 can be stored precisely.
So you are already starting of with imprecise numbers. And
when you add those imprecise numbers the errors can add up!
And the more calculations you do the larger the error can
become.
In your example one result is that 0.15 isn't equal to
0.1 + 0.05. Thus a rule of thumb is never to try to
compare floating point numbers for equality since the
result hardly ever is what you expect.
But even with numbers that can be represented exactly there
can be problems. On my machine I get
jens@cm:~$ perl -e 'print sprintf("%.1f", 0.25). "\n"'
0.2
jens@cm:~$ perl -e 'print sprintf("%.1f", 0.75). "\n"'
0.8
While the 0.8 result for 0.75 is what one would expect,
the 0.2 for 0.25 isn't. The explanation is probably that
it's the result of rounding errors introduced when the
digits to be displayed are calculated.
If you want the precision you're looking for you probably
shouldn't use floating point numbers at all! If you want
e.g. 2 digits after the decimal point then "scale" your
calculations by a factor of 100, so that everything can
be done with integers.
Welcome to the wonderful world of floating point calculations;-)
Regards, Jens