【问题标题】:Is this Java double-parsing behavior according to spec?这是根据规范进行的 Java 双重解析行为吗?
【发布时间】:2014-07-06 14:06:39
【问题描述】:

java.lang.Double.parseValue 方法以不一致的方式处理奇怪的双精度表示。

如果你写了一个非常大的数字,大到超出了double 的范围,但随后附加了一个很大的负指数以使其回到范围内,你最终会进入范围内(在 Scala 的 REPL 中进行了说明) ):

scala>
java.lang.Double.parseDouble("10000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000001e-400")
res25: Double = 1.0E-21

另一方面,如果你写一个非常小的数字,小到超出double 的范围,然后使用一个大的正指数将它带回范围内,它只有在指数本身并不太大:

scala> 
java.lang.Double.parseDouble("0.000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000001e400")
res26: Double = Infinity

scala>
java.lang.Double.parseDouble("0.000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000001e200")
res27: Double = 1.0E-179

这只是一个错误,还是某个地方的规范允许这种行为,或者规范允许所有这些都失败,当一个人得到正确的结果时应该感谢自己的祝福? (如果是bug,修复了吗?)

(顺便说一句:我正在编写自定义 String-to-double 代码,并打算在棘手的情况下推迟到 Java 默认实现,但这个测试用例失败了。)

【问题讨论】:

    标签: java parsing double number-formatting


    【解决方案1】:

    我认为这是一个边缘案例,但也是一个错误。一个更简单的例子是

    String text = "0.000000000000000001e326";
    System.out.println(Double.parseDouble(text));
    System.out.println(new BigDecimal(text).doubleValue());
    

    在 Java 7 update 25 和 Java 8 update 5 中打印

    Infinity
    1.0E308
    

    BigDecimal 解析并转换为 double 表明这个数字是可表示的。

    【讨论】:

    • 这可能会导致一个简单的解决方案。直接解析指数在范围内的常见情况,但对边缘情况使用new BigDecimal(text).doubleValue()
    【解决方案2】:

    几乎可以肯定它不在规范中。关于Floating-Point Literals in the JLS 的相应部分仅指定了浮点文字的。但它并没有谈到它们的有效表示

    当然,必须有限制。没有人会期望像

    这样的字符串
    String s = "0.00... (3 billion zeros) ...001e3000000000";
    

    被解析为1.0。但显然,这里的限制要低得多。

    这个例子显示了限制:

    public class DoubleTest
    {
        public static void main(String[] args)
        {
            runTest(300, 324);
            runTest(300, 325);
            runTest(300, 326);
        }
    
        private static void runTest(int negativeExponent, int exponent)
        {
            String s = prefix(negativeExponent)+"1e"+exponent+"D";
            double d = Double.parseDouble(s);
            System.out.println(
                "For 1e-"+negativeExponent+" * 1e"+exponent+" result is "+d);
        }
    
        private static String prefix(int negativeExponent)
        {
            StringBuilder sb = new StringBuilder("0.");
            for (int i=0; i<negativeExponent; i++)
            {
                sb.append("0");
            }
            return sb.toString();
        }
    }
    

    打印出来

    对于 1e-300 * 1e324 结果是 9.999999999999999E22

    对于 1e-300 * 1e325 结果是 1.0E24

    对于 1e-300 * 1e326 结果是无穷大

    事实上,它主要与所使用的指数有关。导致此救助的相关部分位于FloatingDecimal.java, line 1996

    【讨论】:

      【解决方案3】:

      这是一个错误,但看看 Oracle 的实现可能会说它是设计的

      it's implemented 的方式是将前导的kDigits 转换为 long int。因此,在第一个示例中,计算的指数在Double 范围内,如果它在该范围内,它会愉快地返回结果。

      对于第二种情况,它会认为指数大于maximum decimal exponent 并返回无穷大。

      对于第三种情况,它是 reach here 并返回预期的结果。

      虽然上面的链接指向 OpenJDK 6 的源代码,但他们不太可能触及 JDK 7 和 8 的相关源代码。

      传统上解析双打是fun。在这种情况下,奇怪之处不足为奇。

      【讨论】:

      • 我认为实际的救助已经在readJavaFormatString 方法中引起,而不是在doubleValue 方法中。由于前者有意提供的信息,后者仅返回无穷大。但我同意:看起来很有趣。 FloatingDecimal 类包含单词 "Hack" 9 次(九次!),以及许多其他听起来相当口语化的 cmets(“让我们面对现实吧..”、“HUMBUG”...) .我也不会用十英尺长的杆子碰它……
      • @Marco13 你看过 fun 链接了吗?这可能是 Oracle 修复的一个错误。
      • @Marco13 哦,顺便说一句,那里似乎并没有做任何事情。只有在输入有问题的情况下,它才会退出这个问题,即如果存在 解析 问题。像往常一样,为纯粹的娱乐寻找有趣的 cmets。
      • 解析双打很难,真的很难;有一些犯规陷阱。简单地打印它们就更难了……
      猜你喜欢
      • 1970-01-01
      • 2014-02-18
      • 2012-11-27
      • 2021-07-03
      • 2021-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-17
      相关资源
      最近更新 更多