2

I have recently learned about one of the main limitations of floating points: The fact that some numbers can not be represented properly in binary and might therefore give answers which are not accurate enough for your purpose.

Knowing that round(2.675, 2) and round(2.665, 2) both equal 2.67 I tried to write some code that would give a list of numbers that have this property (being rounded improperly).

See my code below or in this replit: https://repl.it/@FTBlover123/Float-Rounding-Test

Test = True
number1 = 0.005
number2 = 0.015
count = 0
count_per_test = 5
while Test:
    if round(number1, 2) == round(number2, 2):
        print number1, number2, round(number1, 2), round(number2, 2)
        count += 1
    else:
        pass
    number1 += 0.005
    number2 += 0.005
    if count == count_per_test:
        answer = raw_input("End Program? Y / N: ")
        if answer == "Y":
            print "Program Ended"
            Test = False
        elif answer == "N":
            print "Searching For %s more rounding errors" % (count_per_test)
            count = 0
        else:
            print "Error, raw_input incorrect"
            Test = False

#2.675 is a known number with a rounding error.
print 2.665, 2.675, round(2.675, 2), round(2.665, 2)
#79.705 should have one according to the results, but it doesn't truly.
print 79.695, 79.705, round(79.695, 2), round(79.705, 2)

The final 2 prints are an example of one.

Though the code indeed seems to return some rounding errors due to the limitations of float, these seem to be non-true values (except for the first one). This is because I am using the float 0.005 (which can not be expressed in binary) which itself causes the new number1 and number2 to be the incorrect version of its already inaccurate self! This is easily demonstrated by incrementing number1 and number2 by 0.001 (which can also not be expressed in binary), as this also results in answers which are still incorrect, but also different!

Thus, my questions:

Is it possible to create code that checks for the rounding errors due to float limitations correctly, without it suffering from the exact flaw we're trying to test for? (Though a library of such examples would also be appreciated I am much more interested in fixing the code)

The separation between my incorrect outputs seems to increase somewhat exponentially.

  • 0.005 0.015 0.01 0.01
  • 0.035 0.045 0.04 0.04
  • 1.025 1.035 1.03 1.03
  • 21.535 21.545 21.54 21.54

  • 79.695 79.705 79.7 79.7

  • 9164.075 9164.085 9164.08 9164.08

  • 36933.455 36933.465 36933.46 36933.46

What causes this? Is this also true for the correct incorrectly rounded floats?

martineau
  • 119,623
  • 25
  • 170
  • 301

2 Answers2

1

Well, as you've noticed, you can't just keep adding 0.005, because 0.005 is not representable as a floating point number.

What to do instead? Use numbers that ARE representable!

2675 is exactly representable as a float (every integer up to 2^53 is), and so is 1000. So 2675.0/1000.0, while not representable as a float, will be rounded to the closest representable float, just as the literal 2.675 would be. (For that matter, you could use 267.5/100.0, but best to stick to integers, I think.)

So rather than increment the value you intend to check in this way, increment the numerator, and do the division each time.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
  • Python does not have a strict specification about floating-point. The floating-point behavior is implementation-dependent, with implementations typically relying on the underlying platform. So whether the end of the consecutively representable integers is 2^53 is not guaranteed. It is common, these days, but implementations might choose to use a wider or narrower floating-point format, and they might choose a decimal format on hardware that supports it (or if they have a special purpose for choosing it). – Eric Postpischil Aug 14 '18 at 18:20
0

First of all, for more information on python internal floating point arithmetic read this. Because your example uses the same values (Rounding 2.675 and getting 2.67 but you expect 2.68), you maybe already read this document.

Now I try to answer some of your questions:

Q: Is it possible to create code that checks...?

A: Yes, but you need to use some other arithmetic than float. In your case if you want to check why the value is rounded down you can view the float representation of the value with the decimal module.

You write "79.705 should have one according to the results, but it doesn't truly."

That is not correct:

>>> from decimal import Decimal
>>> t = 79.695
>>> print (Decimal(t))
79.69499999999999317878973670303821563720703125

As you can see, the internal float representation differs from the decimal value, because python already rounds if you use something like 'print' on those values. This module can also be used for decimal floating point arithmetic if the 'normal' binary floating point arithmetic is not good enough.

The question is, why do you want a list with such numbers? Because most of the time, the representation error is no problem at all. There are only some cases where you have to think about that problem.

Frieder
  • 1,208
  • 16
  • 25