Search code examples
pythoncomputer-science

Why exact binary representation through division but not multiplication?


Parts of this question have been addressed elsewhere (e.g. is floating point math broken?).

The following reveals a difference in the way numbers are generated by division vs multiplication:

>>> listd = [i/10 for i in range(6)]
>>> listm = [i*0.1 for i in range(6)]
>>> print(listd)
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
>>> print(listm)
[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5]

In the second case, 0.3 has a rounding error of about 1e-16, double floating point precision.

But I don't understand three things about the output:

  1. Since the only numbers here exactly representable in binary are 0.0 and 0.5, why aren't those the only exact numbers printed above?
  2. Why do the two list comprehensions evaluate differently?
  3. Why are the two string representations of the numbers different, but not their binary representations?
>>> def bf(x):
        return bin(struct.unpack('@i',struct.pack('!f',float(x)))[0])
>>> x1 = 3/10
>>> x2 = 3*0.1
>>> print(repr(x1).ljust(20), "=", bf(x1))
>>> print(repr(x2).ljust(20), "=", bf(x2))
0.3                  = -0b1100101011001100110011011000010
0.30000000000000004  = -0b1100101011001100110011011000010

Solution

  • Answering each question:

    1. Since the only numbers here exactly representable in binary are 0.0 and 0.5, why aren't those the only exact numbers printed above?

    Python rounds off the display of any floating point number to the shortest literal that produces the same value when evaluated. So yes, many of those numbers aren't actually the same as the actual number they represent, but if you typed them in in Python, you'd get that (slightly inaccurate) value without the math.

    1. Why do the two list comprehensions evaluate differently?

    0.1 is already inaccurate, as you've stated, so multiplying by it is not exactly equivalent to dividing by 10 (where at least both inputs are precise integers). Sometimes that inaccuracy means the result is not the same as dividing by 10; after all, you multiplied by "just over one tenth", not "one tenth".