在this question中,我编写了一个脚本来查找将值存储为总和的一小部分然后再返回时可能产生的最大错误。
我发现最大错误与Number.EPSILON:
有关 maximumError / Number.EPSILON
是一个很好的数字,8192。
Math.log2(8192)
是12.999999999999998,所以...... 13。
此舍入错误与Number.EPSILON之间的关系是什么?
为什么它是2的好因素? 13"是什么意思"?
更新:脚本刚刚发现最大误差为3.637978807091713e-12除以Number.EPSILON为16384. Math.log2(16384)〜= 14.
答案 0 :(得分:5)
任何大于1(或小于-1)的浮点值除以Number.EPSILON
的值必须为整数。 (对于仅在1
到-1
范围内的值,情况不一定如此。)
回想一下,浮点是位元组(m,e)
的2元组(尾数和指数),其中它们代表的数字的值是m * 2^e
。尾数将值设置为位串,并且指数将位移动到某个幂在当前的ECMAScript 6草案中,Number.EPSILON
的定义是:
1与大于1的最小值之间的差值,可以表示为数值
我们通过在尾数位串中的最大和最小位数采用1000...0001
(1
的尾数)和将尾数向下移动到二进制值{{1}的负指数来推导epsilon }。减去1.000...0001
,你就有了epsilon。请注意,这是不是可能的最小浮点值,但 是浮点值大于1(或小于 - )的最小精度级别1)。*
至于为什么总是产生一个整数,这很容易解释:对于大于1的数字,epsilon是最小可能的精度值。epsilon不可能将值大于1不均匀,因为这表明数字有一些小于epsilon的小数部分,这在定义上是不可能的(因为epsillon是1
数字的最小精度水平)。您可以随意在数字键盘上滚动并将该数字除以>1
- 您将看到结果为整数。
至于为什么结果始终为2的幂,这似乎是因为到目前为止你的所有Number.EPSILON
结果的尾数也是2的幂(可能它们都有{{1的尾数因此,它只是在2的幂之间划分(因此结果也必须是2的幂)。
请注意,产生此案例的所有maximumError
值都是最低位1
的值,因此它们的格式为a
(和{{1} }本身):1
,(2^n) + 1
,1
,5
等等。看来这里有一些数学属性,其中乘法不会恢复完整值,原始9
值变为33
。它的数量很少,65
。此数字的格式表示为100...001
,因此尾数始终为100...000.1111111111...
。
您可能会发现感兴趣的0.000000...000001
的二进制表示:对于非常大或非常小的值,它会清楚地显示由1 * 2^-n
的指数位移引起的二进制尾数偏移零。例如,请参阅1
中的最大值,位移尾数:
num.toString(2)
事实上,错误增长的唯一原因是由于尾数的固定大小,* 2 ^ e
中的尾随Number.MAX_VALUE.toString(2)
的数量正在减少。考虑:
1111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
请注意,这些字符串的长度完全相同,但在第二种情况下,小数点的左边是一个数字。这是因为尾数只大到足以容纳一定数量的二进制数字。从逻辑上讲,无限数量的1
s应该变成与浮点数一样多的100...000.1111111111...
s。考虑十进制的类似情况,其中0.999... is actually equal to 1;因此,二进制> var a = 9, b = 510; ((a/(a+b))*(a+b)).toString(2);
"1000.1111111111111111111111111111111111111111111111111"
> var a = 17, b = 4194; ((a/(a+b))*(a+b)).toString(2);
"10000.111111111111111111111111111111111111111111111111"
等于1
,但我们没有空间来表示无限数字。
由于随着小数点左边的增加,尾随数量减少,因此误差范围也会增加,因为1
越来越接近小数点。
*:考虑一个简单的尾数,如0.11111...
。如果您想使用该尾数使值尽可能小,则可以使用极大的负指数来生成类似1
的值。但是,如果您需要保持值大于0.000...0001
(就像考虑10001
的定义时那样),则只能将其降低到0.00000000000010001
。当前导1
必须保留在小数点左侧时,最终Number.EPSILON
可以走多远的距离受尾数的限制。如果您只是尝试创建最小值并且可以小于1,则可以将尾数向右移动得更远。