关于数学:为什么十进制数不能用二进制表示?

Why can't decimal numbers be represented exactly in binary?

关于浮点表示,已经有几个问题发布到了SO。例如,十进制数字0.1没有精确的二进制表示,因此使用==运算符将其与另一个浮点数字进行比较是很危险的。我了解浮点表示背后的原理。

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

例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分总是精确的。但数字6.10并不准确。我所做的就是把小数点移动一位,突然间我从视错觉变为不准确。从数学上讲,这两个数字不应该有内在的区别——它们只是数字而已。

相反,如果我把小数点往另一个方向移动一位,得到610这个数字,我仍然处于验光状态。我可以一直朝那个方向走(6100,610000000,610000000),它们仍然是准确的,准确的,准确的。但一旦小数点超过某个阈值,数字就不再准确了。

发生什么事?

编辑:为了澄清这一点,我想避免讨论行业标准表示,比如IEEE,并坚持我认为的数学上"纯"的方法。在基数10中,位置值为:

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

在二进制中,它们是:

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

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


如果有足够的空间,十进制数字可以精确地表示,而不是用浮点数。如果使用浮点小数点类型(例如.NET中的System.Decimal),则可以精确表示大量不能用二进制浮点精确表示的值。

让我们换一种方式来看待它——在你可能会感到舒适的10进制中,你不能精确地表达1/3。是0.3333333…(复发)不能将0.1表示为二进制浮点数的原因完全相同。您可以精确地表示3、9和27,但不能表示1/3、1/9或1/27。

问题是3是一个素数,不是10的因子。当你想用一个数乘以3时,这不是问题:你总是可以用一个整数乘以,而不会遇到问题。但是当你除以一个素数而不是基数的因数时,你可能会遇到麻烦(如果你试图用1除以这个数,你就会遇到麻烦)。

虽然0.1通常被用作精确十进制数的最简单示例,但它不能精确地用二进制浮点表示,但可以说0.2是一个更简单的示例,因为它的1/5-和5是导致十进制和二进制之间问题的质数。

关于有限表示问题的旁注:

有些浮点小数点类型的大小是固定的,如System.Decimal,另一些类型如java.math.BigDecimal,是"任意大的",但它们在某些点上会达到限制,无论是系统内存还是数组的理论最大大小。然而,对于这个答案的主要部分,这是一个完全独立的点。即使你有一个真正任意的大量的位可以玩,你仍然不能准确地表示十进制0.1在一个浮动二进制点表示。与另一种方法比较:给定一个任意的十进制数字,您可以精确地表示任何可以精确表示为浮点二进制点的数字。


不精确的原因是基数的性质。在基数10中,不能精确表示1/3。变成0.333…然而,在基数3中,1/3精确地用0.1表示,1/2是一个无限重复的十进制(tresimal?).可以有限表示的值取决于基的唯一素数因子的数目,因此基30[2*3*5]可以表示比基2或基10更多的分数。更适合210基[2*3*5*7]。

这是与"浮点错误"不同的问题。这种不准确是因为几十亿的数值分布在更大的范围内。因此,如果您有23位的有效位,那么您只能表示大约830万个不同的值。然后,8位指数提供256个选项来分布这些值。此方案允许最精确的小数出现在0附近,因此您几乎可以表示0.1。


For example, the number 61.0 has an exact binary representation because the integral portion of any number is always exact. But the number 6.10 is not exact. All I did was move the decimal one place and suddenly I've gone from Exactopia to Inexactville. Mathematically, there should be no intrinsic difference between the two numbers -- they're just numbers.

让我们离开10和2的细节。让我们问一个问题——在基本的b中,哪些数字有终止表示,哪些数字没有?片刻的思考告诉我们,一个数x有一个终止的b表示,只要存在一个整数n,这样x b^n就是一个整数。

例如,x = 11/500有一个终止10表示,因为我们可以选择n = 3,然后选择x b^n = 22,一个整数。然而,x = 1/3并没有,因为无论我们选择什么n我们都无法摆脱3。

第二个例子提示我们考虑因子,我们可以看到,对于任何理性的x = p/q(假设为最低值),我们可以通过比较bq的主因子来回答这个问题。如果q有任何不在b的主因子分解中的主因子,我们将永远找不到合适的n来消除这些因子。

因此,对于基10,任何p/q,其中q具有除2或5之外的主要因素,将不具有终止表示。

现在回到基10和基2,我们看到任何终止10表示的有理数都将是p/q的形式,当q在其主因子分解中只有2s和5s时;当q只有2s时,同样的数字也将有终止2表示。在它的主要因子分解中。

但其中一个案例是另一个案例的子集!无论何时

q has only 2s in its prime factorisation

很明显,这也是真的

q has only 2s and 5s in its prime factorisation

