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

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

我不明白的是,从math的angular度来看,为什么小数点右边的数字再向左边的那个“特别”呢?

例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分总是精确的。 但数字6.10并不确切。 我所做的只是把小数点后移一位,突然间我从Exactopia变成了Inexactville。 在math上,这两个数字之间应该没有内在的差别 – 它们只是数字。

相比之下,如果我把小数位移到另一个方向来产生数字610,我仍然在Exactopia中。 我可以继续沿着这个方向(6100,610000000,610000000000000),他们仍然是确切的,确切的,确切的。 但是一旦小数点越过某个阈值,数字就不再精确。

这是怎么回事?

编辑:为了澄清,我想远离关于IEEE等行业标准表示的讨论,坚持我认为是math“纯”的方式。 在基数10中,位置值是:

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

在二进制中,他们将是:

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

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

十进制数字可以精确地表示,如果您有足够的空间 – 只是不浮点二进制数字。 如果使用浮点小数点types(例如.NET中的System.Decimal ),则可以精确地表示大量无法完全用二进制浮点表示的值。

让我们从另一个angular度来看 – 在基础10中,你可能会感到舒服,你不能完全expression1/3。 这是0.3333333 …(经常性)。 您不能将0.1表示为二进制浮点数的原因完全是由于相同的原因。 你可以完全表示3,9和27,但不能是1/3,1/9或1/27。

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

尽pipe通常使用0.1作为精确十进制数的最简单示例,但不能准确地用二进制浮点数表示0.1,可以说0.2是一个更简单的例子,因为它是1/5,而5是导致十进制和二进制。


处理有限表示问题的附注:

一些浮点小数点types具有像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个选项来分配这些值。 这个scheme允许最精确的小数点在0附近出现,所以你几乎可以代表0.1。

例如,数字61.0具有精确的二进制表示,因为任何数字的整数部分总是精确的。 但数字6.10并不确切。 我所做的只是把小数点后移一位,突然间我从Exactopia变成了Inexactville。 在math上,这两个数字之间应该没有内在的差别 – 它们只是数字

让我们从基数10和2的详细情况中走出一会儿。让我们问 – 在基数b ,什么数字有终止表示,什么数字没有? 一刻的想法告诉我们,当且仅当存在一个整数n使得xb^n是一个整数时,数x具有终止b表示。

因此,例如, x = 11/500具有终止10-表示,因为我们可以selectn = 3 ,然后xb^n = 22 ,一个整数。 然而, x = 1/3不是,因为无论我们select什么,我们将无法摆脱3。

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

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

所以现在回到10和2的基数,我们可以看到任何具有终止10表示的理性在q因子分解只有2 s和5 s的时候就是p/q的forms; 而当q在素数因子分解中只有2 s时,同样的数字将会有一个终止的2-表示。

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

q的素因子只有2 s

这显然也是如此

q的素因子只有2 s和5 s

或者换句话说, 每当p/q具有终止2表示时, p/q具有终止10表示 。 然而,反过来并不成立 – 只要q在其素因子中具有5,它将具有终止10-表示,而不是终止2-表示。 这是其他答案提到的0.1例子。

所以我们有你的问题的答案 – 因为2的主要因素是10的主要因素的子集,所有的2终止数字是10终止的数字,但反之亦然。 这不是61对6.1,而是10对2。

作为一个结束语,如果一些怪癖者使用(比如说)基数为17,但是我们的计算机使用的是基数5,那么你的直觉就不会被误导 – 没有 (非零,非整数)在这两种情况下!

根(math)原因是,当你处理整数时,它们是可以无限的

也就是说,即使它们的数量是无限的,我们也可以“排除”序列中的所有项目,而不会跳过任何项目。 这意味着,如果我们想要获得列表中第610000000000000个位置的项目,我们可以通过公式计算出来。

但是,实数是无限的无限的 。 你不能说“给我6万亿的实际数字”并找回答案。 原因是因为,即使在01之间,当您正在考虑浮点值时,也有无数个值。 任何两个浮点数都是一样的。

更多信息:

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

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

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

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

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

没有什么说我们不能将符号添加到我们的表示系统中,所以我们可以使用二进制引用符号来表示十进制的理由,反之亦然。

