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

Math 为什么十进制数不能用二进制精确表示?,math,floating-point,Math,Floating Point,关于浮点表示法,SO上已经发布了几个问题。例如,十进制数0.1没有精确的二进制表示,因此使用==运算符将其与另一个浮点数进行比较是危险的。我理解浮点表示背后的原理 我不明白的是,为什么从数学的角度来看,小数点右边的数字比左边的数字更特殊 例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分总是精确的。但数字6.10并不准确。我所做的只是把小数点移动一位,突然我从精确到了不精确。从数学上讲,这两个数字之间应该没有本质上的区别——它们只是数字 相比之下,如果我将小数点朝另一个方向移动一位来

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

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

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

相比之下,如果我将小数点朝另一个方向移动一位来生成数字610,我仍然处于Exactopia状态。我可以继续朝那个方向走,6100,6100,6100,6100,6100,6100,但它们仍然是精确的,精确的,精确的。但一旦小数点超过某个阈值,数字就不再精确

发生什么事了

编辑:为了澄清,我不想讨论行业标准表示法,比如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是导致十进制和二进制之间出现问题的素数

处理有限表示问题的旁注:
一些浮点小数点类型具有固定大小,如System。decimal其他类型,如java.math.BigDecimal,其大小是任意的,但它们在某个点上会达到一个极限,无论是系统内存还是数组的理论最大大小。然而,这与这个答案的主要观点是完全不同的。即使你有一个真正任意大数量的位可以玩,你仍然不能用浮点二进制表示法精确地表示十进制0.1。与另一种方法相比:给定任意数量的十进制数字,您可以精确地表示任何可以精确表示为浮点二进制点的数字。

如果您用浮点来表示足够大的数字,就像它可以表示指数一样,那么您也将以小数点前面的不精确性结束。所以我不认为你的问题是完全正确的,因为前提是错误的;移位10并不总是会产生更高的精度,因为在某些点上,浮点数必须使用指数来表示数字的大,这样也会失去一些精度。

BCD--表示是精确的。它们的空间效率不高,但在这种情况下,这是一个必须权衡的准确性问题。

问题是,您不知道该数字是否正好是61.0。考虑这一点:


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

根数学原因是,当你处理整数时,它们是可数无限的

这意味着,即使它们的数量是无限的,我们也可以计算出序列中的所有项,而不会跳过任何项。这意味着,如果我们想得到列表中第61000000000000位的项目,我们可以通过公式计算出来

然而,实数是不可数无限的。你不能说给我61000000000000的真实数字然后回来 答案。原因是,即使在0和1之间,当您考虑浮点值时,值的数量也是无限的。这同样适用于任意两个浮点数

更多信息:

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

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

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


另一种思考方式是,当你注意到61.0是以10为基数的完美表示,小数点的前后移动并没有改变这一点,你正在执行10的10^1,10^1的幂的乘法。在浮点运算中,乘以二的幂不会影响数字的精度。试着取61.0,反复将其除以3,以说明一个完全精确的数字是如何失去精确的表示形式的。

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

有理数的数量是无限的,表示它们的位数是有限的。请参阅。

有一个阈值,因为数字的含义已从整数变为非整数。代表61,你有6*10^1+1*10^0;10^1和10^0都是整数。6.1是6*10^0+1*10^-1,但10^-1是1/10,这绝对不是整数。这就是你最终陷入不精确境地的原因。

平行线可以由分数和整数组成。有些分数,如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被加到分数后面
B11B10 B9 B8B7 B6 B5 B4 B3 B2 B1 B0

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

思考问题的一种方式是用科学符号之类的东西。我们习惯于看到用科学符号表示的数字,如6.022141*10^23。浮点数在内部使用类似的格式存储-尾数和指数,但使用二的幂而不是十的幂

您的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.10000110011…b*2^10b 比如说: 1.100001100110B*2^10b

这是t的四舍五入 尾数会导致与浮点值相关的精度损失。即使尾数可以精确表示,例如,仅添加两个数字时,如果尾数需要太多的数字才能在标准化指数后拟合,我们仍然可以得到数字损失


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

这是一个好问题

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

所有数字都可以用十进制表示或二进制2的补码表示。全都是

但是,其中大多数要求无限多的元素0或1表示二进制位置,或0、1到9表示十进制位置

像十进制的1/3,1/3=0.3333。。。 例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分总是精确的。但数字6.10并不准确。我所做的只是把小数点移动一位,突然我从精确到了不精确。从数学上讲,这两个数字之间应该没有本质上的区别——它们只是数字

