为什么添加0.1多次保持无损?

我知道0.1十进制数不能完全用有限的二进制数来表示( 解释 ),所以double n = 0.1会失去一些精度,不会精确到0.1 。 另一方面, 0.5可以精确地表示,因为它是0.5 = 1/2 = 0.1b

话虽如此,可以理解的是,加三次 0.1不会给0.3所以下面的代码打印为false

 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

 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可以精确表示?

舍入误差不是随机的,它的实现方式是尽量减less误差。 这意味着有时错误不可见,或者没有错误。

举例来说, 0.1并不完全是0.1new BigDecimal("0.1") < new BigDecimal(0.1)但是0.5恰好是1.0/2

这个程序向你展示了真正的价值。

 BigDecimal _0_1 = new BigDecimal(0.1); BigDecimal x = _0_1; for(int i = 1; i <= 10; i ++) { System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue()); x = x.add(_0_1); } 

版画

 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.60.7但是在0.81.0 ,错误被丢弃了。

添加5次应该累积错误,而不是取消它。

出现错误的原因是精度有限。 即53位。 这意味着,随着数字越大,比特越大,比特必须被丢弃。 这导致在这种情况下四舍五入在你的青睐。
你可以得到相反的效果,当得到一个较小的数字,例如0.1-0.0999 => 1.0000000000000286E-4 ,你会看到比以前更多的错误。

一个例子是为什么在Java 6中为什么Math.round(0.49999999999999994)返回1在这种情况下,计算中的一点点损失会导致答案的巨大差异。

除浮点溢出外, x + x + x恰好是实数3 * x的正确舍入(即最接近)的浮点数, x + x + x + x恰好是4 * xx + x + x + x + x再次是5 * x的正确舍入的浮点近似值。

第一个结果,对于x + x + x ,来自x + x是确切的事实。 x + x + x因此是仅一个舍入的结果。

第二个结果是更困难的, 这里讨论的一个例子(和斯蒂芬佳能暗示的x的最后3位数字分析的另一个certificate)。 总之,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.10.1 + 0.1 + 0.1 + 0.1 + 0.1可以预期具有比通过天真错误分析推断的误差更小的误差,但是它们本身仅将结果分别关联到3 * 0.15 * 0.1 ,可以预期接近但不一定等于0.30.5

如果在第四次加法之后继续加0.1 ,那么最终会观察到“ 0.10.1次”的舍入误差,与n * 0.1相差甚至更大。 如果你将“0.1自己加n次”的值作为n的函数,你可以通过binades观察到恒定斜率的线(只要第n个加法的结果注定要落入特定的binade,预计添加的属性可能与以前的添加在相同的结果中产生结果类似)。 在相同的情况下,错误会增加或缩小。 如果您要查看从binade到binade的斜坡序列,您将认识到0.1的二进制数字重复一段时间。 之后,开始吸收,曲线变平。

浮点系统做了各种各样的魔法,包括对于舍入有一些额外的精度。 因此,由于0.1的不精确表示造成的非常小的误差最终四舍五入为0.5。

将浮点看作是一种很好的但是不真实的表示数字的方法。 并非所有可能的数字都容易在电脑中performance出来。 不合理的数字,如PI。 或者像SQRT(2)一样。 (符号math系统可以代表他们,但我确实很容易地说)。

浮点值可能非常接近,但不准确。 它可能非常接近,你可以导航到冥王星,并以毫米为单位。 但在math意义上还不够确切。

当你需要精确而不是近似时,不要使用浮点数。 例如,会计应用程序希望准确跟踪帐户中的某个数量的便士。 整数是好的,因为它们是确切的。 你需要注意的主要问题是整数溢出。

使用BigDecimal作为货币运作良好,因为底层表示是一个整数,尽pipe是一个大的。

认识到浮点数是不精确的,它们仍然有很多用途。 在graphics系统中用于导航或坐标的坐标系统。 天文数值。 科学价值。 (无论如何,你可能无法知道一个棒球的质量是否在一个电子质量之内,所以不精确性并不重要。)

用于统计应用程序(包括会计)使用整数。 要计算通过门的人数,请使用int或long。