1

Why are in some float multiplications in python those weird residuum?

e.g.

>>> 50*1.1
55.00000000000001

but

>>> 30*1.1
33.0

The reason should be somewhere in the binary representation of floats, but where is the difference in particular of both examples?

foxmulder
  • 21
  • 5
  • Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Guy Jan 15 '20 at 10:33
  • 2
    @Guy: No, it does not. That question and its answers discuss some floating-point rounding issues, albeit crudely and generally without the details needed for somebody to reproduce the behaviors, but the behavior asked about here involves choices of formatting that are separate from the rounding errors that occur in floating-point arithmetic operations. – Eric Postpischil Jan 15 '20 at 11:52

1 Answers1

1

(This answer assumes your Python implementation uses IEEE-754 binary64, which is common.)

When 1.1 is converted to floating-point, the result is exactly 1.100000000000000088817841970012523233890533447265625, because this is the nearest representable value. (This number is 4953959590107546 • 2−52 — an integer with at most 53 bits multiplied by a power of two.)

When that is multiplied by 50, the exact mathematical result is 55.00000000000000444089209850062616169452667236328125. That cannot be exactly represented in binary64. To fit it into the binary64 format, it is rounded to the nearest representable value, which is 55.00000000000000710542735760100185871124267578125 (which is 7740561859543041 • 2−47).

When it is multiplied by 30, the exact result is 33.00000000000000266453525910037569701671600341796875. it also cannot be represented exactly in binary64. It is rounded to the nearest representable value, which is 33. (The next higher representable value is 33.00000000000000710542735760100185871124267578125, and we can see …026 is closer to …000 than to …071.)

That explains what the internal results are. Next there is an issue of how your Python implementation formats the output. I do not believe the Python implementation is strict about this, but it is likely one of two methods is used:

  • In effect, the number is converted to a certain number of decimal digits, and then trailing insignificant zeros are removed. Converting 55.00000000000000710542735760100185871124267578125 to a numeral with 16 digits after the decimal point yields 55.00000000000001, which has no trailing zeros to remove. Converting 33 to a numeral with 16 digits after the decimal point yields 33.00000000000000, which has 15 trailing zeros to remove. (Presumably your Python implementation always leaves at least one trailing zero after a decimal point to clearly distinguish that it is a floating-point number rather than an integer.)
  • Just enough decimal digits are used to uniquely distinguish the number from adjacent representable values. This method is required in Java and JavaScript but is not yet common in other programming languages. In the case of 55.00000000000000710542735760100185871124267578125, printing “55.00000000000001” distinguishes it from the neighboring values 55 (which would be formatted as “55.0”) and 55.0000000000000142108547152020037174224853515625 (which would be “55.000000000000014”).
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1
    Using just enough digits is more and more common. It should be the default for repl. It is used in scheme, some Smalltalk (squeak, pharo, gst), Python repr, ruby to_s etc... – aka.nice Jan 15 '20 at 20:00