让我们暂时离开基数10和基数2的细节。让我们来问一下——在基数b中,哪些数字有终止表示,哪些数字没有?片刻的思考告诉我们,一个数字x有一个终止的b-表示,当且仅当存在一个整数n,使得xb^n是一个整数

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

第二个例子促使我们思考因子,我们可以看到,对于任何假设为最低条件的有理x=p/q,我们可以通过比较b和q的素因子来回答这个问题。如果q有任何不在b的素因子分解中的素因子,我们将永远无法找到合适的n来去除这些因子

因此,对于基数10,q具有除2或5以外的素数因子的任何p/q将不具有终止表示

现在回到基数10和基数2,我们看到,当q的素因式分解中只有2和5时,具有终止10表示的任何有理数都将是形式p/q;当q在其素因子分解中只有2时,同样的数将有一个终止的2-表示

但其中一种情况是另一种情况的子集!无论何时

q的素因式分解只有2

显然,这也是事实

q的素因式分解只有2和5

或者,换句话说,只要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,那么你的直觉永远不会被这一点引入歧途——在这两种情况下都不会有非零、非整数终止

上面的高分答案很准确

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


另一个注释是句号,尽管它从不与浮点数相等。即使它是一个精确的表示形式,但在某些浮点系统中,有些数字可以用多种方式精确表示IEEE在这方面做得不好,它从一开始就是一个可怕的浮点规范,所以请期待令人头痛的问题。这里没有区别1/3不等于计算器上的数字0.3333333,不管小数点右边有多少个3。它是或可以足够接近,但不相等。所以你会认为2*1/3不等于2/3,这取决于四舍五入。永远不要使用等于浮点。

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

Hehner的引号表示法就是这样做的一种方法。他使用引号符号来表示序列的重复部分。文章:和维基百科条目:


没有任何东西表明我们不能在表示系统中添加符号,因此我们可以使用二进制引号表示法精确地表示十进制有理数,反之亦然。

我很惊讶,现在还没有人这样说:使用。这样,任何有理数都可以用二进制有限表示

一些例子:

1/30.3333

0; 3
5/9 0.5555

0; 1, 1, 4
10/43 0.232558139534883720930

0; 4, 3, 3
9093/18478 0.49209871198162138759060179673

0; 2, 31, 7, 8, 5
从这里开始,有多种已知的方法将整数序列存储在内存中

除了以完美的精度存储数字外,连分数还有其他一些好处,例如最佳有理近似。如果您决定提前终止连续分数中的数字序列,那么在重新组合为分数时,剩余的数字将为您提供可能的最佳分数。这就是如何找到pi的近似值:

π的连分数:

3; 7, 15, 1, 292 ...
将序列终止于1,则得到分数:

3; 7, 15, 1, 292 ...
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))


一个简单的答案是:计算机没有无限的内存来存储分数,因为它将十进制数表示为科学记数法的形式。根据IEEE 754双精度浮点数标准,存储分数的限制只有53位。
更多信息:

这是一个非常好的例子,先生!。。。但愿我能投两次票。我已经被问了太多次了。这几乎就像人们不能在10垒之外思考。呵呵,世界上有10种人——懂二进制和不懂二进制。@JonSkeet:Ctrl+Alt+Delete只用两个手指看起来很尴尬。@muusbolla:No。十进制表示法1和十进制表示法0.9表示的数字。。。小数点后无限重复的9等于。也许最简单的方法是:让x=0.9。。。。请注意,10x=9.9。。。。。因此9x=10x-x=9.9…-0.9... = 所以9x=9,x=1。有其他的方法可以看出这一点,但我相信这是最简单的。实际上,有理数是可数无限的。但并非每个实数都是有理数。我当然可以产生一个精确的十进制数序列,它最终会达到你们想要给我的任何精确的十进制数。如果你也需要处理无理数,你就会进入不可数的无穷集合。没错,我应该说的是实数,而不是浮点。在哪一点上,逻辑变得不那么适用,依我看——因为我们不仅不能用二进制浮点数来处理所有实数,而且我们甚至不能处理所有有理数,比如0.1。换句话说,我不认为这真的与可数性有关:@jonskeet我知道不同意Jon Skeet会破坏一条基本的自然法则,所以我当然不会这么做:但是,我认为可以把数字的内部表示看作是一组外部表示的值的索引。按照这种思路,你可以看到,无论你的索引列表有多大,即使你有无限的精度,你仍然无法表示所有的实数。@TM:但OP并不是要表示所有的实数。他试图表示所有精确的十进制数,它是有理数的一个子集,所以只能是可数无限的。如果他使用一组无限位作为十进制浮点类型,那么他就没事了。使用这些位作为二进制浮点数类型会导致十进制数出现问题。但是,即使有无限多的位,如果使用二进制浮点数,仍然无法准确表示0.1,就像无法表示一样

