我知道0.1十进制数不能用有限的二进制数精确表示(解释),所以double n = 0.1会失去一些精度,而不会完全是0.1。另一方面,0.5可以精确表示,因为它是0.5 = 1/2 = 0.1b。
已经说过,将0.1加3次并不能得到确切的0.3,所以下面的代码打印false,这是可以理解的:
1 2 3 4
| double sum = 0, d = 0.1;
for (int i = 0; i < 3; i ++)
sum += d ;
System. out. println(sum == 0.3); // Prints false, OK |
但是怎么加上五次0.1就能得到精确的0.5?以下代码打印true:
1 2 3 4
| double sum = 0, d = 0.1;
for (int i = 0; i < 5; i ++)
sum += d ;
System. out. println(sum == 0.5); // Prints true, WHY? |
号
如果0.1不能精确表示,怎么加5次才能精确表示0.5?
- @马尔可托普尼克不明显,想解释一下吗?
- 相关:stackoverflow.com/questions/15575669/&hellip;
- @如果我们使用较少的位(有限位而不是无限位),那么EDOCX1的表示(0)小于实际的0.1。加5次应累计误差,不能取消。加5次(5是奇数不是偶数)也不能取消它…
- 如果你真的研究过它,我相信你能找到它,但是浮点运算充满了"惊喜",有时候只是好奇地看它更好。
- @ICZA表示0.1的错误与添加数字的错误一起取消。这是"显而易见的",因为它遵循对结果的常识性推理。
- 我不知道Java编译器是否允许执行与C编译器相同的技巧,但我至少会考虑总结循环已经被优化的可能性。
- 还与stackoverflow.com/questions/21690585/is-3xx-always-exact相关
- 同意拉塞尔的观点。在禁用编译器优化的情况下尝试,看看会发生什么。
- @不,不是这样的。如果我在循环中添加了其他代码0.1(例如System.out.println()),那么代码就会被执行,这样循环就不会被优化。
- 你在用数学的方法思考这个问题。浮点算术在任何方面都不是数学。
- @这是非常错误的态度。
- @russellborogove即使它被优化掉了,它也只是一个有效的优化,如果sum具有与实际执行循环相同的最终值。在C++标准中,这被称为"相似规则"或"相同可观察行为"。
- @雅各布根本不是真的。浮点算法定义严谨,对误差界等有很好的数学处理。这只是因为许多程序员要么不愿意继续进行分析,要么错误地认为"浮点运算不准确"是所有需要知道的,而分析不值得去费心。
- @霍布斯——IEEEfloatingpoint的定义相当严格,其他的就不那么严格了。虽然可以预测一系列IEEEfloat计算的确切结果,但在许多情况下这并没有多大帮助,因为"预测"基本上意味着用手进行计算,而不是让计算机进行计算。只需理解浮点运算是不精确的,并将详细的分析保存在少数真正需要它的情况下,就可以更简单、更不容易出错(更快/更便宜)。
舍入误差不是随机的,在实现舍入误差的过程中,它试图将误差最小化。这意味着有时错误不可见,或者没有错误。
例如,0.1不完全是0.1,即new BigDecimal("0.1") < new BigDecimal(0.1),但0.5完全是1.0/2。
这个程序向您显示所涉及的真实值。
印刷品
1 2 3 4 5 6 7 8 9 10
| 0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0 |
号
注:0.3稍有偏离,但当到达0.4时,位必须向下移动一位,以适应53位的限制,错误被丢弃。同样,对于0.6和0.7,一个错误又出现了,但是对于0.8到1.0,错误被丢弃了。
Adding it 5 times should cumulate the error, not cancel it.
号
出现错误的原因是由于精度有限。即53位。这意味着,当数字变大时使用更多的位时,必须从末尾删除位。这会导致四舍五入,在这种情况下对您有利。当得到一个较小的数字时,你可以得到相反的效果,例如0.1-0.0999=>1.0000000000000286E-4。你会看到比以前更多的错误。
这就是为什么在Java 6中,为什么数学计算(0.4999999999999999)返回1,在这种情况下,计算结果中的一点丢失与答案有很大的不同。
- 这是在哪里实现的?
- @CPU遵循IEEE-754标准。Java提供了对底层CPU指令的访问,而不涉及。en.wikipedia.org/wiki/ieee_浮点
- @彼得:不一定是CPU。在CPU中没有浮点的机器上(并且没有使用单独的FPU),软件将执行IEEE算法。如果主机CPU有浮点但不符合IEEE要求,我认为对于CPU的Java实现也有义务使用软浮点…
- @ R.。在这种情况下,我不知道如果使用strictfp时间来考虑不动点整数会发生什么。(或BigDecimal)
- @彼得·劳里,用简单的英语来说,这是一个舍入的东西?
- @Eugene的关键问题是有限值浮点可以表示。这种限制会导致信息丢失,并且随着数字的增加,会导致错误丢失。它使用四舍五入,但在这种情况下,向下四舍五入,这样一个稍微过大的数字就会变成正确的值,因为0.1稍微过大。正好0.5
- @彼得·劳里那是我的观点……漂亮答案的thx btw.+1
除溢出外,在浮点运算中,x + x + x正好是正确四舍五入(即最近的)浮点数到实数3*x,x + x + x + x正好是4*x,x + x + x + x + x又是5*x的正确四舍五入浮点近似。
对于x + x + x来说,第一个结果是由于x + x是精确的。因此,x + x + x仅是一次舍入的结果。
第二个结果比较困难,本文讨论了其中一个证明(斯蒂芬·卡农引用了对x最后3位数字的另一个实例分析)。综上所述,3*x与2*x在同一个二进制代码中,或者与4*x在同一个二进制代码中,并且在每种情况下都可以推断出第三个加法上的错误会抵消第二个加法上的错误(第一个加法是准确的,正如我们已经说过的)。
第三个结果"x + x + x + x + x是正确的四舍五入",来自第二个结果,与第一个结果来自x + x的精确性相同。
第二个结果解释了为什么0.1 + 0.1 + 0.1 + 0.1正好是浮点数0.4:有理数1/10和4/10在转换为浮点时以相同的相对误差近似。这些浮点数的比率正好是4。第一和第三个结果表明,0.1 + 0.1 + 0.1和0.1 + 0.1 + 0.1 + 0.1 + 0.1的误差可以预期比单纯的误差分析所推断的误差小,但它们本身只与3 * 0.1和5 * 0.1的结果有关,它们可以预期接近但不一定与0.3和0.5相同。③
如果在第四次添加后继续添加0.1,您将最终观察到舍入误差,使"0.1"添加到自身n次"偏离n * 0.1",甚至偏离n/10。如果将"0.1相加n次"的值作为n的函数绘制出来,你会观察到二进制数的等斜率线(当第n次相加的结果注定要落在一个特定的二进制数中时,加法的性质可以预期与以前的加法相似,后者会产生相同的b。内酰胺)。在同一个二进制代码中,错误将增大或缩小。如果你观察从二进制码到二进制码的斜坡序列,你会在一段时间内识别出0.1在二进制码中的重复数字。之后,吸收开始发生,曲线变平。
- 在第一行,你是说x+x+x是完全正确的,但从问题中的例子来看,它不是。
- @阿尔博兹,我说,x + x + x正是正确的四舍五入浮点数到实数3*x。"正确的四舍五入"在本文中是指"最近的"。
- +1这应该是公认的答案。它实际上提供了对所发生的事情的解释/证明,而不仅仅是模糊的概括性。
- @alboz x + x + x正好是x的三倍,在代表范围内。但x不完全是0.1,三倍x不完全是0.3。
- @阿尔博兹(所有这些都是由这个问题设想的)。但这个答案解释了错误是如何偶然地取消的,而不是以最坏的方式累积的。
- +一个很好的答案是,你和史蒂芬·卡农的对话本身就值得你投赞成票。
- 为什么x+x是精确的?
- @chebus x + x在二进制浮点中是精确的(禁止溢出),因为表示结果的浮点值确实存在,所以没有理由让+返回任何其他值。这个值与x的意义相同,并且指数比x的指数多一个。
- 假设x不完全可表示为0.1,那么0.1+0.1怎么可能精确到0.2?错误累积会发生什么情况,它们会不会将t(四舍五入到最接近的)四舍五入到精确的0.2?
- 我不能完全理解这个问题,你能给我一些我可以学习的资源吗?
- @chebus 0.1是0x1.9999999999999999999999…P-4,十六进制(无限数字序列)。它的双精度近似值为0x1.99999AP-4。0.2是0x1.9999999999999999999…P-3的十六进制形式。出于与0.1近似为0x1.99999AP-4相同的原因,0.2近似为0x1.99999AP-3。同时,0x1.99999AP-3也正好是0x1.99999AP-4+0x1.99999AP-4。
- @切布斯,你最初的问题是关于x+x。x+x是精确的这一事实与x是否是某些十进制表示的最接近的近似值无关,但是的,另一个性质是,如果x是某些数y的最接近的浮点近似值,那么x+x是数2y的最接近的近似值。
- 谢谢您。我误解了这一点,因为X+X精确表示舍入后约0.1+约0.1精确表示0.2。因此x+x意味着,x=逼近x=逼近的2×x现在给出了这种情形,x+x(x=0.1)的值近似为0x1.99 99 9AP-3,所以这个圆现在是精确的0.2,因为在Java中给出的0.1 +0.1给出了0.2(确切地不是近似0.1 +0.1 +0.1)。
浮点系统有各种各样的魔力,包括有一些额外的位精度为四舍五入。因此,由于0.1的不精确表示而产生的非常小的误差最终被四舍五入为0.5。
把浮点看成是表示数字的一种伟大但不精确的方法。并非所有可能的数字都能轻易地在计算机中表示出来。像π这样的无理数。或者像sqrt(2)。(符号数学系统可以代表它们,但我确实说过"容易"。)
浮点值可能非常接近,但不精确。它可能是如此的接近以至于你可以导航到冥王星并且以毫米为单位离开。但在数学意义上仍然不准确。
当需要精确而非近似时,不要使用浮点。例如,会计应用程序希望准确跟踪一个帐户中的某个硬币数。整数是很好的,因为它们是精确的。使用整数时需要注意的主要问题是溢出。
使用bigdecimal表示货币效果很好,因为基础表示是一个整数,尽管它很大。
由于认识到浮点数是不精确的,它们仍然有许多用途。导航坐标系或图形系统中的坐标。天文值。科学价值观。(你可能不知道棒球的确切质量在一个电子的质量范围内,所以不准确并不重要。)
对于计数应用程序(包括记帐),请使用整数。为了计算通过一个门的人数,使用int或long。
- 问题是标签。Java语言定义没有提供"很少的额外精度位",只有很少的额外指数位(只有当您不使用EDCOX1(0)时)。仅仅因为你已经放弃去理解某件事并不意味着它是深不可测的,也不意味着其他人应该放弃去理解它。参见StasOfFuff.com /Strus/1849660作为Java实现的一个例子,以实现语言定义(它不包括任何额外精度位的规定,也不包含任何额外的EXP位的EDCOX1×0)。