【问题标题】:Why does casting Double.NaN to int not throw an exception in Java?为什么将 Double.NaN 转换为 int 不会在 Java 中引发异常?
【发布时间】:2011-08-18 02:18:10
【问题描述】:

所以我知道 IEEE 754 为非实数值指定了一些特殊的浮点值。在 Java 中,将这些值转换为原始 int 确实 not 会抛出我所期望的异常。相反,我们有以下内容:

int n;
n = (int)Double.NaN; // n == 0
n = (int)Double.POSITIVE_INFINITY; // n == Integer.MAX_VALUE
n = (int)Double.NEGATIVE_INFINITY; // n == Integer.MIN_VALUE

在这些情况下不抛出异常的理由是什么?这是一个 IEEE 标准,还是仅仅是 Java 设计者的选择?如果此类强制转换可能出现异常,是否会出现我不知道的不良后果?

【问题讨论】:

    标签: java casting floating-point ieee-754


    【解决方案1】:

    在这些情况下不抛出异常的理由是什么?

    我想原因包括:

    • 这些是边缘情况,在执行此类操作的应用程序中可能很少发生。

    • 这种行为并非“完全出乎意料”。

    • 当应用程序从 double 类型转换为 int 类型时,预计会丢失大量信息。应用程序要么忽略这种可能性,要么在强制转换之前进行检查以防止这种情况......这也可以检查这些情况。

    • 没有其他双精度/浮点操作会导致异常,而且 (IMO) 在这种情况下这样做会有点精神分裂。

    • 在某些硬件平台(当前或未来)上,性能可能会受到影响。

    一位评论员这样说:

    “我怀疑不让转换引发异常的决定是出于强烈希望避免出于任何原因引发异常,因为害怕强制代码将其添加到 throws 子句中。”

    我不认为这是一个合理的解释:

    • Java 语言设计者1 没有避免“出于任何原因”抛出异常的心态。 Java API 中有许多示例可以证明这一点。

    • throws 子句的问题通过取消检查异常得到解决。事实上,许多相关的异常,如 ArithmeticException 或 ClassCastException 都因此被声明为未检查。

    这是一个 IEEE 标准,还是仅仅是 Java 设计者的选择?

    我认为是后者。

    如果此类强制转换可能出现异常,是否会出现我不知道的不良后果?

    除了明显的以外没有...

    (但这并不真正相关。JLS 和 JVM 规范说了他们所说的话,更改它们可能会破坏现有代码。而且我们现在谈论的不仅仅是 Java 代码......)


    我已经做了一些挖掘工作。许多可用于从双精度转换为整数的 x86 指令似乎会产生硬件中断……除非被屏蔽。我不清楚(对我而言)指定的 Java 行为是否比 OP 建议的替代方法更容易或更难实现。


    1 - 我不否认某些 Java 程序员确实有这种想法。但他们是/不是 Java 设计者,这个问题专门询问 Java 设计的基本原理。

    【讨论】:

    • 我怀疑不让转换引发异常的决定是出于强烈希望避免出于任何原因引发异常的动机,因为害怕强制代码将其添加到 throws 子句中。然而,从实际的角度来看,如果代码试图将浮点数转换为整数但它不合适,则代码不太可能正常工作,因此出现异常可能比继续处理虚假数据要好。跨度>
    • (假设的)异常不必是已检查异常。事实上,那将是一个错误。但无论哪种方式,这都不会发生。
    【解决方案2】:

    有一个 1998 年的 ACM 演示文稿,看起来仍然非常流行,并且带来了一些亮点:https://people.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf。

    更具体地说,关于在转换 NaN 和无穷大时出人意料地缺乏异常:参见第 3 页,第 3 点:“在没有 IEEE 标准 754/854 规定的浮点陷阱和标志保护的情况下释放的无穷大和 NaN 与 Java 的说法相悖到稳健性。”

    该演示文稿并没有真正回答“为什么”,而是解释了 Java 语言的浮点实现中存在问题的设计决策的后果,并将它们置于 IEEE 标准甚至其他实现的上下文中。

    【讨论】:

      【解决方案3】:

      不投掷的理由是什么 这些情况下的例外情况?这是一个 IEEE 标准,还是仅仅是一个 Java 设计者的选择?

      第 20 页和第 21 页第 2.2.1 NAN 和 2.2.2 Infinity 部分下的IEEE 754-1985 标准清楚地解释了标准要求 NAN 和 Infinity 值的原因。因此,这不是 Java 的事情。

      3.8.1 Floating Point Arithmetic and IEEE 754 部分中的Java Virtual Machine Specification 指出,当执行到整数类型的转换时,JVM 将向零进行舍入,这解释了您所看到的结果。

      该标准确实提到了一个名为“陷阱处理程序”的功能,该功能可用于确定何时发生溢出或 NAN,但 Java 虚拟机规范明确指出这不适用于 Java。它在第 3.8.1 节中说:

      浮点运算 Java虚拟机不抛出 异常、陷阱或其他信号 IEEE 754 的特殊条件 无效操作,除以零, 上溢、下溢或不精确。这 Java虚拟机没有信号 NaN 值。

      因此,无论后果如何,行为都不是未指定的。

      我有什么不好的后果 不知道是否会出现异常 这样的演员可能吗?

      了解标准中所述的原因应该足以回答这个问题。该标准通过详尽的示例解释了您在此处要求的后果。我会发布它们,但是这里的信息太多了,并且这些示例可能无法在此编辑工具中正确格式化。

      编辑

      我正在阅读 JCP 最近发布的关于 Java Virtual Machine Specification 的最新维护评论,这是他们在 JSR 924 上工作的一部分,在 2.11.14 节命名类型转换指令中包含更多信息,可以帮助您您对答案的追求,还不是您正在寻找的东西,但我相信它会有所帮助。它说:

      在 a 的缩小数字转换中 浮点值转换为整数 类型 T,其中 T 是 int 或 long, 浮点值被转换 如下:

      • 如果浮点值是 NaN,则转换的结果是一个
        int 或 long 0。
      • 否则,如果浮点值不是无穷大,
        浮点值四舍五入为
        使用 IEEE 754 的整数值 V
        向零模式舍入。

      有两种情况:

      • 如果 T 是 long 并且该整数值可以表示为 long,则
        结果是长值 V。
      • 如果 T 是 int 类型并且这个整数值可以表示为 一个 int,那么结果就是 int 价值 V。

      否则:

      • 要么值太小(大的负值 幅度或负无穷大), 结果是最小的 int 类型的可表示值或 长。
      • 或者该值必须太大(一个大的正值或
        正无穷大),结果
        是最大的可表示值 输入 int 或 long。

      从 double to float 的行为符合 使用 IEEE 754。结果是正确的 使用 IEEE 754 四舍五入 最近模式。值太小而不能 表示为浮点数被转换为 类型为正或负零 漂浮;一个太大的值 表示为浮点数被转换为 正无穷或负无穷。一种 双 NaN 始终转换为 一个浮点数 NaN。

      尽管溢出, 下溢或精度损失 可能会发生,从而缩小 数字类型永远不会导致 Java 虚拟机抛出一个 运行时异常(不要混淆 具有 IEEE 754 浮点 例外)。

      我知道这只是重申了您已经知道的内容,但它有一个线索,似乎 IEEE 标准要求四舍五入到最接近的值。也许您可以在那里找到这种行为的原因。

      编辑

      第 2.3.2 节舍入模式状态中讨论的 IEEE 标准:

      通过 默认情况下,舍入表示朝 这 最近。该标准要求其他三个 提供舍入模式;即,朝 0,向 +Infinity 舍入和向 -Infinity 舍入。

      当与转换为整数运算一起使用时,向 -Infinity 舍入会导致转换成为 floor 函数,而向 +Infinity 舍入 是天花板。

      模式舍入会影响溢出,因为当向 O 或向 O 舍入时 - 无限是有效的,正数的溢出会导致默认结果是最大的可表示数字,而不是 + 无限。

      同样,溢出 负量会产生 向 + 无穷大或向 O 舍入时最大的负数有效。

      然后他们继续提到为什么这在区间算术中很有用的例子。再次不确定这是否是您正在寻找的答案,但它可以丰富您的搜索。

      【讨论】:

      • 您似乎在指向 IEEE 标准来证明 NaN 和无穷大的存在。我绝不争辩这些值应该是 IEEE 标准或 Java 的一部分,所以我不确定你为什么要指出这一点。此外,“四舍五入”不会自动解释该决定;这种舍入在数学上没有明确定义。考虑到四舍五入的决定某事,这些都是明智的选择,但必须做出决定并不明显。
      • @Michael McGowan 我想我误解了你的问题。我原以为这与为什么 Java 中的浮点运算从不抛出异常有关。现在我意识到你的意思是为什么向下转换浮点特殊值不会引发算术异常。我想您的问题也适用于“为什么整数数据类型不会在溢出或下溢时引发异常?”因为问题是 Java 整数数据类型只有在面临被零除时才会抛出异常。我也不知道这些设计选择的原因。很抱歉造成误解。
      • @Michael McGowan 虽然您已经接受了一个答案,但出于好奇,我决定阅读更多关于该主题的内容,并且我发现了一些您可能会觉得有用的其他参考资料。我已经编辑了我的答案以包括它们。也许这可以帮助您寻求更多细节。然而,我确信这不是您要寻找的答案。
      • 规范中说它将 NaN 转换为 0 很好,但他们告诉我们为什么吗?是因为最初它以某种方式“更容易”吗?我的猜测是,如果像强制转换这样的“本机”操作从不抛出任何异常,那么 JVM 更容易编写。所以我猜这只是一个实现细节。
      【解决方案4】:

      实际上,我认为在某些铸造过程中会完成一些位操作(可能是因为性能问题?),因此您可能会有一些意想不到的行为。看看使用 >> 和

      例如:

      public static void main(String[] args) {
          short test1 = (short)Integer.MAX_VALUE;
          System.out.println(test1);
          short test2 = (short)Integer.MAX_VALUE-1;
          System.out.println(test2);
          short test3 = (short)Integer.MAX_VALUE-2;
          System.out.println(test3);
          short test4 = (short)Double.MAX_VALUE-3;
          System.out.println(test4);
      }
      

      将输出:

      -1
      -2
      -3
      -4
      

      【讨论】:

        【解决方案5】:

        【讨论】:

          猜你喜欢
          • 2010-09-26
          • 1970-01-01
          • 1970-01-01
          • 2015-10-29
          • 1970-01-01
          • 2011-06-25
          • 1970-01-01
          • 2016-12-29
          • 1970-01-01
          相关资源
          最近更新 更多