关于浮点表示,已经向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 ...
对这些数字也没有任何限制 . 位置无限增加到左侧和右侧 .
20 回答
让's step away for a moment from the particulars of bases 10 and 2. Let'问 - 在
b
基础上,什么数字有终止表示,什么数字't? A moment'的思想告诉我们一个数字x
有一个终止b
-representation当且仅当存在整数n
时x b^n
是一个整数 .因此,例如,
x = 11/500
具有终止10表示,因为我们可以选择n = 3
然后x b^n = 22
,一个整数 . 但是x = 1/3
没有,因为无论我们选择什么n
,我们都无法摆脱3 .第二个例子提示我们考虑因素,我们可以看到,对于任何理性
x = p/q
(假设是最低的),我们可以通过比较b
和q
的主要因子来回答这个问题 . 如果q
有任何不在b
的素数因子分解中的素因子,我们将永远无法找到合适的n
来摆脱这些因素 .因此,对于基数10,任何
p/q
,其中q
具有除2或5之外的素因子将不具有终止表示 .所以现在回到基数10和2,我们看到任何具有终止10-表示的理性将是
p/q
的形式,恰好当q
在其素因数分解中只有2
和5
时;当q
在其素数因子分解中仅有2
时,该相同的数字将具有终止的2表示 .但其中一个案例是另一个案例的子集!每当
显然也是如此
或者,换句话说, whenever p/q has a terminating 2-representation, p/q has a terminating 10-representation . 然而,反过来并不成立 - 只要
q
在其素数因子分解中具有5,它将具有终止的10表示,但不具有终止的2表示 . 这是其他答案提到的0.1
示例 .所以我们有你的问题的答案 - because the prime factors of 2 are a subset of the prime factors of 10, all 2-terminating numbers are 10-terminating numbers, but not vice versa. 它's not about 61 versus 6.1 - it'大约10对2 .
作为一个结尾注释,如果一些怪人使用(比如)基数为17,但是我们的计算机使用了基数为5,那么你的直觉绝不会被误导 - 没有(非零,非整数)数字终止在这两种情况下!
(注意:我会在这里附加'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.90625 / 1.25 = 1.525,是以二进制表示的重复分数:1.11101b / 1.01b = 1.10000110011 ... b我们的机器只有很多位来保存尾数,所以它们只是围绕分数并假设超出某一点的零 . 将61除以10时看到的错误是:
1.100001100110011001100110011001100110011 ... b * 2 ^ 10b
而且,说:
1.100001100110011001100110b * 2 ^ 10b
正是这种尾数的舍入导致了我们与浮点值相关的精度损失 . 即使尾数可以精确表达(例如,当只添加两个数字时),如果尾数需要太多的数字以适合归一化指数后,我们仍然可以得到数字丢失 .
当我们将十进制数字舍入到可管理的大小并且仅给出它的前几位数字时,我们实际上就是这样做的 . 因为我们用十进制表示结果感觉很自然 . 但是如果我们舍入小数然后将其转换为不同的基数,它看起来就像我们得到的小数点一样丑陋,因为浮点舍入 .
数字61.0确实具有精确的浮点运算 - 但对于所有整数而言并非如此 . 如果你写了一个循环,在一个双精度浮点数和一个64位整数中加了一个,最终你就是'd reach a point where the 64-bit integer perfectly represents a number, but the floating point doesn' t-因为没有足够的有效位 .
在小数点右侧达到近似点要容易得多 . 如果你开始用二进制浮点写出所有数字,那就更有意义了 .
考虑它的另一种方式是,当你注意到61.0在基数10中是完全可表示的,并且小数点的移动并没有改变那个时,你正在执行乘以10的幂(10 ^ 1,10 ^ -1) ) . 在浮点数中,乘以2的幂不会影响数字的精度 . 尝试取61.0并将其重复除以3,以说明完全精确的数字如何失去其精确表示 .
你知道整数吗?每个位代表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
正如我们一直在讨论的那样,在浮点运算中,十进制0.1不能用二进制完美表示 .
浮点和整数表示为所表示的数字提供网格或格子 . 算术完成后,结果会从网格上掉下来,并且必须通过舍入返回到网格中 . 示例是二进制网格上的1/10 .
如果我们使用二进制编码的十进制表示作为一个绅士建议,我们能够在网格上保留数字吗?
重复我在对Skeet先生的评论中所说的话:我们 can 表示1 / 3,1 / 9,1 / 27或十进制表示法中的任何理性 . 我们通过添加额外的符号来实现 . 例如,在数字的十进制扩展中重复的数字上的一行 . 我们需要将十进制数表示为二进制数字序列的是 1) 二进制数字序列, 2) 一个小数点, 3) 一些其他符号表示序列的重复部分 .
Hehner's quote notation 是这样做的一种方式 . 他使用引号来表示序列的重复部分 . 文章:http://www.cs.toronto.edu/~hehner/ratno.pdf和维基百科条目:http://en.wikipedia.org/wiki/Quote_notation .
没有什么可以说我们不能在我们的表示系统中添加符号,因此我们可以使用二进制引号表示法精确表示十进制有理数,反之亦然 .
BCD - Binary-coded Decimal - 表示是准确的 . 它们的空间效率不是很高,但在这种情况下,您需要做出准确的权衡 .
问题是你真的不知道这个数字究竟是61.0 . 考虑一下:
c的 Value 是多少?它不完全是61,因为b不是真的.1因为.1没有精确的二进制表示 .
根(数学)原因是当你处理整数时,它们是 countably infinite .
这意味着,即使有一个无限量的它们,我们可以"count out"序列中的所有项目,而不会跳过任何 . 这意味着如果我们想要将项目放在列表中的
610000000000000
位置,我们可以通过公式计算出来 .但是,实数是 uncountably infinite . 你不能说“在
610000000000000
位置给我一个真实的号码”然后回答一下 . 原因是,即使在0
和1
之间,当您考虑浮点值时,也会有无数个值 . 任何两个浮点数都是如此 .更多信息:
http://en.wikipedia.org/wiki/Countable_set
http://en.wikipedia.org/wiki/Uncountable_set
Update: 道歉,我似乎误解了这个问题 . 我的回答是为什么我们不能代表每一个真正的 Value ,我没有意识到浮点被自动归类为理性 .
这就是你不能完全代表基数10的1/3的原因,你需要说0.33333(3) . 在二进制中,它是相同类型的问题,但只是针对不同的数字集发生 .
如果您有足够的空间,则可以精确表示十进制数 - 只是不通过浮动二进制点数 . 如果使用浮点小数点类型(例如.NET中的
System.Decimal
),则可以准确表示大量无法在二进制浮点中精确表示的值 .让我们以另一种方式来看待它 - 在你可能会感到舒适的10号基地,你不能完全表达1/3 . 这是0.3333333 ......(经常性) . 您不能将0.1表示为二进制浮点数的原因完全相同 . 您可以精确地表示3,9和27,但不能代表1 / 3,1 / 9或1/27 .
问题是3是素数,当你想要将数乘以3时,这不是问题:你总是可以乘以整数而不会遇到问题 . 但是当你除以一个素数并且不是你的基数因子的数字时,你可能会遇到麻烦(如果你试图将1除以那个数字就会这样做) .
虽然0.1通常被用作精确十进制数的最简单的例子,它不能用二进制浮点精确表示,但可以说0.2是一个更简单的例子,因为它是1/5 - 而5是引起十进制和二进制之间问题的素数 .
处理有限表示问题的附注:
一些浮动小数点类型具有固定大小,如
System.Decimal
其他像java.math.BigDecimal
是"arbitrarily large" - 但它们是系统内存或数组的理论最大大小 . 然而,这与这个答案的主要部分完全不同 . 即使你有一个真正任意大量的位来使用,你仍然无法在浮动二进制点表示中精确地表示十进制0.1 . 将其与另一种方式进行比较:给定任意数量的十进制数字,您可以精确地表示任何可以表示为浮动二进制点的数字 .并行可以由分数和整数组成 . 一些分数,例如1/7不能以十进制形式表示,没有大量的小数 . 因为浮点是基于二进制的,所以特殊情况会发生变化,但同样存在精度问题 .
我很惊讶没有人说过这个:使用continued fractions . 任何有理数都可以用二进制方式有限地表示 .
一些例子:
1/3(0.3333 ...)
5/9(0.5555 ......)
10/43(0.232558139534883720930 ......)
9093/18478(0.49209871198181621387596060179673 ...)
从这里开始,存在各种已知的在存储器中存储整数序列的方法 .
除了以完美的准确度存储您的数字之外,连续分数还具有一些其他好处,例如最佳有理逼近 . 如果您决定提前终止连续分数中的数字序列,则剩余的数字(重新组合成分数时)将为您提供最佳分数 . 这就是找到pi的近似值的方法:
Pi的持续分数:
将序列终止为1,这给出了分数:
355/113
这是一个很好的理性近似 .
存在无数个有理数,以及用于表示它们的有限数量的位 . 见http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems .
如果你使用浮点数就足够大(因为它可以做指数),那么你也会在小数点前面得到不精确的结果 . 所以我不认为你的问题是完全有效的,因为前提是错误的;不是这样的情况,移位10将始终创建更高的精度,因为在某些时候浮点数将必须使用指数来表示数字的大小,并且也会以这种方式失去一些精度 .
有一个阈值,因为数字的含义已经从整数变为非整数 . 要表示61,你有6 * 10 ^ 1 1 * 10 ^ 0; 10 ^ 1和10 ^ 0都是整数 . 6.1是6 * 10 ^ 0 1 * 10 ^ -1,但10 ^ -1是1/10,绝对不是整数 . 这就是如何你最终进入了Inexactville .
上面得分高的答案钉在上面 .
首先,你在你的问题中混合了基数2和基数10,然后当你在右侧放置一个不能分解到基数的数字时,你会遇到问题 . 像十进制的1/3,因为3不会进入二进制的10或1/5的幂,而不是2的幂 .
另一个评论虽然从来没有使用等于浮点数,期间 . 即使它是一个精确的表示,在一些浮点系统中也有一些数字可以用多种方式准确表示(IEEE对此不好,这是一个可怕的浮点规范,所以期待头痛) . 没有区别这里1/3与计算器0.3333333上的数字不相等,无论小数点右边有多少3 . 它是或者可以足够接近但不相等 . 所以你会期望像2 * 1/3这样的东西不等于2/3,这取决于四舍五入 . 永远不要使用等于浮点数 .
在等式中
因此,我只是想知道我们是否可以使用二进制的对数基本系统,
这可能能够解决问题,所以如果你想用二进制写32.41这样的东西,那就是
要么
不精确的原因是数字基础的性质 . 在基数10中,你不能完全代表1/3 . 它变为0.333 ...但是,在基数3中,1/3精确地用0.1表示,1/2是无限重复的小数(tresimal?) . 可以有限表示的值取决于基数的唯一素因子的数量,因此基数30 [2 * 3 * 5]可以表示比基数2或基数10更多的分数 . 甚至更多基数210 [2 * 3 * 5 * 7] .
这是"floating point error"的另一个问题 . 这种不准确性是因为数十亿的 Value 分布在更大的范围内 . 因此,如果有效位数有23位,则只能表示大约830万个不同的值 . 然后,8位指数提供256个分配这些值的选项 . 此方案允许在0附近出现最精确的小数,因此您几乎可以表示0.1 .
这是一个很好的问题 .
您的所有问题都基于“我们如何代表一个数字?”
所有数字都可以用十进制表示或二进制(2的补码)表示来表示 . All of them !!
BUT some(大多数)需要无限数量的元素("0"或"1"用于二进制位置,或"0","1"到"9"用于十进制表示) .
像十进制表示的1/3(1/3 = 0.3333333 ...... < - 无限数“3”)
像二进制0.1(0.1 = 0.00011001100110011 .... < - 无限数“0011”)
一切都在于这个概念 . 由于您的计算机只能考虑 finite 位数字(十进制或二进制),因此只有一些数字可以在您的计算机中准确表示...
并且如Jon所说,3是素数而不是10的因子,因此1/3不能用基数10中的 finite 个元素来表示 .
即使使用任意精度的算术,基数2中的编号位置系统也不能完全描述6.1,尽管它可以代表61 .
对于6.1,我们必须使用另一种表示(如十进制表示,或IEEE 854,允许基数2或基数10表示浮点值)