1

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.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
Fabian Röling
  • 1,227
  • 1
  • 10
  • 28
  • 1
    I didn't use the [tag:java] tag, because IEEE-754 is a language agnostic standard, but in case it matters, I used Java 11 to do my checks. – Fabian Röling May 11 '20 at 11:41
  • [This question](https://stackoverflow.com/q/9902968/6743127) looks related, but it's actually about a bug in Java 6 and before, so that's not really relevant for this question. – Fabian Röling May 11 '20 at 11:48
  • 1
    The language _does_ matter, because it affects what integer types are available and what `int` and `float` mean: your question isn't purely about IEEE 754. It's also important to specify which IEEE 754 format you're thinking of. But yes, if you allow 64-bit ints (for example), and assume binary64 floating-point, then there are cases of integers `x` and `y` where `x / y` is exactly representable as a float, but `float(x) / float(y)` doesn't give the exact float. For just one example, take `x = 2135497568948934666` and `y = 305071081278419238`. Similarly if you're using 32-bit ints and floats. – Mark Dickinson May 11 '20 at 15:25

2 Answers2

3

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.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
  • Thanks! I can reproduce it in Java with `int` and `float`. Your example from the comment (which you could integrate into your answer BTW) as well, with `long` and `double`. So using `double` is indeed the solution for a reliable `int`/`int` division. How have you found your examples? Just letting a program check all integers and low multiples of them for this property? And do you have a similar example for .49999…? – Fabian Röling May 11 '20 at 15:59
  • @FabianRöling: Much easier than that: For the 32-bit example, I just picked a random integer `x` smaller than `2**31 / 15` and tested `(15 * x) / (3 * x)`. The first `x` I tried failed to give an example, but the second succeeded. For the 64-bit case, I did something similar and the first random `x` I tried worked. There aren't just a handful of such examples; there are _lots_. :-) – Mark Dickinson May 11 '20 at 16:06
  • An example for `0.499999...` would be hard with IEEE 754 binary floating-point: if you have a case where the true value of `x / y` is _exactly_ `0.5`, then `(float)x / (float)y` is also always going to give you exactly `0.5`, essentially because `(float)x` and `(float)y` will have rounded in exactly the same way (every binade behaves the same with respect to rounding). You could definitely find examples for `1.5`, though. – Mark Dickinson May 11 '20 at 16:11
  • I added an example for `1.5`, and a note about the impossibility of an example for `0.5`. – Mark Dickinson May 11 '20 at 16:38
  • That example would still round correctly with `Math.round` in Java, but it would round differently than `Math.round` usually behaves for -1.5 when doing `Math.round((float)-592534758/(float)395023172)`. It would round -1.5000001 to -2, unlike its "normal" (but weird) rounding of -1.5 to -1. – Fabian Röling May 11 '20 at 17:03
  • Ah yes; good point. If you're after something that would round the wrong way, then try `x = 1418199327` and `y = 945466218`. Then `x / y` is again exactly 1.5, but `(float)x / (float)y` will compute it as `1.49999988079071044921875`. – Mark Dickinson May 11 '20 at 17:22
  • But wait a second: Java rounds `-1.5` to `-1` by default? I know JavaScript does this, but I'm surprised that Java does it. – Mark Dickinson May 11 '20 at 17:23
  • Yes. And it's pretty annoying. I had to check for <0 and use `-Math.round(-quotient)`. Your previous comment is exactly what my question was about, can you please add that to the answer? – Fabian Röling May 11 '20 at 18:47
2

It's not entirely clear what you're asking: what do you mean by 'fail'? I think you're wondering if it's possible for there to be integer values for dividend and divisor in your first line and the result to not be what you'd expect because of rounding issues.

That's possible I think. In the code below 999,999,999/2,000,000,000 is slightly less than 0.5 so we expect the result to be 0.0. However in Java the result is 1.0. If you do Math.round(dividend/divisor) you do integer division and get the right answer.

int dividend = 999999999;
int divisor = 2000000000;
float result = Math.round((float)dividend/(float)divisor);
System.out.println(result);  // 1.0

Is that what you meant? The problem here isn't that there's a float that doesn't round correctly, but that in casting integer to float you're losing accuracy. float(dividend) is actually 1.0E9.

Rich N
  • 8,939
  • 3
  • 26
  • 33
  • Yes, I meant something like this, but backwards. For example if "1/2" turned out to be "0.499999997" (which it doesn't, but maybe there's a similar case somewhere with other numbers). Would using `double` always solve this (what you wrote), in all cases? – Fabian Röling May 11 '20 at 13:30