即使有无限位数,也能准确地发送1/3的小数。@Jon这是不正确的:有无限位数的小数,我可以准确地表示“三分之一”。现实世界的问题是,在物理上不可能有无穷多的小数或位。BCD与任何其他基都不一样精确。示例:如何在BCD中精确表示1/3?不能。BCD是十进制的精确表示,因此它的名称的小数部分。1/3也没有精确的十进制表示法。你可以将1/3表示为分数本身。您不需要无限多的位来表示它。你只需要把它表示成分数1/3,而不是取1除以3的结果。有几个系统是这样工作的。然后你需要一种方法来使用标准的/*+-和类似的运算符来处理分数的表示,但是这很简单-你可以用纸和笔来完成这些操作,教计算机来做也没什么大不了的。我说的是二进制2的补码表示法。因为,当然,使用另一种表示法可能有助于你用有限个元素来表示一些数字,而你将需要无限个元素来表示另一些符号系统,如果我们知道循环的开始和结束。人类非常擅长检测周期。但是,一般来说,计算机不是。为了有效地使用重复符号,计算机必须能够计算出循环在哪里。例如,对于数字1/3,循环立即开始。但是对于数字1/97,只有在你算出至少96位的答案后,循环才会显示出来。事实上,你需要96*2+1=193个数字才能确定。事实上,计算机检测周期一点也不难。如果你读过Hehner的论文,他描述了如何检测各种算术运算的周期。例如,在使用重复减法的除法算法中,当你看到之前看到的差异时,你就知道循环从哪里开始。此外,问题是如何准确地表示数字。有时精确的表示意味着大量的位。引号表示法的美妙之处在于,Hehner证明,与标准的32位固定长度表示法相比,表示法的大小平均节省了31%。当然,十进制数字。但这只是定义。不能用十进制表示1/3,正如不能用二进制表示0.1一样。任何量化方案对于无限大的一组数字都是失败的。但你如何用二进制表示呢?例如,15需要表示4位,而292需要表示9位。硬件甚至软件如何知道位边界在哪?这是效率与精度的折衷。您可能会发现这有助于准确理解浮点数字中的情况:。在二进制中,数字3表示为2ª+2°=2+1。又好又容易。现在,看看1/3。你怎么用2的负幂来表示呢?做一点实验,你会发现1/3等于无限序列2^-2+2^-4+2^-6+2^-8+的和,也就是说,用二进制表示不那么容易。Jon Skeet很好地回答了你体内的问题。缺少的一件事是你实际上问了两个不同的问题。题目是为什么十进制数不能用二进制精确表示?答案是,他们可以。在标题和正文之间,您将二进制和浮点表示的概念混为一谈。浮点数是一种以精度为代价,用固定数量的二进制数字表示十进制数的方法。二进制只是一个不同的计数基数,可以表示任何数字,只要给定无限位数,十进制就可以。有几种系统都有精确的十进制表示。它的工作原理和你描述的差不多。SQL十进制类型就是一个例子。LISP语言内置了它。有几个商业和开源库可以使用精确的十进制计算。只是没有硬件支持,而且大多数语言和硬件都实现了IEEE标准,用32位或64位表示无限多的数字。这个问题似乎是离题的,因为它是关于数学的,即使是与编程相关的数学,也会更好,那么为什么alert0.15*0.15显示0.0225?@MichaelGeiser简短回答:在显示点舍入。您认为的0.15实际上存储为IEEE双精度'0.149999999999448884876874'。请参阅。非常清晰的点代码示例!我希望我能投你一票!我必须使用一些函数来探索舍入截止发生的位置。我仍然很惊讶,我们竟然要处理这些垃圾;因为人们几乎100%的时间都在以10为基数工作,而我们使用非整数的时间太多了,以至于你会认为
“浮点数学的默认实现可以处理这种胡说八道。@MichaelGeiser使用base 2的电路比使用base 10的电路更小、更快、更节能。今天,我们也许能够证明这一开销是合理的,但在20世纪70年代,当制定标准时,这是一件大事。在没有处理器电路直接支持的情况下尝试这样做更糟糕,除了速度上的数量级差异之外。这个答案比Jon Skeet自己解释得更好!
2^5 + 2^(log(0.41) / log(2))