6

Could someone specifically explain why the last digit is 5 instead of 2 or 4? Knowing a float must have an inaccuracy in the binary world, I can't get exactly how the result of the case above has been made in detail.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Jacob
  • 71
  • 1
  • 2
  • 7
    Read carefully, folks. This isn't the standard "why is this floating point result inaccurate" question. It's more specific: why is the error more than +/-1 in the last digit? – John Kugelman Dec 17 '16 at 02:54
  • 1
    @JohnKugelman Because binary representation of the number, which seems to be addressed in the other post. Is that not correct? – OneCricketeer Dec 17 '16 at 03:06
  • 3
    @cricket_007: "Because binary", while accurate in a certain sense, is missing too many logical steps for it to qualify as a real explanation. The questioner already knows that floats are binary. – user2357112 Dec 17 '16 at 03:20
  • ObLink: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). And there's lots of good info in the answers to [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – PM 2Ring Dec 17 '16 at 07:15

2 Answers2

7

It's because it's the closest a (64bit) float can be to the "true value" of 10/3.

I use cython here (to wrap math.h nexttoward function) because I don't know any builtin function for this:

%load_ext cython

%%cython
cdef extern from "<math.h>" nogil:
    # A function that get's the next representable float for the 
    # first argument in direction to the second argument.
    double nexttoward(double, long double)

def next_float_toward(double i, long double j):
    return nexttoward(i, j)

(or as pointed out by @DSM you could also use numpy.nextafter)

When I display the values for 10/3 and the previous and next representable float for this value I get:

>>> '{:.50}'.format(10/3)
3.3333333333333334813630699500208720564842224121094

>>> '{:.50}'.format(next_float_toward(10/3., 0))
3.3333333333333330372738600999582558870315551757812

>>> '{:.50}'.format(next_float_toward(10/3., 10))
3.3333333333333339254522798000834882259368896484375

So each other representable floating point value is "further" away.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • 1
    I don't know any built-in function either, but `np.nextafter` is probably simpler.. (although in the answer I was working on I just used `.hex()` and `float.fromhex` to build it manually.) – DSM Dec 17 '16 at 03:20
6

Because the largest representable value smaller than ...335 is ...330 (specifically, it's smaller by 2**-51, or one Unit in the Last Place). ...333 is closer to ...335 than it is to ...330, so this is the best possible rounding.

There are a number of ways to confirm this; MSeifert shows one thing you can do directly with <math.h> from C or Cython. You can also use this visualization tool (found by Googling "floating point representation" and picking one of the top results) to play with the binary representation.

Kevin
  • 28,963
  • 9
  • 62
  • 81