Floating point numbers are an approximation of their decimal equivalents.
They're super useful for most uses, but they can't be relied on for exact precision.
You can't assume that an exact equality comparison with floating point numbers will work:
>>> 0.1 + 0.02 == 0.12
False
Adding 0.1 and 0.02 results in a number that is VERY slightly different from the 0.12 number that we'd expect:
>>> 0.1 + 0.02
0.12000000000000001
This might seem like a huge problem, but it's usually not! This imprecision typically only causes trouble with strict equality.
Typically inaccuracies due to floating point arithmetic are resolved through string formatting.
Formatting that "not quite right" number to 8 decimal points shows the result we'd expect:
>>> print(f"{a+b:.8f}")
0.12000000
Rounding works too:
>>> round(a+b, 8)
0.12
If exact precision is crucial, you could use the decimal module:
>>> from decimal import Decimal
>>> a = Decimal("0.1")
>>> b = Decimal("0.02")
>>> a + b
Decimal('0.12')
But note: doing true decimal arithmetic on computers (which do arithmetic in binary) is much slower.
But why "floating point"? What's the floating part about?
It's the decimal point that floats!
In scientific notation, there's a decimal point and an exponent:
>>> = 6.02 * 10**3 6020.0
If we shrink the exponent the decimal place floats to the left:
>>> 6.02 * 10**-4
0.000602
Scientific notation is in base 10 (decimal). But floating point numbers use the same concept in base 2 (binary).
Double precision floating point numbers (as in Python) consist of 64 bits:
• 1 bit for the sign (+ or -)
• 11 bits for the exponent
• 53 bits for the fraction
The 64 bits used to represent a float in Python are decoded using a formula like this:
number = (-1)**sign + (1 + fraction/2**52) * 2**(exponent-1023)
There's a bit more to it than scientific notation (note 2**52 & 1023 numbers), but it's very similar!
Here's an example of how the bits that make up floating point numbers are parsed. You're welcome to play with this if you'd like to understand it further.
Python's "for" loops are single-purposed: they can loop over an iterable item-by-item. That's it.
The "for" loop itself can't loop in reverse or loop over multiple iterables.
For this reason, looping helpers are a VERY big deal in #Python.
Let's talk about looping helpers. 🧵
Want to loop over an iterable in the reverse direction? If it's a reversible iterable (lists, tuples, dictionaries, etc), you can use the reversed helper:
>>> colors = ["pink", "blue", "green"]
>>> for color in reversed(colors):
... print(color)
...
green
blue
pink
Note that the "for" loop isn't reversing here: reversed is!
The built-in reversed function accepts an iterable and returns a lazy iterable that provides each item from the original iterable in the reverse order.
>>> r = reversed(colors)
>>> next(r)
'green'
>>> next(r)
'blue'
Let's talk about what these methods do and why I recommend these ones first. 🧵
1️⃣ The string join method accepts an iterable of strings to join together.
It should be called on the separator you'd like to join by (e.g. " ", "\n", ",", ""). This is a string method: it's " ".join(my_list) and not my_list.join(" ").