8

我试图追踪一些非常奇怪的 Java 行为。我有一个涉及双精度的公式,但“保证”给出一个整数答案——特别是一个无符号的 32 位整数(唉,Java 做得不好)。不幸的是,我的回答有时不正确。

最终我发现了这个问题,但这种行为对我来说仍然很奇怪:double直接转换为 anint似乎被限制MAX_INT为有符号整数,而转换为doublea然后转换为 an给了我预期的答案( -1;无符号 32 位整数的 MAX INT,表示为有符号 32 位整数)。longint

我写了一个小测试程序:

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”;但是直接转换给了我 MAX_INT 一个 32 位有符号整数。来自 C++ 背景,如果它给了我一个“奇数”而不是预期的 -1(又名“天真铸造”),我会理解,但这让我感到困惑。

那么,接下来的问题是:Java 中的这种“预期”行为(例如double,直接转换为 an 的任何转换int都将被“限制”为MAX_INT)?强制转换是否对任何意外类型执行此操作?例如,我希望它与shortand相似byte;但是当将超大双精度转换为浮动时,“预期行为”是什么?

谢谢!

4

2 回答 2

11

这是预期的行为。请记住,Java 中没有原始 unsigned long 或 int 类型,用于 Narrowing Primitive Conversion (5.1.3) 的Java 语言规范(Java 7) 声明强制转换“太小或太大”的浮点值(无论是 double或 float) 到 int 或 long 的整数类型将使用有符号整数类型的最小值或最大值(强调我的):

浮点数到整数类型 T 的窄化转换需要两个步骤:

  1. 第一步,浮点数转换为 long(如果 T 为 long)或转换为 int(如果 T 为 byte、short、char 或 int),如下所示:

    • 如果浮点数是 NaN(第 4.2.3 节),则转换第一步的结果是 int 或 long 0。
    • 否则,如果浮点数不是无穷大,则浮点值将四舍五入为整数值 V,使用 IEEE 754 向零舍入模式(第 4.2.3 节)向零舍入。那么有两种情况:

      • 一种。如果 T 是 long,并且这个整数值可以表示为 long,那么第一步的结果就是 long 值 V。
      • 湾。否则,如果这个整数值可以表示为 int,那么第一步的结果就是 int 值 V。
    • 否则,以下两种情况之一必须为真:

      • 一种。该值必须太小(一个大的负值或负无穷大),第一步的结果是int或long类型的最小可表示值。
      • 湾。该值一定是太大了(一个大的正值或正无穷大),第一步的结果是int或long类型的最大可表示值。*
  2. 第二步: * 如果T是int或者long,转换的结果就是第一步的结果。* 如果 T 是 byte、char 或 short,则转换的结果是第一步结果的窄化转换为 T 类型(第 5.1.3 节)的结果。

示例 5.1.3-1。缩小原始转换

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

该程序产生输出:

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

char、int 和 long 的结果并不令人惊讶,它们产生了该类型的最小和最大可表示值。

byte 和 short 的结果会丢失有关数值的符号和大小的信息,并且还会丢失精度。通过检查最小和最大 int 的低位可以理解结果。十六进制的最小 int 为 0x80000000,最大 int 为 0x7fffffff。这解释了简短的结果,即这些值的低 16 位,即 0x0000 和 0xffff;它解释了 char 结果,也就是这些值的低 16 位,即 '\u0000' 和 '\uffff';它解释了字节结果,即这些值的低 8 位,即 0x00 和 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 提升为双倍,回到 long(仍然合适),然后提升为 int。在最后一次转换中,多余的位被简单地丢弃,留下 0xFFFFFFFF,当解释为有符号整数时为 -1。

于 2012-05-17T19:12:55.113 回答
3

这就是语言规范的编写方式。将浮点数转换为整数类型,如果值对于目标而言太大,则替换最大值。在从一种整数类型到更小的整数类型的窄化转换中,高位被丢弃。

请参阅 JLS 5.1.3。缩小原始转换

因此,标题中问题的答案是“是”。

于 2012-05-17T19:12:03.913 回答