这里的问题是浮点数以 2 为基数存储。您不能用以 2 为基数的浮点数精确地表示以 10 为基数的小数。
让我们退后一步。.1 是什么意思?还是0.7?它们的意思是 1x10 -1和 7x10 -1。如果您使用二进制作为数字,而不是我们通常使用的以 10 为底的数字,则 .1 表示 1x2 -1或 1/2。.11 表示 1x2 -1 + 1x2 -2或 1/2+1/4 或 3/4。
请注意,在这个系统中,分母始终是 2 的幂。如果没有分母是 2 的幂,则无法以有限位数表示数字。例如,.1(十进制)表示 1/10,但二进制表示无限重复的分数,0.000110011...(0011 模式永远重复)。这类似于以 10 为底,1/3 是无限小数,0.3333....;以 10 为底只能用 2 和 5 的幂的倍数的分母精确表示数字。(顺便说一句,以 12 为底和以 60 为底实际上是非常方便的底,因为 12 可以被 2、3 和 4 整除,并且60 可以被 2、3、4 和 5 整除;但出于某种原因,我们无论如何都使用十进制,而我们在计算机中使用二进制)。
由于浮点数(或定点数)的位数总是有限的,因此它们不能准确地表示这些无限重复的分数。因此,它们要么截断或舍入值以尽可能接近实际值,但并不完全等于实际值。一旦你开始将这些四舍五入的值相加,你就会开始得到更多的错误。在十进制中,如果 1/3 的表示是 0.333,那么它的三个副本加起来就是 0.999,而不是 1。
有四种可能的解决方案。如果您关心的只是精确地表示像 .1 和 .7 这样的小数(例如,您不在乎 1/3 会遇到您提到的相同问题),那么您可以将您的数字表示为十进制,例如使用二进制编码的十进制,并操作它们。这是金融中的一种常见解决方案,其中许多操作都是用十进制定义的。这样做的缺点是您需要自己实现所有算术运算,没有计算机 FPU 的好处,或者找到一个十进制算术库。如前所述,这也对无法以十进制精确表示的分数没有帮助。
另一种解决方案是使用分数来表示您的数字。如果你使用分数,用 bignums(任意大的数字)作为分子和分母,你可以表示任何适合你计算机内存的有理数。同样,缺点是算术会更慢,您需要自己实现算术或使用现有的库。这将解决所有有理数的问题,但是如果您最终得到基于 π 或 √2 计算的概率,您仍然会遇到无法准确表示它们的相同问题,并且还需要使用一个后面的解决方案。
第三种解决方案,如果你关心的只是让你的数字加起来正好等于 1,那么对于你有n 个可能性的事件,只存储这些概率的n -1 的值,并将最后一个的概率计算为1减去其余概率的总和。
第四种解决方案是在处理浮点数(或任何不精确的数字,例如用于表示无理数的分数)时始终需要记住的事情,并且永远不要比较两个数字是否相等。再次以 10 为底,如果将 1/3 的 3 个副本相加,您将得到 0.999。当您想将该数字与 1 进行比较时,您必须进行比较以查看它是否足够接近 1;检查差值的绝对值 1-.999 是否小于阈值,例如 0.01。