BCD – 二进制编码的十进制表示是精确的。 它们不是非常节省空间的,但在这种情况下,这是一个折衷,你必须做出准确的判断。

如果你使用浮点数作为一个足够大的数(因为它可以做指数),那么在小数点前面也会有不精确的结果。 所以我不认为你的问题是完全有效的,因为前提是错误的; 并非如此,移位10将始终创build更高的精度,因为在某些情况下,浮点数将不得不使用指数来表示数字的大小,并且也会失去一些精度。

你不能完全代表基数10的1/3,你需要说0.33333(3)。 在二进制中,它是相同types的问题,但只是出现不同的数字集。

(注意:我将在这里附加'b'来表示二进制数字,其他所有数字都以十进制表示)

思考事物的一种方式就是用科学的符号来expression。 我们习惯于用科学计数法表示数字,例如6.022141 * 10 ^ 23。 浮点数在内部使用类似的格式(尾数和指数)存储,但使用两个幂而不是十个。

你的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)

注意我们在那里做了多less数字。 我们乘以曼陀罗,并增加了指数。 然后,由于尾数大于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 ...

呃哦。 现在我们遇到了麻烦 事实certificate,1.90625 / 1.25 = 1.525,是以二进制表示的重复分数:1.11101b / 1.01b = 1.10000110011 … b我们的机器只有这么多位才能保存尾数,所以他们只会绕过这个分数并假定超过某个点零。 您将61除以10所看到的错误是以下两者之间的差异:

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

这是尾数的舍入,导致我们与浮点值相关的精度损失。 即使尾数可以精确表示(例如,当只添加两个数字时),如果尾数在指数归一化之后需要太多的数字来拟合,我们仍然可以得到数字丢失。

实际上,当我们将十进制数字舍入到一个可pipe理的大小时,我们实际上一直都在做这种事情,只给出它的前几位数字。 因为我们用小数表示结果感觉很自然。 但是,如果我们舍入小数,然后将其转换为不同的基数,它会看起来像我们得到的小数点,因为浮点四舍五入。

这是一个很好的问题。

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

所有的数字都可以用十进制表示或用二进制(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,尽pipe它可以代表61。

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

我很惊讶,没有人说过这一点:使用持续的分数 。 任何有理数都可以有限地用二进制表示。

一些例子:

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 

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

除了以完美的准确度存储数字之外,连续分数还有其他一些好处,例如最佳合理逼近。 如果您决定尽早终止连续分数的数字序列,则剩下的数字(当重新组合到一个分数时)将会给您最好的分数。 这是如何findpi的近似值:

Pi的继续分数:

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

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

355/113

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

在等式中

 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)) 

问题是,你真的不知道这个数字是否恰好是61.0。 考虑这个:

 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,这绝对不是一个整数。 这就是你最终在Inexactville。

并行可以由分数和整数组成。 一些分数例如1/7不能以十进制forms表示,没有很多小数。 因为浮点是基于二进制的,所以特殊情况会改变,但同样的准确度问题也会出现。

有无限数量的有理数,有限的位数来表示它们。 见http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

数字61.0确实有一个确切的浮点运算 – 但是对于所有的整数都不是这样。 如果你写了一个循环,把一个双精度浮点数和一个64位整数相加,那么你最终会达到一个64位整数完全代表一个数的点,但是浮点数不会 – 因为没有足够的重要位。

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

另一种思考的方式是,当你注意到在基数10中61.0是完全可以表示的,并且移动小数点不会改变这个,那么你正在执行乘以10的幂(10 ^ 1,10 ^ -1 )。 在浮点数中,乘以2的幂不影响数字的精度。 尝试取61.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

浮点二进制表示forms:

符号指数分数(我认为不可见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,不pipe小数点右边有多less个3。 它是或可以接近,但不相等。 所以你会期望类似2 * 1/3不等于2/3取决于舍入。 不要与浮点数相等。

正如我们一直在讨论的,在浮点运算中,小数点0.1不能用二进制完美表示。

浮点和整数表示为所表示的数字提供了网格或格子。 当算术完成时,结果会从网格中消失,并且必须通过舍入重新放回网格上。 示例是二进制网格上的1/10。

如果我们使用二进制编码的十进制表示,就像一位先生提出的那样,我们能否把数字保留在网格上?