提问人:zhoudu 提问时间:5/26/2023 更新时间:5/26/2023 访问量:88
如何理解Java中double的这种奇怪行为?[复制]
How to understand this strange behavior of double in Java? [duplicate]
问:
我试图更深入地理解浮点数。我知道二进制(以 2 为基数)浮点数不能完全表示某些十进制数。但是我对 Java 中 double 的这种奇怪行为感到困惑(在 Java 中,double 是一种二进制浮点数数据类型)。
我写了这段 Java 代码:
public void myTestMethod() {
double num;
double factor;
double compared;
double result1;
double result2;
String output;
num = 0.3;
factor = 10;
compared = 3;
//num * 10
result1 = num * factor;
//add 10 num, which is mathematically equal to num * 10
result2 = num + num + num + num + num + num + num + num + num + num;
output = "num: " + num + "\n";
output = output + "result1: " + result1 + "\n";
output = output + "result2: " + result2 + "\n";
if (result1 == compared) {
output = output + "result1 == compared\n";
} else {
output = output + "result1 != compared\n";
}
if (result2 == compared) {
output = output + "result2 == compared\n";
} else {
output = output + "result2 != compared\n";
}
System.out.print(output);
}
运行此方法会生成以下输出:
num: 0.3
result1: 3.0
result2: 2.9999999999999996
result1 == compared
result2 != compared
据我所知,“factor”的值正好是 10,“compared”的值正好是 3,因为这些整数可以精确地用 double 表示。但也许“num”的值与 0.3 略有不同。
现在我想知道:
“num”的值是 0.3(确切地说)吗?
如果 “num” 的值是 0.3,那么,为什么 “result2 == compared” 是假的?
如果“num”的值不是0.3,那么,为什么它的String输出是“0.3”?据我所知,二进制浮点数总是可以精确地用十进制数表示。因此,如果“num”的值不是 0.3,则其 String 输出不应为“0.3”,而应类似于“0.3000000000001”。
如果 “num” 的值不是 0.3,那么,为什么 “result1 == compared” 为真?
如果“num”的值不是0.3,那么,它是什么?
答:
由于所有十进制数的范围都是无限的,并且任何数据类型都具有有限的空间,因此不能使用 double 数据类型表示所有可能的十进制数。这就是为什么某些数字将四舍五入到下一个可能的表示形式的原因。
有关实现的更多详细信息,请查看 IEEE 754 标准。
- “num”的值是 0.3(确切地说)吗?
不,它是 0.299999999999999988897769753748434595763683319091796875。
- 如果 “num” 的值是 0.3,那么,为什么 “result2 == compared” 是假的?
前提是错误的;的值不是 0.3。此外,使用九次加法进行计算。在每次添加中,都有一个舍入步骤。这些四舍五入的添加结果是 2.9999999999999999555910790149937383830547332763671875。num
result2
- 如果“num”的值不是0.3,那么,为什么它的String输出是“0.3”?据我所知,二进制浮点数总是可以精确地用十进制数表示。因此,如果“num”的值不是 0.3,则其 String 输出不应为“0.3”,而应类似于“0.3000000000001”。
Java 规范说,双精度
的默认格式是打印足够的十进制数字来唯一区分数字。具体来说,它转换为具有最少数字的十进制数字,其属性是将十进制数字转换回类型会产生原始值。double
将 0.3 转换为产生最接近的可表示值 0.299999999999999988897769753748434595763683319091796875,因此,在打印 0.29999999999999999888897769753748434595763683319091796875 时。使用默认格式时,Java 会生成“0.3”。double
- 如果 “num” 的值不是 0.3,那么,为什么 “result1 == compared” 为真?
num
是 0.299999999999999988897769753748434595763683319091796875。将其乘以 10 将生成 2.999999999999999988897769753748434595763683319091796875 使用实数算术,但这个数字在 中无法表示。浮点乘法运算生成最接近的值,即 3。double
double
- 如果“num”的值不是0.3,那么,它是什么?
它是0.29999999999999988897769753748434595763683319091796875。
为了了解这里发生了什么,让我们用十进制做一个类似的问题——也就是说,根本不涉及二进制浮点。我们将使用十进制浮点数,精度限制为三位有效数字。 我们将从值 1/3 开始,然后将其乘以 10,这应该得到 10/3。
但是,当然,我们不能完全用小数表示 1/3。我们能得到的最接近的 3 位有效数字是 0.333。但这已经很接近了。
如果我们取 0.333,然后乘以 10,我们显然得到 3.33。这与 10/3(即 3.33333...)并不完全相同,但同样,它非常接近。
但假设我们取 0.333 并将其添加到自身 10 次。这应该给出相同的答案,对吧?
0.333 + 0.333 + 0.333 是 0.999。到目前为止还好。
0.999 + 0.333 应该是 1.332。但是我们只有三个有效数字的精度。因此,我们将不得不将其四舍五入为1.33。
1.33 + 0.333 应该是 1.663。但是我们只有三个有效数字的精度。因此,我们将不得不将其四舍五入为1.66。
1.66 + 0.333 应该是 1.993。但我们必须将其四舍五入到1.99。
(你开始看到一种模式了吗?情况会变得更糟。
1.99 + 0.333 应为 2.323,四舍五入为 2.32。
2.32 + 0.333 应为 2.653,四舍五入为 2.65。
2.65 + 0.333 应为 2.983,四舍五入为 2.98。
最后,2.98 + 0.333 应该是 3.313,四舍五入为 3.31。
所以 0.333 × 10 是 3.33,但 0.333 + 0.333 + ... + 0.333 是 3.31。为什么我们得到了两个不同的答案?
因为几乎每次我们添加 0.333 时,我们都必须对结果进行四舍五入,并且(在这种情况下)这总是意味着丢弃 0.003。经过七次四舍五入的加法步骤,这些小的四舍五入误差加起来对最终结果产生了重大影响。
当你在二进制中重复添加 0.3 时,或多或少会发生完全相同的事情。0.3 在二进制中并不完全表示,因此您不是每次都添加正好 0.3。您添加的值与 0.3 略有不同,经过 9 次添加后,舍入误差加起来与仅将 0.3 乘以 10 时大不相同。有关更多详细信息,请参阅 Eric Postpischil 的回答。
评论
Double#toString(double)
的 Javadoc。(4) 与问题 1 和问题 2 相同,但对 .(5) 已经回答了。(奖金)阅读 Stephen 提供的链接以了解结果。System.out.println(new BigDecimal(num))
num
result2
result1