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默认实现,但此测试用例失败。)
答案 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。在这种情况下奇怪的事情没有惊喜。