Floating Point numbers: What, Why, and How?

A short read on floating point numbers, floating point approximations, and a possible solution on how to handle that in Python.

In your python terminal try executing this

>>> 0.1+0.2 == 0.3

Weird is it not? The answer should have been True. Every programmer would have faced this issue at some point in time. If they have not, they are bound to face it in the future. As to the question 'Why is this happening?', first, we have to understand how floating point numbers are represented inside a computer.

This is not a bug in Python or any language as a matter of fact. It is caused by an inconsistency between the different number systems. Read on for a further explanation.

First: What are floating-point numbers?

Floating-point numbers are used to represent fractional numbers (fractions in short and are used in most engineering and technical calculations. For example, 3.256, 2.1, and 0.0036 are floating-point numbers. Thus 0.1 can be expressed in its fractional form as 1/10 and 0.2 as 2/10. When we take the sum of these two fractions we get, 3/10 which is 0.3.

Fairly straightforward is it not? That is because we use base-10 or the decimal number system for everyday calculations. The base-10 number system uses numbers from 0 to 9 for representations and calculations. Thus 0.1 + 0.2 = 0.3. When it comes to computers, they understand higher-level programming languages like Python, Java, C, by converting them and storing them in the base-2 number system or the binary number system that comprises 0s and 1s.

Base-10 vs Base-2: The showdown!

floatingpoint_showdown.jpg

Consider 1/3 in decimal. The decimal is a recursive fraction that goes 0.3333333. Similarly, 2/3 is 0.66666666. Taking the sum of the two we get 0.99999999, not 1. Since the base of the decimal system is 10, only fractions with denominators containing either of the prime factors of 10 which are 2 and 5 can be represented precisely. Similarly, only fractions with denominators containing only a prime factor of 2, which is 2 itself, can be represented precisely in the binary system. Unfortunately for most decimal numbers, there is no precise representation in the binary number system.

Back to 0.1 + 0.2

In base-2, 1/10 is an infinitely repeating fraction 0.0001100110011001100110011001100110011001100110011.... The same is the case for 2/10 which is 0.001100110011001100110011001100110011.... Stopping at any finite number results in an approximation. On most machines today, floats are approximated using the IEEE double-precision floating-point standard representation which uses 64 bits for representations.

floatingpoint_ieee64bit.jpg

The first bit is the sign bit, S, the next 11 bits are the exponent bits, E, and the final 52 bits are the fraction F. Accordingly the closest decimal value of the truncated value 0.1 would be 0.1000000000000000055511151231257827021181583404541015625 which is close to 0.1 but not exactly equal. Similarly, for 0.2 it is 0.200000000000000011102230246251565404236316680908203125.

Most programming languages return 0.30000000000000004 as the sum of 0.1 and 0.2. But interestingly enough you can see that 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 is not equal to 0.30000000000000004. This is because when a machine adds 0.1 and 0.2 it only sees the truncated binary values of 0.1 and 0.2. And the best approximation to the sum of these two binary numbers is, unsurprisingly, a value just above 0.3: approximately 0.30000000000000004.

But wait there’s more!

but-wait-theres-more.gif

These approximation errors creep up when you try to round numbers too! You would expect rounding 2.675 to two places after the decimal point would return 2.68. But no! It returns 2.67, as 2.675 converted to a binary floating-point number is again replaced with a binary approximation, whose exact value is 2.67499999999999982236431605997495353221893310546875 which is closer to 2.67 than 2.68.

A solution in Python

anna-kendrick-pitch-perfect.gif

When working with decimal calculations what is considered to be a good practice is to always round the decimals to the number of positions of your programming requirement. In scenarios where it matters which way the decimals are rounded or the accuracy of floating-point arithmetic, Python has a module named decimal.

>>> from decimal import Decimal
>>> Decimal(2.675)
Decimal('2.67499999999999982236431605997495353221893310546875')

Unless you are handling projects which determine flight paths or the integrity of a nuclear power plant or processing the financial information of companies, floating-point approximations are not that big of an issue. It is wise to be mindful rather than oblivious about these when you do floating-point calculations in your language.

Happy coding!

Did you find this article valuable?

Support Dev B by becoming a sponsor. Any amount is appreciated!