Why this matters
Many programs loop over decimal values. A graph might need a point every 0.1 seconds. A sensor might report values from 0.0 to 1.0. A simulation might step through time in fractions of a second. The natural first attempt is a loop that adds the step each time and stops at the end value.
That attempt often fails in a way that is hard to notice. The loop may miss its last value, run one time too many, or never stop at all. The cause is not a bug in Python. It is how computers store decimal numbers.
This lesson explains why the errors happen, shows them with real output, and then shows the fix: use an integer counter, and compute the decimal value from the counter each time. The common mistakes section shows how to compare two decimal numbers safely. The lesson on half-open ranges in this module covers why a range from 0 to n needs n + 1 values when both ends are included.
Why 0.1 is not exact
A float in Python is a floating-point number. It is a 64-bit value that stores a number in binary, which means base 2. Humans count in base 10, where each place is a power of 10. Computers count in base 2, where each place is a power of 2.
Some fractions are easy in base 10 and hard in base 2. The fraction 1/3 cannot be written exactly in base 10, because it becomes 0.3333 and the threes never end. In the same way, 1/10 cannot be written exactly in base 2. It becomes a pattern that repeats forever, so the computer stores the closest value that fits in 64 bits.
To see the value the computer actually stores, use the Decimal class from the decimal module. It prints every digit of a number:
Output:
0.1 written out exactly: 0.1000000000000000055511151231257827021181583404541015625The stored value is slightly larger than 0.1. Each time you add 0.1 to a running total, the small extra amount is added too, and the errors can build up. Also, the total is rounded again at each addition, so the error changes in a way that is hard to predict by hand.
Python prints a float with the shortest text that identifies it, so print(0.1) shows 0.1 even though the stored value is slightly larger. The printed text hides the error until a sum is computed.
Adding 0.1 twelve times
Start with a loop that adds 0.1 twelve times and prints the total after each step. The whole program is this loop:
The output is:
Adding 0.1 twelve times:
step 1: 0.1
step 2: 0.2
step 3: 0.30000000000000004
step 4: 0.4
step 5: 0.5
step 6: 0.6
step 7: 0.7
step 8: 0.7999999999999999
step 9: 0.8999999999999999
step 10: 0.9999999999999999
step 11: 1.0999999999999999
step 12: 1.2Step 10 should be 1.0, but the stored total is 0.9999999999999999. Step 11 jumps over 1.0 entirely and shows 1.0999999999999999. A loop that stops when x equals 1.0 would never stop here, because the value 1.0 never appears in the sum.
This is what the phrase "loop drift" means. The loop variable drifts away from the value you expected, a little at each step, and the drift decides whether the loop ends at the right place.
A loop that never reaches its end
Take a loop that keeps going while x is not exactly 1.0:
x = 0.0
while x != 1.0:
x += 0.1The condition x != 1.0 is true at every step, because the running total never becomes exactly 1.0. The loop does not end on its own.
To check how long it would run, the program below runs the same addition up to 10 million times and reports whether x ever equals 1.0. The loop stops early if it does. The limit is there so the program ends. Without the limit, the loop would not end.
Output:
hit 1.0 within 10000000 steps: FalseThe loop never equals 1.0 in 10 million steps. A loop written as while x != 1.0 would run without ending, and the program would appear to freeze.
A loop that misses its end value
A loop with <= looks safer, because it includes the end value. Try it with 0.0 to 0.3:
Output:
y = 0.0
y = 0.1
y = 0.2
iterations for y <= 0.3: 3The loop prints three values, but the list 0.0, 0.1, 0.2, 0.3 has four values. The fourth step gives 0.30000000000000004, which is larger than 0.3, so the condition y <= 0.3 is false and the loop stops before the end value. The end value was never reached, so the loop skipped it.
The same thing can happen at the end of any range, depending on the start, the step, and how many steps the sum takes. The loop cannot know the stored value has drifted above the end, so it does what the condition says.
Errors that grow over many steps
The drift is small in one step, but it adds up. The next program adds 0.1 one million times and prints the total. Since 1,000,000 times 0.1 is exactly 100000, a correct total would print 100000.0. The math.fsum function adds the same list with extra care for rounding, and it is included for comparison.
Output:
sum of 0.1 one million times: 100000.00000133288
math.fsum of 0.1 one million times: 100000.0The loop total is off by about 0.0000013. For many programs this is too much. A total of money, a physics simulation, or a loop that counts steps can all go wrong with this kind of error.
The built-in sum function also returns 100000.0 for this list in Python 3.14. That result is not the same as the loop above, which still drifts. Code that depends on it should not assume the same result on other versions.
Python's own float is 64-bit. A 32-bit value drifts faster. The struct module can round a number to 32 bits, so this program adds 0.1 ten times with 32-bit values:
Output:
float32 sum of 0.1 ten times: 1.0000001192092896
float32 equals 1.0: Falsestruct.pack("f", x) stores x as a 32-bit float, and struct.unpack reads it back as a Python float. Python has no separate 32-bit type in its core, so the output shows the 64-bit float that holds the 32-bit value. Its digits are more than the 1.0000001 that Java prints for a float, but both are the same stored value.
With 32-bit values, the error appears after only ten steps. A 32-bit type is not a safer choice for loops.
The fix: count with an integer
The method that avoids the problem is to count with an integer. An int holds whole numbers exactly, so the counter never drifts. The loop runs from 0 to the number of steps, and each decimal value is computed from the counter with one division. In Python, / always gives a float, so i / 10 is a decimal value even when i is an integer.
Output:
i=0 value=0.0
i=1 value=0.1
i=2 value=0.2
i=3 value=0.3
i=4 value=0.4
i=5 value=0.5
i=6 value=0.6
i=7 value=0.7
i=8 value=0.8
i=9 value=0.9
i=10 value=1.0range(11) gives the integers 0 to 10, so the loop runs exactly eleven times. The value at i = 10 is 10 divided by 10, which is 1.0 exactly. The value at i = 3 is 3 divided by 10, which prints as 0.3.
The division i / 10 is computed once for each value. The error does not build up, because each value is computed from the integer, not from the previous value. The result is rounded only once.
Why division beats repeated addition
The two ways to compute the value at step 3 give different results. Here is a program that compares them:
Output:
3 * 0.1 = 0.30000000000000004
3 / 10 = 0.3
3 / 10 == 0.3: TrueThe multiplication 3 * 0.1 uses the stored value of 0.1, which is slightly too large, so the product is slightly too large. The division 3 / 10 is rounded once to the nearest stored value of 0.3, which is the same value the literal 0.3 gets. So the comparison with 0.3 is true.
A range that does not start at zero
The same idea works for any start and end. The next example goes from 2.5 to 3.5 in four steps. It computes each value from the counter with the formula start + (end - start) * i / n:
Output:
i=0 value=2.5
i=1 value=2.75
i=2 value=3.0
i=3 value=3.25
i=4 value=3.5The numbers in this example are exact in binary, so the printed values are exact too. With other values, such as steps of 0.1, the printed values may show small rounding errors. The loop still ends at exactly i = n, so the end value is always produced. The multiplication happens before the division in (end - start) * i / n, and Python evaluates left to right, so the expression works as written.
Adding an integer counter to the condition
The loops above use an integer counter in the for header. You can keep the same idea in a while loop by counting steps with an integer and using it in the condition:
Output:
x = 1.2 after 12 steps, x == 1.0: FalseThe condition steps < 12 is the part that stops the loop. The x != 1.0 check is still there, but it cannot be relied on alone. Once the counter is the reason the loop ends, the drift cannot make the loop run forever.
Common mistakes
Using == or != on a float as the loop end
The loop while x != 1.0: never stops, as the earlier section showed. Any test that asks for exact equality with a decimal value can fail for the same reason. Use an integer counter as the end test instead.
When two decimal results must be compared, math.isclose checks whether they are equal within a small tolerance. The example below shows why it is needed:
Output:
False TrueStepping with <= past the end value
The loop while y <= 0.3: with a step of 0.1 runs three times, not four, because the fourth value is slightly above 0.3. A <= test does not save you when the stored value overshoots. Count with an integer.
Adding a step many times
Adding 0.1 one million times gives 100000.00000133288, not 100000.0. Multiplying a count by the step gives a value that is correct to one rounding step. For example, 1000000 * 0.1 prints 100000.0 in Python, so the answer is right for this count. Computing each value from the counter is the safer habit.
Using a 32-bit value
Python's own float is 64-bit, so the loops in this lesson behave the same way Java's double does. A 32-bit value drifts faster. The sum of ten 32-bit 0.1 values is 1.0000001192092896, and it does not equal 1.0. Use the 64-bit float for decimal loops, and still count with an integer.
Passing a decimal step to range
Python's range takes whole numbers only. A loop that uses a decimal step fails when it starts:
for i in range(0, 3, 0.5):
print(i)Running it prints a traceback, shown here with the folder names replaced by ...:
Traceback (most recent call last):
File ".../floating_point_loop.py", line 1, in <module>
for i in range(0, 3, 0.5):
~~~~~^^^^^^^^^^^
TypeError: 'float' object cannot be interpreted as an integerThe error says range expects an integer. Use a while loop with a float variable for the decimal value, or keep the counter an integer and compute the decimal value from it, as in the fix above.
Forgetting the count includes both ends
A range from 0 to 1 in steps of 0.1 has eleven values, not ten, because both 0 and 1 are included. A loop written as for i in range(10) prints only ten values and drops 1.0. The lesson on half-open ranges covers this in full.
Practice
Exercise 1: how many values does a loop from 0 to 1 in steps of 0.25 print?
Predict the count for this loop, then check it:
count = 0
x = 0.0
while x <= 1.0:
count += 1
x += 0.25
print(count)Show the answer
The count is 5. The values are 0.0, 0.25, 0.5, 0.75 and 1.0. Each of these is a sum of binary fractions that is exact, so the loop reaches 1.0 and the <= test is true at that step. The program prints 5.
Exercise 2: does a loop that steps by 0.1 and stops at 0.5 end?
Predict whether the loop below ends, and how many steps it takes.
Show the answer
The loop ends after 5 steps. The sums are 0.1, 0.2, 0.30000000000000004, 0.4 and 0.5, and the fifth sum is exactly 0.5. The check steps > 1000 is only a safety limit, and it does not fire. The output is steps until x == 0.5: 5. This is luck, not a rule: the same loop with an end value of 0.3 never hits it. In 1000 steps it passes 0.3 without landing on it.
Exercise 3: print eleven values from 0.0 to 1.0 without drift
Write a loop that prints the values 0.0, 0.1, 0.2 up to 1.0 using an integer counter, so that the end value is always printed.
Show the answer
for i in range(11):
value = i / 10
print(value)range(11) gives the integers 0 to 10, which is eleven values. Each value is i / 10, so there is no running total to drift. The printed values match the output in the integer counter section above.
Exercise 4: find the number of values in a range from 2.5 to 3.5 in n steps
For a range from start to end with n steps, how many values does the loop print, and why?
Show the answer
It prints n + 1 values: one for each step and one for the starting point. With n = 4, the loop runs over range(n + 1), which gives 0 through 4, and prints five values, as shown in the range example output. The loop includes both ends.
Exercise 5: check the sum of 0.1 without a loop
Write a program that computes the sum of 0.1 one million times using a counter and a multiplication, not a loop of additions. Print the result.
Show the answer
The output is 100000.0. Multiplying an integer count by the step is one rounding step, so the result is not built up from a million additions. This works well for a fixed step, but it is only a replacement for the loop when the loop does nothing else.
Summary
- Most decimal fractions, including 0.1, cannot be stored exactly in binary, so each stored value is a little off.
- Adding a step over and over lets the error build up, so the loop can skip its end value or never reach it.
- Use an integer counter as the loop variable and compute the decimal value from the counter.
- Do not use
==or!=on a float as the loop end, and do not rely on<=to catch an end value. - Use the 64-bit
floatrather than a 32-bit value for decimal loops, check the count of values at both ends of the range, and remember thatrangetakes whole numbers only.