I was trying to track down some very weird Java behavior. I have a formula that involves a double, but is "guaranteed" to give an integer answer -- specifically, an unsigned 32-bit integer (which, alas, Java doesn't do well). Unfortunately, my answers were sometimes incorrect.
Eventually I found the issue, but the behavior is still very odd to to me: a double
cast directly to an int
seems to be capped at the MAX_INT
for a signed integer, whereas a double
cast to a long
that is then cast to an int
gives me the expected answer (-1; the MAX INT of an unsigned 32-bit integer represented as a signed 32-bit integer).
I wrote a little test program:
public static void main(String[] args) {
// This is the Max Int for a 32-bit unsigned integer
double maxUIntAsDouble = 4294967295.00;
long maxUintFromDoubleAsLong = (long)maxUIntAsDouble;
long maxUintFromDoubleAsInt = (int)maxUIntAsDouble;
int formulaTest = (int) (maxUintFromDoubleAsLong * 1.0);
int testFormulaeWithDoubleCast = (int)((long) (maxUintFromDoubleAsLong * 1.0));
// This is a more-or-less random "big number"
long longUnderTest = 4123456789L;
// Max int for a 32-bit unsigned integer
long longUnderTest2 = 4294967295L;
int intFromLong = (int) longUnderTest;
int intFromLong2 = (int) longUnderTest2;
System.out.println("Long is: " + longUnderTest);
System.out.println("Translated to Int is:" + intFromLong);
System.out.println("Long 2 is: " + longUnderTest2);
System.out.println("Translated to Int is:" + intFromLong2);
System.out.println("Max UInt as Double: " + maxUIntAsDouble);
System.out.println("Max UInt from Double to Long: " + maxUintFromDoubleAsLong);
System.out.println("Max UInt from Double to Int: " + maxUintFromDoubleAsInt);
System.out.println("Formula test: " + formulaTest);
System.out.println("Formula Test with Double Cast: " + testFormulaeWithDoubleCast);
}
When I run this little program I get:
Long is: 4123456789
Translated to Int is:-171510507
Long 2 is: 4294967295
Translated to Int is:-1
Max UInt as Double: 4.294967295E9
Max UInt from Double to Long: 4294967295
Max UInt from Double to Int: 2147483647
// MAX INT for an unsigned int
Formula test: 2147483647
// Binary: all 1s, which is what I expected
Formula Test with Double Cast: -1
The bottom two lines are the ones I'm trying to understand. The double cast gives me the expected "-1"; but the straight cast gives me MAX_INT for a 32-bit signed integer. Coming from a C++ background, I would understand if it gave me an "odd number" instead of the expected -1 (aka "naive casting"), but this has me perplexed.
So, to the question then: is this "expected" behavior in Java (e.g. any double
cast directly to an int
will be "capped" to MAX_INT
)? Does casting do this for any unexpected types? I would expect it to be similar for short
and byte
, for instance; but what is the 'expected behavior' when casting an oversized-double to float?
Thanks!
This is just the way the language spec is written. Converting a floating-point to an integer type, if the value is too large for the destination then the maximum value is substituted. In a narrowing conversion from one integer type to a smaller one, the high-order bits are discarded.
See the JLS 5.1.3. Narrowing Primitive Conversion
So, the answer to the question in the title is "yes".
This is expected behavior. Remember that there are no primitive unsigned long or int types in Java, and the Java Language Specification (Java 7) for Narrowing primitive conversion (5.1.3) states that casting a "too small or too large" floating point value (be it double or float) to an integral type of int or long will use the minimum or maximum value of signed integral types (emphasis mine):
The first case
int formulaTest = (int) (maxUintFromDoubleAsLong * 1.0);
thus promotesmaxUintFromDoubleAsLong
to a double via multiplication and then casts it to an int. Since the value is too large to represent as a signed integer, the value becomes 2147483647 (Integer.MAX_VALUE) or 0x7FFFFFFF.As for the latter case:
So
int testFormulaeWithDoubleCast = (int)((long) (maxUintFromDoubleAsLong * 1.0));
first promotes maxUintFromDoubleAsLong to double, back to long (still fitting) and then to an int. In the last cast, the excess bits are simply dropped, leaving you with 0xFFFFFFFF, which is -1 when interpreted as a signed integer.