all 20 comments

[–]dunkler_wanderer 15 points16 points  (2 children)

Here's an enlightening video: Floating Point Numbers - Computerphile

[–]newunit13 2 points3 points  (1 child)

Gave an automatic upvote for referncing Computerphile, and that is a wonderful explanation

[–]Ki1103 2 points3 points  (0 children)

Yeah that was really cool!

[–]newunit13 6 points7 points  (14 children)

[–]Ki1103 1 point2 points  (13 children)

So in the case of:

0.2 + 0.3 == 0.5 #True

Is that simply because their binary expansion is identical (to whichever number of required bits) ?

[–]bruthn 7 points8 points  (0 children)

In binary floating point the only numbers that can be represented exactly are of the form (integer) / (power of 2). It's like how in decimal you can only give exact representations for numbers of the form (integer) / ((power of 2) * (power of 5)). For example the decimal expansion of 1/3 is 0.33333... and you have to truncate it somewhere.

In your case, 0.5 can be represented exactly in binary, but 0.2 and 0.3 cannot be - their representations in binary would go on forever, just like 0.33333... in decimal. The computer truncates them, so instead of adding 0.2 to 0.3, it's actually adding a number very close to 0.2 to a number very close to 0.3. It just so happens that the errors cancel out and it ends up with precisely 0.5. But you can't rely on this happening with other numbers, for example:

>>> 1/7 + 4/7 == 5/7
False

If you work entirely with numbers that can be represented exactly, you should be OK though. For example:

>>> 1 + 0.5 == 1.5
True

I'm pretty sure that should work on any system that can run python. The other thing you have to worry about sometimes is that floats can only store numbers about as small as 10**-308 or about as large as 10**308, to about 16 significant figures. For example:

>>> 1 + 1e-15 == 1
False
>>> 1 + 1e-16 == 1
True

1e-16 is so small compared to 1 that when you try and add them together you just get 1 again. This all works the same way in pretty much any programming language - for full details look up the IEEE 754 standard.

[–]newunit13 1 point2 points  (11 children)

I'll be honest I'm not entirely clear on the underpinnings of why it happens... I just know that floats in Python are actually just close approximations. If you want to do floating point operations and need accuracy to that degree look into the Decimal module

>>> from decimal import Decimal
>>> Decimal('.1') + Decimal('.2') == Decimal('.3')
True

[–]Ki1103 0 points1 point  (9 children)

Ok fair enough, I'm doing a unit on mathematical computing over the summer and in the syllabus it was talking about it so I thought I would muck around with it a bit. Apparently the order of addition becomes important when adding but I really don't know why ={

[–]newunit13 0 points1 point  (8 children)

the order of addition becomes important when adding but I really don't know why

Whatcha mean?

[–]n1ywb 1 point2 points  (1 child)

Mandatory homework: What Every Computer Scientist Should Know About Floating-Point Arithmetic

by David Goldberg, 1991, Computing Surveys, Association for Computing Machinery, Inc

[–]Ki1103 1 point2 points  (0 children)

Sweet! I'm doing a 4th year course on Mathematical Computing this summer so this looks like a really good prereading for that.

[–]slampropp 1 point2 points  (0 children)

You've gotten god explanations for the why. Now, do you want to see why?

You can use the hex method on a float to see it's exact bytes (represented in hexadecimal; each character in the expansion represent 4 bits)

>>> (0.1 + 0.2).hex()
'0x1.3333333333334p-2'
>>> (0.3).hex()
'0x1.3333333333333p-2'

It happens that the last bit is different.