Search code examples
javaroundingprecisionieee-754

Does a float exist that wrongly evaluates to slightly below .5 or .0?


I'm using this to get a properly rounded integer division:

Math.round((float)dividend/(float)divisor)

Division by 0 is already handled elsewhere. Other than that, could this ever fail?
I could imagine the result of that division being a float that should be something.5, but actually evaluates to something.4999997 or so. Does such a float exist?

To check this myself, I tried to print out all floats that are close to .5 like this:

for(int i=Integer.MIN_VALUE+1; i!=Integer.MIN_VALUE; i++){
   float f=Float.intBitsToFloat(i);
   if ((f>(int)f+0.4999999 && f<(int)f+0.5000001 && f!=(int)f+0.5)
     ||(f<(int)f-0.4999999 && f>(int)f-0.5000001 && f!=(int)f-0.5)){
      System.out.println(Float.toString(f));
   }
}

(Integer.MIN_VALUE was checked manually.)

That only printed:

-0.4999999
-0.49999994
-0.49999997
-0.50000006
0.4999999
0.49999994
0.49999997
0.50000006

Since -0.5 and 0.5 are definitely floats, that would mean that no such float exists. But maybe there's a possibility that my range is too small and a problematic numbers goes outside of it. I wanted a second opinion anyway, so I'm asking here, maybe someone has advanced inside knowledge about IEEE754 and has proof why this absolutely cannot ever happen.

Broadening the range by one digit had this result, none of those numbers answer this question.

Related question: What about something.99999? A similar test had this result. All those floats are distinct from the floats for their exact integers.


Solution

  • Short answer: yes, it could fail.

    Longer answer: your question depends on exactly which integer and floating-point formats you're considering - it's not purely a question about IEEE 754.

    For example, if you're looking at the IEEE 754 "double precision" binary64 type (let's assume your language calls it double), and only considering a fixed-width 32-bit integer (signed or unsigned), then every such integer is exactly representable as a binary64 float, so in that case conversion to double doesn't change the value, and you can indeed rely on (double)x / (double)y being correctly rounded according to the current rounding mode (assuming that whatever language you're using guarantees that the division is correctly rounded according to the current rounding mode, for example because it guarantees to follow the IEEE 754 standard).

    If on the other hand float is the IEEE 754 binary32 "single-precision" floating-point type, then there are examples of 32-bit signed integers x and y such that x is exactly divisible by y (so that the quotient is a small, perfectly representable, integer), but (float)x / (float)y gives a different value. One such example is x = 296852655 and y = 59370531. Then x / y is exactly 5, but the closest binary32 float to x is 296852640.0, the closest binary32 float to y is 59370532.0, and the closest binary32 float to the quotient 296852640.0 / 59370532.0 is exactly 4.999999523162841796875, one ulp away from 5.0.

    Similar examples exist with binary64 "double precision" IEEE 754 floating-point and 64-bit integers. For example, with x = 2135497568948934666 and y = 305071081278419238, both x and y fit into a signed 64-bit integer type, x / y is exactly 7, but (double)x / (double)y is 6.99999999999999911182158029987476766109466552734375, again 1 ulp away from the exact result.

    Here are a couple more examples that are closer to the question you asked in the title: assuming IEEE 754 single-precision arithmetic again, take x = 592534758 and y = 395023172, which are both representable as signed 32-bit integers. Then x / y is exactly 1.5, but (float)x / float(y) is 1.50000011920928955078125. For an example where the rounding goes the other way, if x = 1418199327 and y = 945466218, then x / y is 1.5 and (float)x / (float)y is 1.49999988079071044921875.

    However, it's worth mentioning that no such examples exist for 0.5: if x and y are 32-bit signed integers such that x / y is exactly 0.5, then than means that y is exactly twice x, and that implies that (float)y will be exactly twice (float)x (because if (float)x is the closest representable value to x then 2.0 * (float)x must be the closest representable value to 2.0 * x). So in that case, (float)x / (float)y is guaranteed to be 0.5, too.