这是根据规范的Java双解析行为吗?

时间:2014-05-18 15:43:19

标签: java parsing double number-formatting

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

这只是一个错误,或者某个地方是否存在允许此行为的规范,或者规范所允许的所有规则都失败了,当得到正确的结果时,应该感谢一个人的祝福? (如果它是一个错误,是否已修复?)

(旁白:我正在编写自定义字符串到双重代码,并且会针对棘手案例推迟Java默认实现,但此测试用例失败。)

3 个答案:

答案 0 :(得分:14)

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

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表示此数字是可表示的。

答案 1 :(得分:10)

这几乎肯定不符合规范。有关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,结果为无限

实际上,它主要与正在使用的 exponent 有关。导致此救助的相关部分在FloatingDecimal.java, line 1996

答案 2 :(得分:9)

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

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

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

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

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

传统上解析双打fun。在这种情况下奇怪的事情没有惊喜。