为什么十进制数字不能完全用二进制表示?


284

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

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

例如,数字61.0具有精确的二进制表示形式,因为任何数字的整数部分始终是精确的。但是数字6.10并不准确。我所要做的就是将小数点后一位移到突然之间,我从埃克塞索比亚(Exactopia)来到了Inexactville。从数学上讲,两个数字之间应该没有本质上的区别,它们只是数字。

相比之下,如果我将小数点向另一方向移动一位以产生数字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 ...

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


2
您可能会发现这对准确理解浮点数内部发生的事情很有帮助:浮点数剖析
John D. Cook

57
用二进制表示,数字3表示为2 1 + 2°= 2 + 1。好,易于。现在,看看1/3。您如何用2的负幂表示呢?稍作试验,您将看到1/3等于无限序列2 ^ -2 + 2 ^ -4 + 2 ^ -6 + 2 ^ -8 + ...的和。用二进制表示并不容易。
拉尔斯·豪格斯

21
乔恩·斯基特(Jon Skeet)很好地回答了您体内的问题。缺少的一件事是您实际上提出了两个不同的问题。标题问题是“为什么十进制数字不能完全用二进制表示?” 答案是,他们可以。在标题和正文之间,您将“二进制”和“浮点表示”的概念混为一谈。浮点数是一种以精确度为代价,用固定数量的二进制数字表示十进制数字的方法。二进制只是用于计数的一个不同的基础,在给定无限数量的数字的情况下,二进制可以表示任何数字。
克里斯·布莱克威尔

3
有几种系统具有精确的十进制表示形式。它的工作原理很像您所描述的。SQL十进制类型就是一个示例。LISP语言是内置的。有一些商业和开源库可以使用精确的十进制计算。只是没有对此的硬件支持,并且仅有的大多数语言和硬件都实现了IEEE标准,以32位或64位表示无限数量的数字。

1
这个问题似乎离题,因为它是关于数学的(即使它是与编程相关的数学),并且在数学
Cole Johnson

Answers:


360

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

让我们换一种方式来看-在您可能会满意的以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。将其与另一种方式进行比较:给定任意数量的十进制数字,您可以精确地表示任何可精确表示为浮点二进制数的数字。


8
先生,这真是个好例子!
Tom Ritter

5
希望我能投票两次。完全被问到我太多次了。这几乎就像人们无法想象基地10赫赫之外
贾斯汀Niessner

38
是的,世界上有10种人-懂二进制的人和不懂二进制的人。
duffymo

83
@JonSkeet:Ctrl + Alt + Delete用两只手指看起来会很尴尬。
拉尔斯·豪格斯

20
@muusbolla:否。十进制表示形式1和十进制表示形式0.9...9在小数点后无限重复s)表示的数字相等。也许最简单的方法如下:让x = 0.9...。注意10x = 9.9....。因此,9x = 10x - x = 9.9... - 0.9... = 9这样9x = 9x = 1。还有其他方法可以看到这种情况,但是我认为这是最简单的。
杰森

25

例如,数字61.0具有精确的二进制表示形式,因为任何数字的整数部分始终是精确的。但是数字6.10并不准确。我所要做的就是将小数点后一位移至突然之间,我从埃克塞索比亚(Exactopia)来到了Inexactville。从数学上讲,两个数字之间应该没有本质上的区别-它们只是数字

让我们暂时离开10和2的底数。让我们问一下-在base中b,哪些数字具有终止表示,哪些数字没有?片刻的想法告诉我们,当且仅当存在一个整数(即整数)时,数字x才具有终止b表示。nx b^n

因此,例如,x = 11/500具有一个终止的10表示形式,因为我们可以选择n = 3,然后选择x b^n = 22一个整数。但是x = 1/3事实并非如此,因为无论n我们选择什么,我们都将无法摆脱3。

第二个例子提示我们思考的因素,我们可以看到,对于任何合理的 x = p/q(假定为最低项),我们可以通过比较的主要factorisations回答这个问题bq。如果q有任何素数因素不在的素数分解中b,我们将永远找不到合适的方法n来摆脱这些因素。

因此,对于以10为底的数,具有2或5以外的素数的任何 p/q事物q都将不具有终止表示。

