已经向 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 ...
这些数字也没有任意限制。位置向左和向右无限增加。
如果您有足够的空间,小数可以精确表示 - 只是不能用浮点 二进制 点数表示。如果您使用浮点 decimal 点类型(例如 .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。将其与相反的方式进行比较:给定任意数量的十进制数字,您可以精确地表示任何可以精确地表示为浮点二进制点的数字。
例如,数字 61.0 具有精确的二进制表示,因为任何数字的整数部分总是精确的。但数字 6.10 并不准确。我所做的只是将小数点移动一位,然后我突然从 Exactopia 转到了 Inexactville。从数学上讲,这两个数字之间应该没有本质的区别——它们只是数字。
让我们暂时远离基数 10 和 2 的细节。让我们问一下 - 在基数 b
中,哪些数字具有终止表示,哪些数字没有?片刻的想法告诉我们,一个数 x
有一个终止的 b
表示当且仅当存在一个整数 n
使得 x b^n
是一个整数。
因此,例如,x = 11/500
有一个终止的 10 表示,因为我们可以选择 n = 3
,然后选择 x b^n = 22
,一个整数。但是 x = 1/3
没有,因为无论我们选择什么 n
,我们都无法摆脱 3。
第二个例子提示我们考虑因子,我们可以看到对于任何 有理 x = p/q
(假设是最低限度的),我们可以通过比较 {2 的素因子分解来回答这个问题} 和 q
。如果 q
有任何不在 b
的素因子分解中的素因子,我们将永远无法找到合适的 n
来消除这些因子。
因此,对于以 10 为底的 任何 p/q
,其中 q
的质因数不是 2 或 5 将没有终止表示。
所以现在回到基数 10 和 2,我们看到任何具有终止 10 表示的有理数都将是 p/q
的形式,恰好当 q
在其素因数分解中只有 2
和 5
时;并且恰好当 q
在其素数分解中只有 2
时,相同的数字将具有终止的 2 表示。
但是其中一种情况是另一种情况的子集!每当
在其素因数分解中只有 2s
显然也是如此
在其素因数分解中只有 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 为底,那么您的直觉永远不会因此而误入歧途 - 不会有(非零,非整数)数字终止在这两种情况下!
0.15
实际上是(当存储为 IEEE 双精度时)`0.149999999999999994448884876874`。请参阅jsfiddle。
根本(数学)原因是,当您处理整数时,它们是可数无限的。
这意味着,即使它们的数量是无限的,我们也可以“数出”序列中的所有项目,而不会跳过任何项目。这意味着如果我们想要获得列表中第 610000000000000
位的项目,我们可以通过公式计算出来。
然而,实数是不可数无限的。您不能说“给我位置 610000000000000
的实数”并得到答案。原因是,即使在 0
和 1
之间,当您考虑浮点值时,也有无限数量的值。这同样适用于任何两个浮点数。
更多信息:
http://en.wikipedia.org/wiki/Countable_set
http://en.wikipedia.org/wiki/Uncountable_set
更新:抱歉,我似乎误解了这个问题。我的回答是关于为什么我们不能代表每一个真正的价值,我没有意识到浮点被自动归类为理性。
重复我在对斯基特先生的评论中所说的话:我们可以用十进制表示法表示 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。
没有什么说我们不能在我们的表示系统中添加一个符号,所以我们可以使用二进制引号表示法精确地表示十进制有理数,反之亦然。
BCD - Binary-coded Decimal - 表示是准确的。它们不是很节省空间,但在这种情况下,您必须为准确性做出权衡。
这是一个很好的问题。
你所有的问题都是基于“我们如何表示一个数字?”
所有数字都可以用十进制表示或二进制(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,我们必须使用另一种表示法(如十进制表示法,或 IEEE 854,它允许以 2 或 10 为基数来表示浮点值)
如果你用浮点数做一个足够大的数字(因为它可以做指数),那么你最终也会在小数点前出现不精确。所以我不认为你的问题是完全有效的,因为前提是错误的;移位 10 并不总是会产生更高的精度,因为在某些时候浮点数将不得不使用指数来表示数字的大小,并且也会因此而失去一些精度。
这与您不能以 10 为基数精确表示 1/3 的原因相同,您需要说 0.33333(3)。在二进制中,它是同一类型的问题,但只发生在不同的数字集上。
(注意:我将在此处附加“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,一个不错的短数字。但是,如果我们将其转换为二进制,这是什么?我们将按照通常的方式进行 - 尽可能减去 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
正是尾数的这种舍入导致我们与浮点值相关联的精度损失。即使尾数可以精确表示(例如,当仅添加两个数字时),如果在标准化指数后尾数需要太多数字来适应,我们仍然会丢失数字。
当我们将十进制数字四舍五入到可管理的大小并只给出它的前几位时,我们实际上一直在做这种事情。因为我们用十进制表示结果,所以感觉很自然。但是,如果我们将小数四舍五入,然后将其转换为不同的基数,那么它看起来就像我们通过浮点四舍五入得到的小数一样难看。
我很惊讶没有人这么说:使用 continued fractions。任何有理数都可以用这种方式有限地表示为二进制。
一些例子:
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
这是一个极好的有理近似。
在等式中
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.0 确实具有精确的浮点运算——但并非所有整数都如此。如果你编写了一个循环,将一个双精度浮点数和一个 64 位整数加一,最终你会达到一个 64 位整数完美地表示一个数字的点,但浮点不会——因为没有足够的有效位。
到达小数点右侧的近似点要容易得多。如果您开始以二进制浮点数写出所有数字,那会更有意义。
另一种思考方式是,当您注意到 61.0 可以完美地以 10 为底表示,并且移动小数点并不会改变这一点时,您正在执行乘以 10 的幂 (10^1, 10^-1 )。在浮点数中,乘以 2 的幂不会影响数字的精度。尝试将 61.0 反复除以 3,以说明完美精确的数字如何失去其精确表示。
有一个阈值,因为数字的含义已经从整数变为非整数。要表示 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)就不能以十进制形式表示。因为浮点是基于二进制的,所以特殊情况会发生变化,但会出现同样的精度问题。
有无限数量的有理数和有限数量的比特来表示它们。请参阅http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems。
你知道整数吗?每个位代表 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
上面的高分答案确定了它。
首先,您在问题中混合了基数 2 和基数 10,然后当您在右侧放置一个不可整除的数字时,您会遇到问题。就像十进制的 1/3,因为 3 不等于 10 的幂,或者二进制的 1/5 不等于 2 的幂。
另一个评论虽然永远不要使用等于浮点数,句号。即使它是一个精确的表示,在一些浮点系统中也有一些数字可以用不止一种方式准确表示(IEEE 对此很不好,这是一个可怕的浮点规范,所以会让人头疼)。这里没有什么不同,1/3 不等于计算器上的数字 0.3333333,无论小数点右侧有多少个 3。它是或可以足够接近但不相等。所以你会期望像 2*1/3 这样的东西不等于 2/3,具体取决于四舍五入。永远不要使用等于浮点数。
正如我们一直在讨论的,在浮点运算中,十进制 0.1 不能完美地用二进制表示。
浮点和整数表示为表示的数字提供网格或格子。算术完成后,结果会脱离网格,必须通过四舍五入将其放回网格上。示例是二进制网格上的 1/10。
如果我们像一位绅士建议的那样使用二进制编码的十进制表示,我们能否将数字保留在网格上?
一个简单的答案:计算机没有无限的内存来存储分数(在将十进制数表示为科学记数法形式之后)。根据双精度浮点数的 IEEE 754 标准,我们只有 53 位的限制来存储小数。欲了解更多信息:http://mathcenter.oxford.emory.edu/site/cs170/ieee754/
其他20个答案已经总结的内容我就不重复了,简单回答一下:
您的内容中的答案:
为什么不能以两个数字为基数准确表示某些比率?
出于同样的原因,小数不足以表示某些比率,即分母包含除 2 或 5 以外的素因数的不可约分数,至少在其小数展开的尾数中总是有一个不定串。
为什么十进制数不能用二进制精确表示?
从表面上看,这个问题是基于对价值观本身的误解。任何数字系统都不足以以事物本身告诉您它既是数量的方式来表示任何数量或比率,同时也提供了关于表示的内在价值的解释。因此,所有定量表示和一般模型都是象征性的,只能在事后理解,即在教一个人如何阅读和解释这些数字之后。
由于模型是反映现实的真实的主观事物,因此我们不需要将二进制字符串严格解释为两个负幂和正幂的总和。相反,人们可能会观察到我们可以创建任意一组符号,这些符号使用以 2 为底或任何其他底数来精确地表示任何数字或比率。试想一下,我们可以用一个词甚至一个符号来指代所有的无限,而无需“显示无限”本身。
例如,我正在为混合数字设计二进制编码,以便我可以比 IEEE 754 浮点数具有更高的精度和准确度。在写这篇文章的时候,想法是有一个符号位,一个倒数位,一个标量的一定数量的位来确定要“放大”小数部分的多少,然后将剩余的位在混合数的整数部分,后者是定点数,如果设置了倒数位,则应将其解释为除以该数。这样做的好处是允许我通过使用具有终止小数扩展的倒数来表示具有无限小数扩展的数字,或者根据我的需要,直接作为分数,可能作为近似值。
你不能用二进制精确地表示 0.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 less 0 25/256 0.09765625 greater 1 51/512 0.099609375 greater 1 103/1024 0.1005859375 less 0 205/2048 0.10009765625 less 0 409/4096 0.099853515625 greater 1 819/8192 0.0999755859375 greater 1
现在,如果您阅读最后一列,您会得到 0001100110011
。 1/10 的无限重复二进制分数是 0.0001100110011 并非巧合...
1
和十进制表示0.9...
(在小数点后无限重复9
)表示的数字是相等的。也许最简单的方法是:让 x =0.9...
。请注意10x = 9.9....
。因此9x = 10x - x = 9.9... - 0.9... = 9
使得9x = 9
和x = 1
。还有其他方法可以看到这一点,但我相信这是最简单的。