是否有一种简单的方法来判断特定数字是否在其浮点表示中进行四舍五入?我问的原因和我问的一个问题有关here并提出了类似的问题here , 在其他人中。
回顾一下,我试图问为什么,例如,表达式 0.5 % 0.1 不会导致大约为零,而是给出(大约)0.1。许多受访者喋喋不休地谈论大多数数字如何无法精确表示等等,但未能真正解释为什么对于某些值,当没有余数时,% 运算符的结果与零相差甚远。我花了很长时间才弄清楚发生了什么,我认为值得分享。另外,它解释了我为什么问我的问题。
如果除数以浮点格式向上舍入,但被除数却不是,则 % 运算符的结果似乎不会为零。除法算法迭代地从被除数中减去除数,直到得到负值。商是迭代次数,余数是被除数剩下的部分。可能不太清楚为什么这会导致错误(当然不是我),所以我将举一个例子。
对于 0.5 % 0.1 =(大约)0.1 的情况,0.5 可以精确表示,但 0.1 不能并且向上舍入。在二进制中,0.5 简单地表示为 0.1,但二进制中的 0.1 是 0.00011001100...重复最后 4 位数字。由于浮点格式的工作方式,在最初的 1 之后,它会被截断为 23 位(单精度)。(有关完整解释,请参阅被广泛引用的 What Every Computer Scientist Should Know About Floating-Point Arithmetic。)然后将其向上舍入,因为这更接近 0.1(十进制)值。因此,除法算法使用的值是:
0.1 0000 0000 0000 0000 0000 000 --> 0.5(十进制),并且
0.0001 1001 1001 1001 1001 1001 101 --> 0.1(十进制)
除法算法迭代次数为;
(1) 1.00000000000000000000000 - 0.000110011001100110011001101 =
(2) 0.011001100110011001100110011 - 0.000110011001100110011001101 =
(3) 0.01001100110011001100110011 - 0.000110011001100110011001101 =
(4) 0.001100110011001100110011001 - 0.000110011001100110011001101 =
(x)0.0001100110011001100110011 - 0.000110011001100110011001101 =
-0.000000000000000000000000001
如图所示,在第 4 次迭代之后,进一步相减将得到负数,因此算法停止,剩下的被除数值(以粗体显示)是余数,即小数 0.1 的近似值。
此外,表达式 0.6 % 0.1 可以按预期工作,因为 0.6 会向上舍入。表达式 0.7 % 0.1 无法按预期工作,尽管无法准确表示 0.7,但它不会向上舍入。 我还没有对此进行详尽的测试,但我认为这就是正在发生的事情。这让我(终于!)想到了我的实际问题:
有谁知道判断特定数字是否会被四舍五入的简单方法?
最佳答案
让我们考虑 float a > b > 0
时的情况。每个 float 都是其 ulp 的倍数,我们可以这样写:
a = na*ulp(a). ulp(a)=2^ea
。 na 是 a 的整数有效数。 ea 是它的偏置指数。
b = nb*ulp(b). ulp(b)=2^eb
。 nb 是 b 的整数有效数。 eb 是其有偏指数。
对于标准化 float ,2^p > na >= 2^(p-1)
其中 p 是浮点精度(对于 IEEE 754 double ,p=53 位)。
因此我们可以执行(可能很大的)整数除法:na*2^(ea-eb)=nb*q+nr
从中我们推导出na*2^(ea-eb)*2^eb = nb*2^eb*q+nr*2^eb
,即a=b*q+nr*2^eb
.
换句话说,在标准化之前,nr 是浮点余数的整数有效数,eb 是其偏置指数。
由此可见,余数运算是精确的,因为显然nr <= nb,所以余数可以表示为float。因此严格来说,余数永远不会四舍五入。
当商四舍五入到最接近的整数而不是截断时,这是 IEEE 余数运算,
a=b*q+r
那么余数可以为负 r<0
在这种情况下,您感兴趣的是:
a=b*(q-1) + (b+r)
我认为这种情况下 r 为负,迫使 b+r
结果就是你所说的四舍五入。不幸的是,没有简单的方法可以在不执行该操作的情况下判断余数是否为负,除非 nb 是 2 的幂(在逐渐下溢的情况下为 2^(p-1) 或更小)。
但您似乎对具体案例感兴趣a=i/10^j
和b=1/10^j
但只有 float 近似float(i/10^j)
和float(1/10^j)
。假设 10^j 和 i 可以精确表示( double 下 j<23 且 i<=2^53),那么我们可以使用融合乘法加法来获取表示误差:
ea=fma(10^j,float(i/10^j),-i). 10^j*float(a)=10^j*a+ea.
eb=fma(10^j,float(1/10^j),-1). 10^j*float(b)=10^j*b+eb.
您有i*b=a
现在您想要比较它与浮点近似的情况,这样您就可以得到余数:
r = (a+ea/10^j)-i*(b+eb/10^j) = 1/10^j * ea - i/10^j * eb.
浮点近似可能有效,但并非总是有效:
float(float(float(b)*ea) - float(float(a)*eb))
但是,您最好再次使用 fma:
r = fma(-i,eb,ea)/10^j
余数的符号将为您提供浮点近似值的边...
这里我们稍微简化了问题,因为我们没有考虑商可能偏差超过 1 的情况。那应该没问题,因为 i < 2^53 但我们没有证明它。
这只是一种风格练习,因为我们正在用更复杂的表达式替换简单的表达式。
关于java - 如何知道以浮点格式表示时分数是否会向上舍入(re : java remainder [%] results when using fp's),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40061900/