Java整型原始类型的强制转换是否在转换类型的MAX_INT处“截断”?

8

我试图追踪一些非常奇怪的Java行为。我有一个包含双精度浮点数的公式,但“保证”给出整数答案--具体来说,是无符号32位整数(遗憾的是,Java不擅长处理这种类型)。不幸的是,我的答案有时是错误的。

最终我找到了问题所在,但这个行为仍然对我来说非常奇怪:将一个double直接强制转换为int似乎被截断为带符号整数的MAX_INT, 然而将一个double强制转换为一个long, 然后再将其转换为int会给我预期的答案(-1;表示为有符号32位整数的无符号32位整数的MAX_INT)。

我写了一个小测试程序:

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);
}

当我运行这个小程序时,我会得到以下结果:
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

我试图理解的是最后两行。双重转换将给出预期的“-1”;但是直接转换却给出了32位有符号整数的MAX_INT。作为一个C++背景下的人,如果它给我一个“奇数”而不是预期的-1(即“天真的转换”),我会理解的,但是这让我感到困惑。
那么问题来了:这是Java中的“预期”行为吗(例如,任何double直接转换为int都将被“限制”为MAX_INT)?对于任何意外类型,转换是否会执行此操作?例如,我会期望它对于short和byte也是类似的;但是当将超大的double转换为float时,它的“预期行为”是什么?
谢谢!
2个回答

11

这是预期的行为。请记住,Java没有原始的unsigned long或int类型,并且Java语言规范(Java 7)有关缩小原始转换(5.1.3)的规定指出,将“太小或太大”的浮点值(无论是double还是float)强制转换为int或long整数类型将使用符号整数类型的最小或最大值(重点是我的):

A narrowing conversion of a floating-point number to an integral type T takes two steps:

  1. In the first step, the floating-point number is converted either to a long, if T is long, or to an int, if T is byte, short, char, or int, as follows:

    • If the floating-point number is NaN (§4.2.3), the result of the first step of the conversion is an int or long 0.
    • Otherwise, if the floating-point number is not an infinity, the floating-point value is rounded to an integer value V, rounding toward zero using IEEE 754 round-toward-zero mode (§4.2.3). Then there are two cases:

      • a. If T is long, and this integer value can be represented as a long, then the result of the first step is the long value V.
      • b. Otherwise, if this integer value can be represented as an int, then the result of the first step is the int value V.
    • Otherwise, one of the following two cases must be true:

      • a. The value must be too small (a negative value of large magnitude or negative infinity), and the result of the first step is the smallest representable value of type int or long.
      • b. The value must be too large (a positive value of large magnitude or positive infinity), and the result of the first step is the largest representable value of type int or long. *
  2. In the second step: * If T is int or long, the result of the conversion is the result of the first step. * If T is byte, char, or short, the result of the conversion is the result of a narrowing conversion to type T (§5.1.3) of the result of the first step.

Example 5.1.3-1. Narrowing Primitive Conversion

class Test {
    public static void main(String[] args) {
        float fmin = Float.NEGATIVE_INFINITY;
        float fmax = Float.POSITIVE_INFINITY;
        System.out.println("long: " + (long)fmin + ".." + (long)fmax);
        System.out.println("int: " + (int)fmin + ".." + (int)fmax);
        System.out.println("short: " + (short)fmin + ".." + (short)fmax);
        System.out.println("char: " + (int)(char)fmin + ".." + (int)(char)fmax);
        System.out.println("byte: " + (byte)fmin + ".." + (byte)fmax);
    }
}

This program produces the output:

long: -9223372036854775808..9223372036854775807
int: -2147483648..2147483647
short: 0..-1
char: 0..65535
byte: 0..-1

The results for char, int, and long are unsurprising, producing the minimum and maximum representable values of the type.

The results for byte and short lose information about the sign and magnitude of the numeric values and also lose precision. The results can be understood by examining the low order bits of the minimum and maximum int. The minimum int is, in hexadecimal, 0x80000000, and the maximum int is 0x7fffffff. This explains the short results, which are the low 16 bits of these values, namely, 0x0000 and 0xffff; it explains the char results, which also are the low 16 bits of these values, namely, '\u0000' and '\uffff'; and it explains the byte results, which are the low 8 bits of these values, namely, 0x00 and 0xff.

第一个案例int formulaTest = (int) (maxUintFromDoubleAsLong * 1.0);通过乘法将maxUintFromDoubleAsLong提升为double,然后将其转换为int。由于该值过大而无法表示为有符号整数,因此该值变为2147483647(Integer.MAX_VALUE)或0x7FFFFFFF。
至于后一种情况:

将带符号整数的缩小转换为整数类型T只会丢弃除最低n位之外的所有位,其中n是用于表示类型T的位数。除了可能丢失有关数值大小的信息外,这可能导致结果值的符号与输入值的符号不同。

因此,int testFormulaeWithDoubleCast = (int)((long)(maxUintFromDoubleAsLong * 1.0));首先将maxUintFromDoubleAsLong提升为double,然后再转换为long(仍适合),最后转换为int。在最后一个转换中,多余的位被简单地丢弃,使您得到0xFFFFFFFF,如果将其解释为有符号整数,则为-1。

我知道 Java 中缺少 "unsigned" (这是在处理面向位的协议时使 Java 如此令人头疼的东西之一),但大部分其他方面都是崭新的。绝对没有想到 "short" 和 "byte" 的行为会是这样的。谢谢。:D - John Price

3
这只是语言规范的写法。如果将浮点数转换为整数类型,且值太大,则会替换为最大值。在从一个整数类型到较小的类型的缩小转换中,高位比特将被舍弃。
请参阅JLS 5.1.3. 缩小原始转换
因此,标题中问题的答案是“是”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接