或者,换句话说,当p/q有一个终止的2个表示时,p/q有一个终止的10个表示。相反,当q在其主因子分解中有一个5时,它将有一个终止的10个表示,而不是一个终止的2个表示。这是其他答案提到的0.1示例。

所以我们有你的问题的答案-因为2的素因子是10的素因子的一个子集,所有2个终止数都是10个终止数,反之亦然。不是61比6.1,而是10比2。

最后一点要注意的是,如果有人使用(比如说)BASE 17,而我们的计算机使用BASE 5,那么你的直觉就不会被它误导——在这两种情况下都不会有(非零,非整数)数字终止!


根(数学)原因是当你处理整数时,它们是可数无穷大的。

这意味着,即使它们的数量是无限的,我们也可以"计数"序列中的所有项目,而不必跳过任何项目。这意味着,如果我们想把这个项目放在清单的第1位(4位),我们可以通过一个公式计算出来。

然而,实数是无限的。你不能说"给我1号(4号)位置的实数"然后再得到答案。原因是,在考虑浮点值时,即使在01之间,也有无限多的值。任何两个浮点数都是如此。

更多信息:

http://en.wikipedia.org/wiki/countable_集

http://en.wikipedia.org/wiki/uncountable_集

更新:抱歉,我似乎误解了这个问题。我的回答是,为什么我们不能代表每一个实际值,我没有意识到浮点自动被归类为理性的。


重复我在对斯基特先生的评论中所说的:我们可以用十进制符号表示1/3、1/9、1/27或任何有理数。我们通过添加一个额外的符号来实现。例如,数字上的一行,在数字的十进制扩展中重复。我们需要将十进制数表示为二进制数的序列,即1)二进制数的序列,2)基数点,以及3)表示序列重复部分的其他符号。

海纳的引文符号就是这样做的一种方式。他使用引号来表示序列的重复部分。文章:http://www.cs.toronto.edu/~hehner/ratno.pdf和维基百科条目:http://en.wikipedia.org/wiki/quote_notation。

没有什么可以说明我们不能在表示系统中添加符号,所以我们可以使用二进制引号表示法精确地表示十进制有理数,反之亦然。


二进制编码的十进制表示是精确的。它们的空间效率不是很高,但在这种情况下,为了保证准确性,这是一种权衡。


如果你用浮点数做一个足够大的数(就像它可以做指数一样),那么你也会以小数点前的不精确而告终。所以我不认为你的问题是完全正确的,因为前提是错误的,移动10不会总是产生更高的精度,因为在某一点上,浮点数必须使用指数来表示较大的数字,也会失去一些精度。


这是个好问题。

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

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

但有些(大多数)需要无限数量的元素(二进制位置为"0"或"1",十进制表示为"0"、"1"到"9")。

像1/3的十进制表示(1/3=0.3333333…<-无限个"3")。

类似于二进制的0.1(0.1=0.0001100110011….<-无限数"0011")。

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

如前所述,3是一个素数,不是10的因子,所以1/3不能用以10为基数的有限个元素来表示。

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

对于6.1,我们必须使用另一种表示法(如十进制表示法,或允许以2或10为基数表示浮点值的IEEE854)


