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

Bar*_*own 275 math floating-point

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

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

例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分始终是精确的.但数字6.10并不准确.我所做的只是将十进制移动到一个地方,然后我突然从Exactopia转到了Inexactville.在数学上,两个数字之间应该没有内在差异 - 它们只是数字.

相比之下,如果我将小数位移到另一个方向以产生数字610,我仍然在Exactopia中.我可以继续向那个方向前进(6100,610000000,610000000000000),它们仍然是精确,准确,准确的.但是一旦小数越过某个阈值,数字就不再精确了.

这是怎么回事?

编辑:为了澄清,我想远离关于行业标准表示的讨论,例如IEEE,并坚持我认为是数学上"纯粹"的方式.在基数10中,位置值为:

... 1000  100   10    1   1/10  1/100 ...
Run Code Online (Sandbox Code Playgroud)

在二进制文件中,它们将是:

... 8    4    2    1    1/2  1/4  1/8 ...
Run Code Online (Sandbox Code Playgroud)

对这些数字也没有任何限制.位置无限增加到左侧和右侧.

Jon*_*eet 349

如果您有足够的空间,则可以精确表示十进制数- 只是不通过浮动二进制点数.如果使用浮动小数点类型(例如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.将其与另一种方式进行比较:给定任意数量的十进制数字,您可以精确地表示任何可以表示为浮动二进制点的数字.

  • 通过总共超过2个手指来拧紧人性. (111认同)
  • @JonSkeet:*Ctrl + Alt + Delete*只用两根手指就会显得很尴尬. (83认同)
  • 是的,世界上有10种人 - 那些懂二元的人和不理解二元的人. (37认同)
  • @muusbolla:不.用十进制表示"1"表示的数字和十进制表示"0.9 ......"(无限重复小数点后的"9")相等.也许最容易看到的方法是:让x =`0.9 ......`.注意`10x = 9.9 ....`.因此`9x = 10x - x = 9.9 ... - 0.9 ... = 9`使得`9x = 9`和'x = 1`.还有其他方法可以看到这一点,但我相信这是最简单的. (20认同)
  • 这是一个该死的好例子先生! (8认同)
  • ...希望我能两次投票.我被问过这个问题太多次了.这几乎就像人们不能在基地之外思考.呵呵 (5认同)
  • 值得注意的是.3333333无限重复,不是1/3的近似值,实际上等于1/3.它与.999999完全相同的概念...等于1. (3认同)
  • @Lars Haugseth:想想Ctrl + Alt*踏板*:o) (3认同)
  • @ChrisW:那是因为你确定"确切"的方式.我的观点是,无论你在0.333333结束时有多少"3"......你不会得到1/3 ...而如果你给我任何形式的确切二进制数[10100010101].[101010101 ] 因为在二进制点的每一边都有你想要的大小,我可以给你一个十进制值,绝对完全相同的值,在有限的数字位数. (2认同)
  • @Barry:不是相对素数,但是一个基数的素数因子不是另一个基数的因子(例如10和6不是相对素数,但是1/10是基数6中的无穷小数,和1/6基数为10)是无限小数. (2认同)
  • @muusbolla:我认为这取决于各种精确的定义,特别是"等于".例如,有意义的是,如果没有d的非零值,其中x + d = y,则x = y.对于0.9重复和1 ...之间的差异,您找不到任何d值. (2认同)

Jam*_* M. 33

不精确的原因是数字基础的性质.在基数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.


Aak*_*shM 23

例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分始终是精确的.但数字6.10并不准确.我所做的只是将十进制移动到一个地方,然后我突然从Exactopia转到了Inexactville.在数学上,两个数字之间应该没有内在差异 - 它们只是数字.

让我们离开基地10和2的细节片刻.让我们问一下 - 在基础上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代表.

但其中一个案例是另一个案例的子集!每当

q只有2其主要因素分解

显然也是如此

q在其主要因子分解中只有2s和5s

