1

I have read this article NUM00-J. Detect or prevent integer overflow and this question How does Java handle integer underflows and overflows and how would you check for it?.

As you can see, there are many solutions to prevent integer overflow when multiplying an integer by an integer. But I wonder is there any solution to prevent integer overflow when multiplying an integer by float?

My current (silly) solution:

public static final int mulInt(int a, float b) {
    double c = a * b;
    return c > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)c;
}

But it has a lot of problems:

  1. It can get the expected result when multiplying performs multiplication with both parameters having to be small numbers.
  2. When either parameter is large digits the result is bound to be incorrect (I know in part because of the floating-point data type).
  3. Suppose if the result of the calculation is even greater than the maximum value of double, it will be unstoppable and return a negative number.

So, what is the real solution to this problem?

Your answer will be very helpful, I will appreciate it!

UPDATE: There is another question here How can I check if multiplying two numbers in Java will cause an overflow? that is quite similar BUT it is about multiplying an integer by an integer instead of multiplying by a float.

  • Does this answer your question? [How can I check if multiplying two numbers in Java will cause an overflow?](https://stackoverflow.com/questions/1657834/how-can-i-check-if-multiplying-two-numbers-in-java-will-cause-an-overflow) – Ervin Szilagyi Oct 03 '21 at 17:04
  • For positive values at least, int x int cannot exceed the range of long. So `long c = (long) a * (long) b`. Then check the result before casting back to int, or use Math.toIntExact.. Handling one or both negative numbers left as exercise for the reader. – user16632363 Oct 03 '21 at 17:24
  • @ervin-szilagyi Are not! Entire integer and long. I am in need of integer and float! – Tần Quảng Oct 03 '21 at 19:52
  • @user16632363 But the question I have is integer with float. If you cast `b` to int or long, the result will definitely not be true because it will remove the decimal part of `b`! – Tần Quảng Oct 03 '21 at 19:58
  • Well sure, it will remove the decimal part. Exactly what you are trying to do here: `Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)c` – Ervin Szilagyi Oct 03 '21 at 20:08
  • @ervin-szilagyi You can see I said that's my current "silly" solution. I multiply them by a variable of data type double, then check if the result exceeds the maximum integer value, if so, return the integer maximum value, otherwise return the number closest to the result. Why is the result? Because the maximum error for removing the decimal part cannot be greater than 1 (0 - 0.99999...). If I remove the decimal part of `b`, the result can be greatly skewed. But this solution is not perfect, it has the disadvantages I listed above! – Tần Quảng Oct 03 '21 at 20:15

1 Answers1

0

Below is a C approach that may shed light in Java.

Perform the multiplication using double, not float math before the assginment to gain the extra precision/range of double. Overflow is not then expected.

A compare like c > Integer.MAX_VALUE suffers from Integer.MAX_VALUE first being converted into a double. This may lose precision.*1 Consider what happens if the converted value is Integer.MAX_VALUE + 1.0. Then if c is Integer.MAX_VALUE + 1.0, code will attempt to return (int) (Integer.MAX_VALUE + 1.0) - not good. Better to use well formed limits. (Negative ones too.) In C, maybe Java, floating point conversion to int truncates the fraction. Special care is needed near the edges.

#define INT_MAX_PLUS1_AS_DOUBLE ((INT_MAX/2 + 1)*2.0)

int mulInt(int a, float b) {
  // double c = a * b;
  double c = (double) a * b;
  
  //return c > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)c;
  if (c < INT_MAX_PLUS1_AS_DOUBLE && c - INT_MIN > -1.0) {
    return (int) c;
  } 
  if (c > 0) return INT_MAX;
  if (c < 0) return INT_MIN;
  return 0; // `b` was a NaN
}

c - INT_MIN > -1 is like c > INT_MIN - 1, but as INT_MIN is a -power-of-2, INT_MIN - 1 might not convert precisely to double. c - INT_MIN is expected to be exact near the edge cases.


*1 When int is 32-bit (or less) and double is 64-bit (with 53-bit significand) not an issue. But important with wider integer types.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I have tried converting the code you give to Java code and testing it on [OnlineGDB](https://onlinegdb.com/LtzvTRJqjV), it seems correct to do calculations where `b` are numbers **without** decimal part, but if there is decimal part, the result willn't be correct. – Tần Quảng Oct 05 '21 at 12:02
  • @TầnQuảng `System.out.println(mulInt(268435455, 7.9F)); // 2120640094.5` is unclear. `mulInt()` returns an `int`. Why expect `2120640094.5`? What did code print for you? (2120640120), what was expected? – chux - Reinstate Monica Oct 05 '21 at 13:50
  • @chux-reinstate-monica `2120640094.5` is the correct result of the calculation, but the result I expect is `2120640094` (error: `0.5`) and not `2120640120` (error: `26`). – Tần Quảng Oct 05 '21 at 16:39
  • @TầnQuảng `7.9F` does not have the value of 7.9. Instead it has the value of the nearest `float` or 7.900000095367431640625. The product 268435455 * 7.900000095367431640625 is about 2,120,640,120.0999999046.... Truncated that is 2120640120, the reported and correct answer. Your expectation is incorrect as `7.9F` is not 7.9. Instead, try `b` with a fractional _exact_ part representable as a `float` like 0.5, 0.0625, 0.75, 0.125, 0.900000095367431640625 ... – chux - Reinstate Monica Oct 05 '21 at 16:57
  • @TầnQuảng Recall each `float` is some limited integer divided by a power-of-2. 7.9 is not such a number. 7.900000095367431640625 is: 16,567,501/(pow(2,21). – chux - Reinstate Monica Oct 05 '21 at 17:01
  • @chux-reinstate-monica I thought for a bit, can actually change `b` to `double` if an exact result is needed, at least its performance and speed will be more stable than using `Math.round` right? – Tần Quảng Oct 05 '21 at 19:08
  • @TầnQuảng " change b to double if an exact result is needed" --> No. The problem still exist yet rather than 9 decimal places out, more like 17. Edge cases with the `int` truncation will still plague code. If `mulInt()` is to provide a _rounded to the nearest `int`_ rather than a truncated fraction, then yes, use `double c = Math.round((double) a * b);` . That step does simplify the following range test a bit. `if (c < INT_MAX_PLUS1_AS_DOUBLE && c >= INT_MIN) {` – chux - Reinstate Monica Oct 05 '21 at 19:16
  • @TầnQuảng ".... its performance and speed will be more ...." --> Get functionality correct _first_, then worry about speed. – chux - Reinstate Monica Oct 05 '21 at 19:21
  • @chux-reinstate-monica I mean use `Math.round` for `c` in `return` and not in calculation. I asked the same question on the coderanch, they said that "multiplying `Integer.MAX_VALUE` by `Float.MAX_VALUE` also cannot overflow the maximum value of double" (although I have tried multiplying and the result is: Infinity). I use `int` for `a` because most cases of `a` are `int` (I very rarely use `long` in my code), and `b` is usually modules, data types is usually float, there are only a few cases where `b` is `int`. – Tần Quảng Oct 05 '21 at 20:02
  • @TầnQuảng < "multiplying Integer.MAX_VALUE by Float.MAX_VALUE also cannot overflow the maximum value of double" (although I have tried multiplying and the result is: Infinity).> --> because you did `a * b` and not `(double) a * b`. Do you see now why one can overflow and the other does not? – chux - Reinstate Monica Oct 05 '21 at 20:24
  • @TầnQuảng I recommend to use `Math.round()` as in `double c = Math.round((double) a * b);`. With `double c = (double) a * b;` and `c` had the value 0.999, would you the function to to return 1 or 0? – chux - Reinstate Monica Oct 05 '21 at 20:25
  • @chux-reinstate-monica I don't care about the decimal part of `c` when returning because the error is not too big (<= 0.9999...). But I wonder one thing: If I write `mulInt(int a, double b)` but then cast `a` to `double`: `double c = (double)a * b` then why not write directly to `mulInt(double a, double b)`? Is it any different? – Tần Quảng Oct 05 '21 at 22:54
  • @TầnQuảng I've directly answered many of your questions, yet ones like "value 0.999, would you the function to to return 1 or 0?" go indirectly answered with "I don't care ... because the error is not too big". Is an error of 1.0 OK, 1.0001, 2.0 ... (Seems error of 26) is too much. What is the error threshold? That limit affects parameter type selection. You could do your multiplication before the function call and then call something like `int FP_to_int_with_minmax_limit(double x);`, IMO, a better approach than combining a approximate multiplication and conversion. – chux - Reinstate Monica Oct 05 '21 at 23:50
  • @chux-reinstate-monica Sorry for not answering your question directly. What I mean is that the error of `c` when cast from `double` to `int` is insignificant to me (because the error is always less than 1 and `c` is the return result, not the actual output perform any further calculations). So now I need something that optimizes the speed and precision of math at `c`, that's why I asked if you should I use `Math.round` if I want to improve speed and math precision?! – Tần Quảng Oct 06 '21 at 04:03