这种 Java 双重解析行为是否符合规范?

2022-09-01 23:12:14

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

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

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

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

(题外话:我正在编写自定义字符串到双精度代码,并且打算在棘手的情况下遵从Java默认实现,但是这个测试用例失败了。


答案 1

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

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

在 Java 7 更新 25 和 Java 8 更新 5 中打印

Infinity
1.0E308

BigDecimal解析并转换为双精度值显示此数字是可表示的。


答案 2

几乎可以肯定它不在规范中。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,第1996行