提问人:Barry Brown 提问时间:7/7/2009 最后编辑:Barry Brown 更新时间:6/2/2023 访问量:127953
为什么十进制数不能用二进制精确表示?
Why can't decimal numbers be represented exactly in binary?
问:
已经向 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 ...
对这些数字也没有任意限制。位置无限向左和向右增加。
答:
如果你有足够的空间,十进制数可以精确地表示 - 只是不能用浮点数来表示。如果您使用浮点小数类型(例如 在 .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 是导致十进制和二进制之间问题的素数。
处理有限表示问题的旁注:
一些浮点小数点类型具有固定大小,就像其他类型一样,它们是“任意大”——但它们在某些时候会达到限制,无论是系统内存还是数组的理论最大大小。然而,这与这个答案的主要观点完全不同。即使你有真正任意大量的位可供使用,你仍然无法在浮点表示中精确地表示十进制 0.1。相反:给定任意数量的十进制数字,您可以精确地表示任何可以精确表示为浮点二进制点的数字。System.Decimal
java.math.BigDecimal
如果你用浮点数做一个足够大的数字(因为它可以做指数),那么你最终也会在小数点前面出现不准确。所以我认为你的问题并不完全有效,因为前提是错误的;并不是说移动 10 总是会产生更高的精度,因为在某些时候,浮点数将不得不使用指数来表示数字的大小,并且也会以这种方式失去一些精度。
BCD - 二进制编码的十进制 - 表示是精确的。它们不是很节省空间,但在这种情况下,这是您必须为准确性做出的权衡。
评论
问题是你真的不知道这个数字是否真的是 61.0 .考虑一下:
float a = 60;
float b = 0.1;
float c = a + b * 10;
c的值是多少?它不完全是 61,因为 b 不是真正的 .1,因为 .1 没有精确的二进制表示。
根本(数学)原因是,当你处理整数时,它们是可数的无限。
这意味着,即使它们的数量是无限多的,我们也可以“数出”序列中的所有项目,而不会跳过任何项目。这意味着如果我们想将项目放在列表中的第 th 位,我们可以通过公式来计算。610000000000000
然而,实数是不可数的无限。你不能说“给我位置的真实数字”,然后得到一个答案。原因是,在考虑浮点值时,即使在 和 之间,也有无限数量的值。这同样适用于任何两个浮点数。610000000000000
0
1
更多信息:
http://en.wikipedia.org/wiki/Countable_set
http://en.wikipedia.org/wiki/Uncountable_set
更新:很抱歉,我似乎误解了这个问题。我的回答是关于为什么我们不能表示每个实数值,我没有意识到浮点被自动归类为有理值。
评论
数字 61.0 确实具有精确的浮点运算,但并非所有整数都是如此。如果你编写了一个循环,将 1 添加到双精度浮点数和 64 位整数中,最终你会达到一个点,即 64 位整数完美地表示一个数字,但浮点数却不表示,因为没有足够的有效位。
到达小数点右侧的近似点要容易得多。如果你开始用二进制浮点数写出所有数字,那就更有意义了。
另一种思考方式是,当你注意到 61.0 可以完美地以 10 为基数表示时,并且移动小数点不会改变这一点,你正在执行 10 的幂乘法 (10^1, 10^-1)。在浮点数中,乘以 2 的幂不会影响数字的精度。试着把 61.0 再除以 3,以说明一个完全精确的数字是如何失去其精确表示的。
这与你不能以 10 为基数精确表示 1/3 的原因相同,你需要说 0.33333(3)。在二进制中,它是相同类型的问题,但只是发生在不同的数字集上。
有无数个有理数,以及用有限数量的位来表示它们。请参见 http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems。
评论
有一个阈值,因为数字的含义已从整数变为非整数。要表示 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/7,如果没有很多很多小数,就不能以十进制形式表示。由于浮点是基于二进制的,因此特殊情况会发生变化,但会出现相同的精度问题。
你知道整数吧?每个位代表 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
(注意:我将在此处附加“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,一个不错的短数字。但是,如果我们将其转换为二进制,这是什么呢?我们将按照通常的方式进行 - 尽可能减去 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.11101b / 1.01b = 1.10000110011...b 我们的机器只有这么多位来容纳尾数,所以它们只会将分数四舍五入并假设零超过某个点。将 61 除以 10 时看到的错误是以下两者之间的差值:
1.100001100110011001100110011001100110011...b * 2^10b 并且,说:
1.100001100110011001100110b * 2^10b
正是尾数的这种舍入导致了我们与浮点值相关的精度损失。即使尾数可以精确表示(例如,当只添加两个数字时),如果尾数在归一化指数后需要太多的数字来适应,我们仍然会得到数字损失。
实际上,当我们将十进制数四舍五入到一个可管理的大小并只给出它的前几位数字时,我们一直在做这种事情。因为我们用十进制来表示结果,所以感觉很自然。但是,如果我们将小数点四舍五入,然后将其转换为不同的基数,它看起来就像我们因浮点四舍五入而得到的小数一样丑陋。
这是个好问题。
你所有的问题都是基于“我们如何表示一个数字?
所有数字都可以用十进制表示或二进制(2 的补码)表示。所有的人!!
但是有些(其中大多数)需要无限数量的元素(“0”或“1”表示二进制位置,或“0”、“1”到“9”表示十进制表示)。
就像十进制表示中的 1/3 一样(1/3 = 0.3333333... <- 无限个“3”)
像二进制中的 0.1 ( 0.1 = 0.00011001100110011...<-无限个“0011”)
一切都在这个概念中。由于您的计算机只能考虑有限的数字集(十进制或二进制),因此只有一些数字可以在您的计算机中准确表示......
正如 Jon 所说,3 是一个质数,它不是 10 的因数,所以 1/3 不能用以 10 为基数的有限数量的元素来表示。
即使使用任意精度的算术,基数 2 中的编号位置系统也无法完全描述 6.1,尽管它可以表示 61。
对于 6.1,我们必须使用另一种表示形式(如十进制表示形式,或允许以 2 为基数或以 10 为基数表示浮点值的 IEEE 854)
评论
例如,数字 61.0 具有精确的二进制表示,因为任何数字的整数部分始终是精确的。但这个数字 6.10 并不准确。我所做的只是将小数点后一位移动,突然间我从 Exactopia 变成了 Inexactville。从数学上讲,这两个数字之间不应该有本质的区别——它们只是数字。
让我们暂时离开基地 10 和 2 的细节。让我们问 - 在基数中,哪些数字具有终止表示,哪些数字没有?片刻的思考告诉我们,当且仅当存在一个整数,即整数时,一个数字才具有终止表示。b
x
b
n
x b^n
因此,例如,有一个终止的 10 表示,因为我们可以选择一个整数,然后选取一个整数。但是没有,因为无论我们选择什么,我们都无法摆脱这 3 个。x = 11/500
n = 3
x b^n = 22
x = 1/3
n
第二个例子促使我们思考因数,我们可以看到,对于任何有理数(假设是最低的),我们都可以通过比较 和 的质因数分解来回答这个问题。如果有任何质因数不是在质因数分解中,我们将永远无法找到一个合适的去掉这些因数。x = p/q
b
q
q
b
n
因此,对于以 10 为基数,任何具有 2 或 5 以外的质因数的地方都不会有终止表示。p/q
q
所以现在回到以 10 和 2 为底数,我们看到任何具有终止 10 表示的有理数将恰好是其素因数分解中只有 s 和 s 的形式;并且当其质因式分解中只有 s 时,相同的数字将具有终止的 2 表示。p/q
q
2
5
q
2
但其中一个案例是另一个案例的子集!每当
q
其质因数分解中只有 s2
显然,这也是事实
q
在其质因式分解中只有 s 和 s2
5
或者,换句话说,每当 P/Q 具有终止 2 表示时,P/Q
就具有终止 10 表示。然而,反之则不成立 - 每当其质因式分解中有 5 时,它就会有一个终止的 10 表示,但没有终止的 2 表示。这是其他答案提到的示例。
q
0.1
因此,我们有了您问题的答案 - 因为 2 的质因数是 10 的质因数的子集,所以所有 2 终止数都是 10 终止数,反之亦然。这不是关于 61 对 6.1 - 而是大约 10 对 2。
作为结束语,如果由于某些怪癖,人们使用(比如)以 17 为基数,但我们的计算机使用以 5 为基数,那么您的直觉永远不会因此而误入歧途——在这两种情况下都不会有(非零、非整数)数字终止!
评论
0.15
x
b
上面的高分答案一针见血。
首先,您在问题中混合了以 2 为基数和以 10 为基数的基数,然后当您在右侧放置一个不可整除到基数的数字时,您会遇到问题。就像十进制的 1/3 一样,因为 3 不会进入 10 的幂,或者二进制的 1/5 不会进入 2 的幂。
另一条注释虽然永远不要使用等于浮点数,句点。即使它是一个精确的表示,在某些浮点系统中也有一些数字可以用不止一种方式准确表示(IEEE对此很糟糕,它一开始就是一个可怕的浮点规范,所以预计会很头疼)。这里没有什么不同,1/3 不等于计算器上的数字 0.3333333,无论小数点右边有多少个 3。它足够接近或可以足够接近,但不相等。因此,根据四舍五入,您会期望 2*1/3 不等于 2/3。切勿将 equal 与浮点一起使用。
重复我在对斯基特先生的评论中所说的话:我们可以用十进制表示法表示 1/3、1/9、1/27 或任何有理数。我们通过添加一个额外的符号来做到这一点。例如,在数字的十进制扩展中重复的数字上的线条。我们需要将十进制数表示为二进制数序列的是 1) 二进制数序列,2) 基点,以及 3) 其他一些符号来表示序列的重复部分。
Hehner 的引号符号是做到这一点的一种方式。他使用引号来表示序列的重复部分。文章:http://www.cs.toronto.edu/~hehner/ratno.pdf 和维基百科条目:http://en.wikipedia.org/wiki/Quote_notation。
没有什么说我们不能在表示系统中添加符号,因此我们可以使用二进制引号表示法精确地表示十进制有理数,反之亦然。
评论
我很惊讶还没有人说过这一点:使用连续分数。任何有理数都可以以这种方式用二进制有限地表示。
一些例子:
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
这是一个极好的有理近似。
评论
正如我们一直在讨论的,在浮点运算中,小数点 0.1 不能用二进制完美表示。
浮点数和整数表示形式为所表示的数字提供网格或格子。算术完成后,结果会从网格中掉落,必须通过四舍五入将结果放回网格中。示例是二进制网格上的 1/10。
如果我们像一位绅士所建议的那样使用二进制编码的十进制表示,我们是否能够将数字保留在网格上?
评论
在等式中
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))
一个简单的答案:计算机没有无限的内存来存储分数(在将十进制数表示为科学记数法的形式之后)。根据 IEEE 754 双精度浮点数标准,我们只有 53 位的限制来存储分数。 欲了解更多信息: http://mathcenter.oxford.emory.edu/site/cs170/ieee754/
我就不费心重复其他20个答案已经总结的内容,所以我只简单回答:
您的内容中的答案:
为什么以两个数字为基数不能准确地代表某些比率?
出于同样的原因,小数不足以表示某些比率,即分母包含除 2 或 5 以外的质因数的不可约分数,至少在其小数扩展的尾数中总是有一个不定字符串。
为什么十进制数不能用二进制精确表示?
从表面上看,这个问题是基于对价值观本身的误解。任何数字系统都不足以表示任何数量或比率,因为事物本身告诉你它既是一个数量,同时也给出了关于表示的内在价值的解释。因此,所有的定量表示和一般的模型都是象征性的,只能在后验理解,即在人们被教导如何阅读和解释这些数字之后。
由于模型是主观的东西,只要它们反映了现实,就其真实而言,我们不需要严格地将二进制字符串解释为负幂和正幂之和。相反,人们可能会观察到,我们可以创建一组任意的符号,这些符号使用以二为基数或任何其他基数来精确表示任何数字或比率。试想一下,我们可以用一个词,甚至一个符号来指代所有的无穷大,而不需要“显示无穷大”本身。
例如,我正在为混合数字设计二进制编码,以便我可以比 IEEE 754 浮点数具有更高的精度和准确性。在撰写本文时,我们的想法是用一个符号位、一个倒数位、一定数量的标量来确定“放大”小数部分的程度,然后将剩余的位平均分配在混合数的整数部分和后者的定点数之间, 如果设置了倒数位,则应解释为除以该数字的 1。这样做的好处是,我可以使用具有无限十进制扩展的数字,这些倒数确实具有终止十进制扩展,或者直接表示分数,可能作为近似值,具体取决于我的需要。
你不能用二进制精确地表示 0.1,原因与你不能用传统的英制尺子测量十分之一英寸的原因相同。
英制标尺,就像二进制分数一样,都是关于两半的。你可以测量半英寸,或四分之一英寸(当然是一半的一半),或八分之一,或十六分之一,等等。
但是,如果您想精确测量十分之一英寸,那您就不走运了。它不到八分之一英寸,但超过十六分之一。如果你试着更精确,你会发现它比 3/32 多一点,但比 7/64 少一点。我从未见过一个真正的尺子的渐变比 64 精细,但如果你算一算,你会发现 1/10 小于 13/128,它超过 25/256,它超过 51/512。你可以越来越精细,达到 1024 度、2048 度、4096 度和 8192 度,但你永远不会找到一个明确的标记,即使是在无限精细的 2 底尺上,也恰好对应于 1/10 或 0.1。
不过,你会发现一些有趣的东西。让我们看一下我列出的所有近似值,对于每个近似值,明确记录 0.1 是小于还是大于:
分数 | 十进制 | 0.1 是... | 作为 0/1 |
---|---|---|---|
1/2 | 0.5 | 少 | 0 |
1/4 | 0.25 | 少 | 0 |
1/8 | 0.125 | 少 | 0 |
1/16 | 0.0625 | 大 | 1 |
3/32 | 0.09375 | 大 | 1 |
7/64 | 0.109375 | 少 | 0 |
13/128 | 0.1015625 | 少 | 0 |
25/256 | 0.09765625 | 大 | 1 |
51/512 | 0.099609375 | 大 | 1 |
103/1024 | 0.1005859375 | 少 | 0 |
205/2048 | 0.10009765625 | 少 | 0 |
409/4096 | 0.099853515625 | 大 | 1 |
819/8192 | 0.0999755859375 | 大 | 1 |
现在,如果你读下最后一列,你会得到.1/10 的无限重复二进制分数是 0.0001100110011,这绝非巧合......0001100110011
上一个:列出 N 以下所有素数的最快方法
评论