现在回到10和2的基数,我们看到任何以10表示终止的有理数都将p/q恰好在素数分解中q只有2s和5s 时具有形式。而相同的数恰好在素数分解中q只有2s 时才具有终止2表示。

但是其中一种情况是另一种情况的子集!每当

q2在素数分解中只有s

显然也是这样

q在素数分解中只有2s和5s

或者换句话说,只要p/q具有终止2表示,p/q就具有终止10表示。但是,逆数成立-只要q在素数分解中有5,它将有一个终止的10表示,而不是一个终止的2表示。这是0.1其他答案提到的示例。

因此,我们可以回答您的问题- 因为2的素数是10的素数的子集,所以所有2终止数均为10终止数,反之亦然。它不是6.1对6.1,而是10对2。

作为一个闭注,如果使用的一些怪癖的人(比如说)基地17,但使用的基地5我们的电脑,你的直觉绝不会被这个带坏了-就不会有任何(非零,非整数)终止号码在两种情况下!


那么,为什么“ alert(0.15 * 0.15)”显示为“ 0.0225”?
Michael Geiser 2014年

5
@MichaelGeiser简短答案:在显示点舍入。您认为的0.15实际上是(当存储为IEEE double时)“ 0.149999999999999994494448884876874”。参见jsfiddle
AakashM 2014年

很清楚点代码示例!希望我能对此表示赞成!我必须使用一些功能来探索舍入截止发生的位置。我仍然惊讶于我们实际上不得不处理这种垃圾。由于人们几乎100%的时间都以10为基数工作,而且我们经常使用非整数,因此您会认为默认的浮点数学实现会处理这种胡说八道。
Michael Geiser 2014年

1
@MichaelGeiser与基数为10的电路相比,基数为2的电路更小,更快,功率效率更高。今天,我们也许可以证明开销是合理的,但是在1970年代制定标准时,这是一个没关系 如果没有处理器电路的直接支持,尝试执行此操作会更加糟糕,因为速度会出现数量级的差异。
Mark Ransom

这个答案比Jon Skeet本人解释得更好!
goelakash

16

根本原因(数学上)是,当您处理整数时,它们是无穷大的

这意味着,即使它们数量不限,我们也可以“算出”序列中的所有项目,而无需跳过任何项目。这意味着,如果要使该项目在610000000000000列表中的第th个位置,则可以通过公式进行计算。

但是,实数是无限的。您不能说“给我位置的真实数字610000000000000”并找回答案。原因是,当您考虑浮点值时,即使在0和之间1,也存在无限数量的值。对于任何两个浮点数也是如此。

更多信息:

http://en.wikipedia.org/wiki/Countable_set

http://en.wikipedia.org/wiki/不可计数_设置

更新: 抱歉,我似乎对这个问题有误解。我的回答是关于为什么我们不能代表每个真正的价值,我还没有意识到浮点会自动归类为有理数。


6
实际上,有理数无限的。但是,并非每个数都是有理数。我当然可以产生一个精确的十进制数序列,该序列将最终达到您要给我的任何精确的十进制数。如果您还需要处理无理数,那么您将陷入无数个无限集合中。
乔恩·斯基特

是的,我应该说的是“真实”,而不是“浮点数”。将澄清。
TM。

1
到那时,逻辑变得不那么适用了,IMO-因为我们不仅不能使用二进制浮点数处理所有数,而且甚至不能处理所有有理数(例如0.1)。换句话说,我认为这与可数性完全无关:)
乔恩·斯凯特

@jonskeet我知道不同意Jon Skeet会破坏自然的基本定律,所以我当然不会这样做:)但是,我确实认为可以将数字的内部表示作为a的索引。您要在外部表示的一组值。通过这种思路,您可以看到,无论索引列表有多大(即使您说过,精度都是无限的),您仍然无法表示所有实数。
TM。

3
@TM:但是OP并没有尝试代表所有实数。他试图表示所有精确的十进制数,这是有理数的子集,因此只能是无限的。如果他使用一组无限的位作为十进制浮点类型,那么他会很好。它将这些位用作二进制浮点类型,这会导致十进制数出现问题。
乔恩·斯基特

10

