Repeating what was said in the comments. The 32-bit version of "4319.9997" is actually closer to "4319.9995". When numpy/Python/C tries to convert "4319.9998" from a 64-bit float to a 32-bit float the only two options are either "4319.9995" or "4320.0" and "4320.0" is closer so it rounds up. I can't say this is exactly how this is happening, but it makes some sense to me.
Original Answer:
I can't say I fully understand what I'm about to answer, but some of this makes sense. I think it comes down to how Python (or C or something else) converts the string literal to a 32-bit float.
I took the binary printing function from:
https://stackoverflow.com/a/16444778/433202
import struct
def binary(num):
return ''.join('{:0>8b}'.format(c) for c in struct.pack('!f', num))
And used it to print out the numbers as 32-bit IEEE floats:
In [62]: binary(4319.9997)
Out[62]: '01000101100001101111111111111111'
In [63]: binary(4319.9998)
Out[63]: '01000101100001110000000000000000'
So that's 0 for sign, 10001011 for the exponent portion, and 000011011111...for the fractional significand portion.
So when those string literals I'm entering get converted to a double, it hits some threshold and the fractional portion gets a 1 added to it which rolls all the bits up into the whole number portion of the significand.
The big misconception/misunderstanding of this whole thing for me is that when I was told that casting a float to int would "truncate" the fractional portion of the number I assumed it was doing this in base-10, but it (C?) is actually doing it in base-2. This makes obvious sense, but I had never thought about it until this issue.
The part I still don't understand is why the conversion from the float string literal that I type "4319.9998" gets bumped to the next number (+1). Why not accept the precision issue and keep it as the same value as "4319.9997"? I made a 64-bit (double) version of the binary function and when I print out these two versions of the numbers:
In [91]: binary64(4319.9997)
Out[91]: '0100000010110000110111111111111111101100010101101101010111010000'
In [92]: binary64(4319.9998)
Out[92]: '0100000010110000110111111111111111110010111001001000111010001010'
If you count the bits and separate things out for 64-bit floating point representation, both values have many 1s after the "whole number" portion of the signficand (after shifting the .
over exponent number of digits) so I'm not sure why one would be rounded up and the other not.