Bonus: Round-off error

Tags

Bonus lessons are optional.

Try this in the console:

  • 0.1 * 3
Reflect

What do you expect? What did you get?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
Georgina

Huh? It should be 0.3, but I got 0.30000000000000004!

Right.

It happens because of the way compies do math. We humans use base 10, where we have ten digits, 0 through 9. When we get to 9 and add 1, we add another place to the left: 10. Get to 99, add 1, we get 100. Another place to the left.

Compies aren't like us. They use binary, or base 2. We have 10 digits, compies only have two: 0 and 1. They count like this: 1, 10, 11, 100, 101, 110, 111, 1000... Each place contains a binary digit, or bit.

Ray
Ray

You'd need lotsa bits for a large number, like 3,223,399.

Indeed! In binary, that's 1100010010111101100111.

I used the bin() function to work that out.

When you give Python an expression in decimal, like 0.1 * 3, Python converts each piece into binary, does the calculation in binary, and converts the result back into decimal.

Trouble is, those conversions can give you round-off error, since calculations that are round in decimal might not be round in binary, and vice versa.

Usually, this isn't a problem. The error in 0.30000000000000004 is..., well, very small. Quadrillionthish small. Apply the round function to it, and the error will go away.

Adela
Adela

You have but-face. You're about to say but.

Aye. But try this in the console.

  • 0.1 * 3 == 0.3
Reflect

Wadja get?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ethan
Ethan

False. That's not good.

Aye, not good.

Check out this code.

  1. num = 0.0
  2. while num != 0.3:
  3.     user_input = float(input('What number when multiplied by 3 will give you 0.3? '))
  4.     num = user_input * 3.0
  5.     if num != 0.3:
  6.         print("That's not it. Please try again.")
  7. print('OK, bye!')
Reflect

Run the program. What happened? Why?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
Georgina

Well, that's a pain. The program loops forever. I had to Ctrl+C to stop it.

I put in 0.1 for the input. Line 4 multiples that by 3. We should get 0.3, but the debugger says we actually get 0.30000000000000004.

The while in line 2 compares 0.30000000000000004 to 0.3. They aren't the same, so the loop keeps going.

Aye! And you're right, it is a pain.

It won't be a big problem for the rest of this course, since we'll use loops that aren't affected by the issue. But you might come across it later if you do more with Python.

There are a coupla solutions. An easy one is in Python's math library, the function isclose. It returns True if two floats are nearly equal, False if they aren't. It only allows a tiny difference.

Try this.

  1. import math
  2.  
  3. num = 0.0
  4. while not math.isclose(num, 0.3):
  5.     user_input = float(input('What number when multiplied by 3 will give you 0.3? '))
  6.     num = user_input * 3.0
  7.     if not math.isclose(num, 0.3):
  8.         print("That's not it. Please try again.")
  9. print('OK, bye!')

isclose is in the math library, so you need to import that first. You can see the new tests in lines 4 and 7.

Reflect

Try it. What happened?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ray
Ray

It worked!

Yay!

Bee tee dubs, this is a problem in every language I've worked with.

Summary

  • Round-off error happens because calculations that are round in decimal might not be round in binary.
  • Usually it isn't an issue because the differences are tiny, but sometimes it causes problems.
  • isclose in the math library returns true if two floats are nearly equal.