重复我在对Skeet先生的评论中所说的话:我们可以用小数点表示1 / 3、1 / 9、1 / 27或任何有理数。我们通过添加一个额外的符号来做到这一点。例如,在数字上的一行以数字的十进制扩展名重复。我们需要将十进制数表示为二进制数的序列是:1)二进制数序列,2)小数点和3)一些其他符号来指示序列的重复部分。

Hehner的引号表示法就是这样做的一种方式。他使用引号来表示序列的重复部分。文章:http : //www.cs.toronto.edu/~hehner/ratno.pdf和Wikipedia条目:http : //en.wikipedia.org/wiki/Quote_notation

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


如果我们知道循环的开始和结束位置,则该表示法将起作用。人类非常擅长检测周期。但是,总的来说,计算机不是。为了能够有效地使用重复符号,计算机将必须能够在进行计算后弄清楚循环的位置。例如,对于数字1/3,循环立即开始。但是对于数字1/97,直到您计算出至少96位数字的答案后,循环才会显示出来。(实际上,您需要确保96 * 2 + 1 = 193位数字。)
Barry Brown

4
实际上,计算机检测周期并不难。如果您阅读Hehner的论文,他将描述如何检测各种算术运算的周期。例如,在使用重复减法的除法算法中,当您看到以前遇到的差异时,您知道循环从何处开始。
ntownsend

3
另外,问题在于要精确地表示数字。有时,精确表示意味着很多位。引号表示法的优点在于,Hehner证明与标准的32位固定长度表示法相比,平均而言,表示法的大小节省了31%。
ntownsend

6

BCD- 二进制编码的十进制 -表示形式准确。它们的空间使用效率不是很高,但是在这种情况下,您必须权衡取舍。


1
BCD的准确度与任何其他基准相同。示例:如何在BCD中精确表示1/3?你不能
约尔格W¯¯米塔格

12
BCD是DECIMAL的精确表示,因此是其名称的um(十进制)部分。也没有精确的小数表示形式1/3。
艾伦(Alan)2009年

4

同样的原因,您不能精确地以10为底数表示1/3,因此您需要说0.33333(3)。在二进制中,它是相同类型的问题,但只是针对不同的数字集而发生。


4

(注意:我将在此处附加“ b”以表示二进制数字。所有其他数字均以十进制表示)

思考事物的一种方法是用科学记数法之类的东西。我们已经习惯了用科学记数法表示的数字,例如6.022141 * 10 ^ 23。浮点数在内部使用类似的格式存储-尾数和指数,但使用2的幂而不是10的幂。

您的61.0可以改写为1.90625 * 2 ^ 5,或带有尾数和指数的1.11101b * 2 ^ 101b。要将其乘以十并(移动小数点),我们可以执行以下操作:

