有几个关于浮点表示法的问题被提交给了SO。例如,十进制数0.1没有精确的二进制表示,因此使用==操作符将其与另一个浮点数进行比较是危险的。我理解浮点表示法背后的原理。

我不明白的是,为什么从数学的角度来看,小数点右边的数字比左边的数字更“特殊”?

例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分总是精确的。但6.10这个数字并不准确。我所做的只是把小数点移了一位突然间我就从精确乌托邦变成了不精确镇。从数学上讲,这两个数字之间不应该有本质差别——它们只是数字。

相比之下,如果我把小数点向另一个方向移动一位,得到数字610,我仍然在Exactopia。我可以继续往这个方向(6100,610000000,610000000000000)它们仍然是完全,完全,完全的。但是一旦小数点越过某个阈值,这些数字就不再精确了。

这是怎么呢

编辑:为了澄清,我不想讨论诸如IEEE之类的行业标准表示,而是坚持我所相信的数学上的“纯粹”方式。以10为基数,位置值为:

... 1000  100   10    1   1/10  1/100 ...

在二进制中,它们将是:

... 8    4    2    1    1/2  1/4  1/8 ...

这些数字也没有任意的限制。位置向左和向右无限增加。


当前回答

BCD -二进制编码的十进制-表示是精确的。它们不是很节省空间,但在这种情况下,这是为了准确性而必须做出的权衡。

其他回答

(注意:我将在这里添加'b'来表示二进制数。其他数字均为十进制)

一种思考方法是用科学记数法。我们习惯看到用科学符号表示的数字,比如6.022141 * 10^23。浮点数内部使用类似的格式存储——尾数和指数,但使用2的幂而不是10。

你的61.0可以重写为1.90625 * 2^5,或者1.11101b * 2^101b加上尾数和指数。把它乘以10(移动小数点),我们可以这样做:

(1.90625 * 2 ^ 5) * 1.25 * 2 ^ (3) = 2.3828125 * 2 ^ (8) = 1.19140625 * 2 ^ (9)

或者在二进制中用尾数和指数:

(1.1110b * 2^101b) * (1.01b * 2^11b) = (10.011000b * 2^1000b) = (1.0011000b * 2^1001b)

Note what we did there to multiply the numbers. We multiplied the mantissas and added the exponents. Then, since the mantissa ended greater than two, we normalized the result by bumping the exponent. It's just like when we adjust the exponent after doing an operation on numbers in decimal scientific notation. In each case, the values that we worked with had a finite representation in binary, and so the values output by the basic multiplication and addition operations also produced values with a finite representation.

现在,考虑一下我们如何用61除以10。我们先把尾数分成1.90625和1.25。小数是1.525,一个很短的数。但是如果我们把它转换成二进制呢?我们会用通常的方法来做——尽可能减去2的最大幂,就像把整数小数转换成二进制一样,但我们将使用2的负幂:

1.525         - 1*2^0   --> 1
0.525         - 1*2^-1  --> 1
0.025         - 0*2^-2  --> 0
0.025         - 0*2^-3  --> 0
0.025         - 0*2^-4  --> 0
0.025         - 0*2^-5  --> 0
0.025         - 1*2^-6  --> 1
0.009375      - 1*2^-7  --> 1
0.0015625     - 0*2^-8  --> 0
0.0015625     - 0*2^-9  --> 0
0.0015625     - 1*2^-10 --> 1
0.0005859375  - 1*2^-11 --> 1
0.00009765625...

哦哦。现在我们有麻烦了。原来,1.90625 / 1.25 = 1.525,用二进制表示时是一个重复分数:1.1110b / 1.01b = 1.10000110011…b我们的机器只有这么多位来容纳尾数,所以它们会四舍五入,假设超过某一点是零。当你用61除以10时,你看到的错误是:

1.100001100110011001100110011001100110011……B * 2^10b 而且,说: 1.100001100110011001100110b * 2^10b

正是尾数的舍入导致了我们与浮点值相关的精度损失。即使当尾数可以精确地表示(例如,当只是两个数字相加时),如果在标准化指数后尾数需要太多数字来拟合,我们仍然会得到数字损失。

实际上,我们一直在做这样的事情,当我们把小数四舍五入到一个可管理的大小时,只给出它的前几位。因为我们用十进制表示结果,所以感觉很自然。但是如果我们四舍五入一个小数,然后把它转换成不同的底数,它看起来就像我们通过浮点四舍五入得到的小数一样难看。

你们知道整数,对吧?每一位代表2^n

2 ^ 4 = 16 2 ^ 3 = 8 2 ^ 2 = 4 2 ^ 1 = 2 2 ^ 0 = 1

浮点数也是一样的(有一些区别),但是比特代表2^-n 2 ^ 1 = 1/2 = 0.5 2 ^ 2 = 1 / (2 * 2) = 0.25 2 ^ 3 = 0.125 2 ^ 4 = 0.0625

浮点二进制表示法:

符号指数分数(我认为无形的1被附加到分数) B11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0

问题是你并不知道这个数字是否真的是61.0。考虑一下:

浮动a = 60; 浮动b = 0.1; c = a + b * 10;

c的值是多少?它不是61,因为b不是。1因为。1不是精确的二进制表示。

这和你不能精确地以10为基数表示1/3的原因是一样的,你需要说0.33333(3)。在二进制中,这是相同类型的问题,只是发生在不同的数字集上。

这是个好问题。

你所有的问题都是基于“我们如何表示一个数字?”

所有的数字都可以用十进制表示,也可以用二进制(2的补码)表示。所有人!!

但有些(大多数)需要无穷多个元素(二进制位置为“0”或“1”,十进制表示为“0”,“1”到“9”)。

比如十进制表示的1/3(1/3 = 0.3333333…<-包含无限个“3”)

比如二进制中的0.1 (0.1 = 0.00011001100110011....<-带有无限个“0011”)

一切都在这个概念中。由于您的计算机只能考虑有限的数字集(十进制或二进制),只有一些数字可以准确地表示在您的计算机…

乔恩说过,3是质数,不是10的因数,所以1/3不能用以10为底的有限个数来表示。

即使使用任意精度的算术,以2为基数的编号位置系统也不能完全描述6.1,尽管它可以表示61。

对于6.1,我们必须使用另一种表示法(比如十进制表示法,或者允许以2为底或以10为底表示浮点值的IEEE 854)。