(注意:我将在这里附加"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.11101B*2^101B)*(1.01B*2^11B)=(10.0110001B*2^1000B)=(1.00110001B*2^1001B)

注意我们在那里做了什么来乘以这些数字。我们将尾数相乘并加上指数。然后,由于尾数结束大于2,我们通过碰撞指数对结果进行了规范化。就像我们对十进制科学记数法中的数字进行运算后调整指数一样。在每种情况下,我们处理的值都有一个二进制的有限表示,因此,基本乘法和加法运算输出的值也会产生一个有限表示的值。

现在,考虑一下我们如何将61除以10。我们从尾数1.90625和1.25开始。在十进制中,这给出了1.525,一个很好的短数字。但如果我们把它转换成二进制,这是什么呢?我们将用通常的方法——尽可能减去两个的最大幂,就像将整数小数转换为二进制一样,但我们将使用两个的负幂:

1
2
3
4
5
6
7
8
9
10
11
12
13
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.11101b/1.01b=1.100001100011…b我们的机器只有这么多的位来容纳尾数,所以它们将小数四舍五入,并假定超过某个点为零。将61除以10所看到的错误是:

1.100001101001100110011001100110011001100110011…B*2^10B并且说:1.1000010011001100110B*2^10B

正是尾数的四舍五入导致了精度的降低,我们将其与浮点值联系起来。即使尾数可以精确表示(例如,只加两个数字),如果尾数在归一化指数后需要太多的数字才能容纳,我们仍然可以得到数字损失。

实际上,当我们把十进制数四舍五入到一个可管理的大小,并给出它的前几个数字时,我们一直在做这种事情。因为我们用十进制表示结果,所以感觉很自然。但是如果我们将一个小数舍入,然后将其转换为另一个基数,它看起来就像浮点舍入得到的小数一样丑陋。


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


我很惊讶还没有人这么说:用连分数。任何有理数都可以用二进制有限表示。

一些例子:

1/3(0.3333…)

1
0; 3

5/9(0.5555…)

1
0; 1, 1, 4

10/43(0.232558139534883720930…)

1
0; 4, 3, 3

9093/18478(0.4920987119818162138759606179673…)

1
0; 2, 31, 7, 8, 5

从这里开始,有多种已知的方法可以将整数序列存储在内存中。

除了以完美的精度存储数字之外,连分数还有其他一些好处,例如最佳有理逼近。如果您决定提前终止连续分数中的数字序列,则剩余的数字(重新组合成分数)将为您提供尽可能最好的分数。这就是找到圆周率近似值的方法:

π的连分数:

1
3; 7, 15, 1, 292 ...

以1终止序列,这给出了分数:

355/113

这是一个很好的有理近似。


方程式中

1
2
2^x = y ;  
x = log(y) / log(2)

因此,我想知道我们是否可以有一个类似二进制的对数基系统,

1
 2^1, 2^0, 2^(log(1/2) / log(2)), 2^(log(1/4) / log(2)), 2^(log(1/8) / log(2)),2^(log(1/16) / log(2)) ........

这也许可以解决这个问题,所以如果你想用二进制写32.41之类的东西,那就是

1
2^5 + 2^(log(0.4) / log(2)) + 2^(log(0.01) / log(2))

1
2^5 + 2^(log(0.41) / log(2))

平行线可以由分数和整数组成。有些分数(如1/7)不能用小数形式表示,没有很多小数。因为浮点是基于二进制的,所以特殊情况会发生变化,但同样的精度问题也会出现。


问题是你不知道这个数字到底是不是61.0。考虑一下:

1
2
3
float a = 60;
float b = 0.1;
float c = a + b * 10;

代码>

C值多少?它不完全是61,因为b不是真正的.1,因为.1没有精确的二进制表示。


有一个阈值,因为数字的含义已经从整数变为非整数。要表示61,您有6*10^1+1*10^0;10^1和10^0都是整数。6.1是6*10^0+1*10^-1,但10^-1是1/10,绝对不是整数。这就是你最终在不准确的地方的结局。


数字61.0确实有一个精确的浮点运算,但并非所有整数都是这样。如果编写了一个循环,在双精度浮点数和64位整数中都添加了一个,那么最终您将到达一个点,其中64位整数完全代表一个数字,但浮点数不代表,因为没有足够的有效位。

在小数点右边到达近似点要容易得多。如果你开始用二进制浮点数写出所有的数字,那就更有意义了。

另一种思考方法是,当你注意到61.0在基数10中是完全可表示的,并且移动小数点不会改变这一点时,你正在执行10的乘方运算(10^1,10^-1)。在浮点运算中,乘以2的幂不影响数字的精度。尝试取61.0,并将其反复除以3,以说明一个完全精确的数字如何会失去其精确表示。


你知道整数对吗?每一位代表2^n

2 ^ 4=162 ^ 3=82 ^ 2=42 ^ 1=22 ^ 0=1

对浮点(有一些区别)来说是一样的,但是位代表2^-n2 ^ 1=1/2=0.52^-2=1/(2*2)=0.252 ^ 3=0.1252 ^ 4=0.0625

浮点二进制表示:

符号指数分数(我认为不可见1附加到分数后)b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0


上面那个高分的答案很清楚。

首先,你在问题中混合了基数2和基数10,然后当你把一个不可分割的数字放在右边的基数上时,你会遇到问题。就像十进制的1/3,因为3不等于10的幂,或者二进制的1/5不等于2的幂。

另一条注释虽然从未使用与浮点数相等的句点。即使它是一种精确的表示,在某些浮点系统中也有一些数字可以用多种方式精确地表示(IEEE对此很糟糕,它从一开始就是一个糟糕的浮点规范,因此会让人头疼)。这里没有区别,1/3不等于计算器0.3333333上的数字,不管小数点右边有多少个3。它是或可以足够接近,但不相等。因此,根据四舍五入的不同,您希望类似于2*1/3的值不等于2/3。不要将equal与浮点一起使用。


正如我们所讨论的,在浮点运算中,十进制0.1不能完美地用二进制表示。

浮点和整数表示为表示的数字提供网格或格。当算法完成后,结果会从网格中掉出来,必须通过四舍五入将其放回网格中。示例是二进制网格上的1/10。

如果我们按照一位先生的建议使用二进制编码的十进制表示法,我们是否能够在网格上保留数字?


有理数是无限多的,用有限位数来表示它们。请参见http://en.wikipedia.org/wiki/floating诳point诳accuracy诳problems。