换句话说,只要p/q有终止的2表示,p/q就有一个终止的10表示.反过来但是没有持有-只要q在它的质因数分解有5,它将有一个终端10的表示,但不是一个终止2表示.这是0.1其他答案提到的例子.

所以我们有你的问题的答案 - 因为2的素数因子是10的素数因子的子集,所有2终止数字是10终止数字,但反之亦然.它不是61而不是6.1--大约是10而不是2.

作为一个结尾注释,如果一些怪人使用(比如)基数为17但我们的计算机使用了基数为5,那么你的直觉绝不会被误导 - 没有(非零,非整数)数字终止在这两种情况下!

  • @MichaelGeiser简短回答:在显示点四舍五入.您认为`0.15`实际上是(当存储为IEEE双倍时)`0.14999999999999994448884876874`.见[jsfiddle](http://jsfiddle.net/j69gLdvr/). (5认同)
  • 这个答案比乔恩·斯基特本人解释得更好! (2认同)

TM.*_*TM. 15

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

这意味着,即使它们数量无限,我们也可以"计算"序列中的所有项目,而不会跳过任何项目.这意味着如果我们想要将项目放在610000000000000列表中的第th位置,我们可以通过公式计算出来.

然而,实数无数无限.你不能说"给我位置上的实数610000000000000"并得到答案.原因是,当您考虑浮点值时,即使在0和之间1,也存在无限数量的值.任何两个浮点数都是如此.

更多信息:

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

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

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

  • 实际上,有理数*是无数的.但并非每个*真实*数字都是有理数.我当然可以生成一个精确的十进制数序列,它将达到你想要给我的任何精确的十进制数.如果你需要处理*无理*数字,那么你就会进入无数无限集. (6认同)
  • @TM:但是OP并没有试图代表所有实数.他试图表示所有精确的*十进制*数,这是*有理数*的一个子集,因此只能数无数.如果他使用无限位*作为十进制浮点类型*那么他就没事了.它将这些位用作*二进制*浮点类型,导致十进制数问题. (3认同)
  • 在这一点上,逻辑变得不太适用,IMO - 因为我们不仅不能使用二进制浮点处理所有“实”数,而且甚至不能处理所有“有理”数(例如 0.1)。换句话说,我认为这与可数性根本无关:) (2认同)

nto*_*end 9

重复我在对Skeet先生的评论中所说的话:我们可以用十进制表示法表示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.

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

  • 实际上,计算机检测循环并不困难.如果您阅读Hehner的论文,他将介绍如何检测各种算术运算的周期.例如,在使用重复减法的除法算法中,当您看到之前看到的差异时,您就知道周期的开始位置. (4认同)
  • 此外,问题是关于准确表示数字.有时精确表示意味着很多位.引号表示法的优点在于Hehner表明,与标准的32位固定长度表示相比,表示的平均大小节省了31%. (3认同)

Ala*_*lan 6

BCD - 二进制编码的十进制 - 表示是精确的.它们的空间效率不是很高,但在这种情况下,您需要做出准确的权衡.

  • BCD是DECIMAL的精确表示,因此它的名称是"十进制"部分.也没有1/3的精确十进制表示. (12认同)
  • BCD 并不比任何其他基数更精确或更不精确。示例:如何用 BCD 精确表示 1/3?你不能。 (2认同)

Boo*_*jum 5

(注意:我将在此处附加“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)

注意我们在那里做了什么来乘以数字。我们将尾数相乘并添加指数。然后,由于尾数大于二,我们通过改变指数来标准化结果。就像我们对十进制科学计数法的数字进行运算后调整指数一样。在每种情况下,我们使用的值都具有有限的二进制表示形式,因此基本乘法和加法运算输出的值也产生具有有限表示形式的值。

现在,考虑如何将 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

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

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


Thi*_*hib 5

这是一个很好的问题。

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

所有数字都可以用十进制表示形式或二进制(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)