(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.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.10000110011 ... b我们的机器只有这么多的位可容纳该尾数,因此它们将舍入该分数并假设零超过特定点 将61除以10时看到的错误是:

1.100001100110011001100110011001100110011 ... b * 2 ^ 10b
并说:
1.100001100110011001100110b * 2 ^ 10b

正是尾数的这种舍入导致我们与浮点值相关联的精度损失。即使可以精确表示尾数(例如,仅将两个数字相加),如果在对指数进行归一化处理后,尾数需要太多位数以适合时,我们仍然会损失数值。

实际上,当我们将十进制数四舍五入到一个可控制的大小并只给出它的前几位数时,我们一直在做这种事情。因为我们用十进制表示结果,所以感觉很自然。但是,如果我们将小数点四舍五入然后将其转换为其他基数,则由于浮点数舍入,它看起来和我们获得的小数点一样难看。


4

这是一个很好的问题。

您所有的问题都基于“我们如何代表数字?”

所有数字都可以用十进制表示或二进制(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中的编号位置系统虽然可以表示61,但也不能完全描述6.1。

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


您可以将1/3表示为分数本身。您不需要无限数量的位来表示它。您仅将其表示为分数1/3,而不是取1并将其除以3的结果。然后,您需要一种使用标准的/ * +-和类似的运算符来处理分数表示的方法,但这很容易-您可以用笔和纸进行这些操作,教计算机进行该操作没什么大不了的。

我说的是“二进制(2的补码)表示形式”。因为,当然,使用其他表示形式可能会帮助您使用有限数量的元素来表示某个数字(并且对于其他一些元素,您将需要无限数量的元素)
ThibThib

3

如果用浮点数做一个足够大的数字(因为它可以做指数),那么最后也会在小数点前出现不精确的情况。所以我不认为您的问题是完全正确的,因为前提是错误的。并非总是将10移位会产生更高的精度,因为在某个点,浮点数将不得不使用指数来表示该数字的大数,并且同样会失去一些精度。


3

令我惊讶的是,还没有人这么说:使用连续分数。这样,任何有理数都可以用二进制有限地表示。

一些例子:

1/3(0.3333 ...)

0; 3

5/9(0.5555 ...)

0; 1, 1, 4

10/43(0.232558139534883720930 ...)

0; 4, 3, 3

9093/18478(0.49209871198181621387596060179673 ...)

0; 2, 31, 7, 8, 5

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

除了精确存储数字外,连续分数还具有其他一些好处,例如最佳有理逼近。如果您决定尽早终止连续小数部分的数字序列,则剩余数字(当重新组合为小数部分时)将为您提供最佳的小数部分。这是找到pi的近似值的方法:

Pi的继续分数:

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

在1处终止序列,得到分数:

355/113

这是极好的有理近似值。


但是,您将如何用二进制表示呢?例如,15需要表示4位,而292需要9。硬件(甚至软件)如何知道每个位之间的位边界?这是效率与准确性之间的权衡。
热情的

2

在等式中

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

因此,我只是想知道我们是否可以为二进制提供对数基本系统,例如

 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的内容,那将是

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

要么

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

1

问题是您真的不知道该数字是否确实是61.0。考虑一下:


float a = 60;
float b = 0.1;
float c = a + b * 10;

c的值是多少?它不完全是61,因为b并不是真正的.1,因为.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,这绝对不是整数。这就是您最终进入Inexactville的方式。


1

可以由分数和整数组成一个并行数。如果没有很多小数位数,则某些分数(例如1/7)无法用小数形式表示。因为浮点是基于二进制的,所以特殊情况会发生变化,但是同样会出现精度问题。


0

有无限数量的有理数,以及表示它们的位数。请参阅http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems


但是,即使使用无限个位数,如果使用浮点二进制点,您仍然将无法精确表示0.1,就像即使使用无限个位数也无法精确表示1/3一样。
乔恩·斯基特

3
@Jon这是不正确的:例如,使用无限数量的小数,我可以精确地表达“三分之一” 。实际问题是,在物理上不可能有“无限数量”的小数或位。
ChrisW

0

数字61.0确实具有精确的浮点运算-但是并非所有数字都正确整数都正确。如果您编写了一个将双精度浮点数和64位整数都加一个的循环,那么最终您会到达一个64位整数完美表示一个数字的点,但是浮点不能—因为没有足够的有效位。

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

另一种思考的方式是,当您注意到61.0在以10为基数的情况下可以完美表示,并且将小数点移到另一位也不会改变时,您将乘以10(10 ^ 1、10 ^ -1的幂) )。在浮点中,乘以2的幂不会影响数字的精度。尝试采用61.0并将其重复除以三,以说明完全精确的数字可能会失去其精确表示的方式。


0

你知道整数吗?每一位代表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


0

上面的高分答案很好。

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

尽管永远不要使用浮点数等于句号等于的另一条评论。即使是精确的表示形式,某些浮点系统中的一些数字也可以用一种以上的方式准确表示出来(IEEE对此很不好,一开始它是一个可怕的浮点规范,所以让人头疼)。不管小数点右边有3个多少,这里1/3都不等于计算器0.3333333上的数字。它足够或可以足够接近但不相等。因此,根据舍入,您会希望2 * 1/3之类的值不等于2/3。绝对不要使用浮点数等于。


0

正如我们一直在讨论的那样,在浮点算法中,十进制0.1不能完美地用二进制表示。

浮点数和整数表示法为表示的数字提供了网格或格子。算术完成后,结果将脱离网格,必须通过舍入将其放回到网格中。示例是二进制网格上的1/10。

如果我们按照一位绅士的建议使用二进制编码的十进制表示形式,是否能够将数字保留在网格中?


1
当然是十进制数。但这只是定义。您不能以十进制表示1/3,也不能以二进制表示0.1。任何量化方案对于无限大的数字集都会失败。
Kylotan'2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.