有没有办法用i387 fsqrt指令进行正确的舍入?...
... 除了改变x87控制字中的精确模式 - 我知道这是可能的,但它不是一个合理的解决方案,因为它有令人讨厌的重入类型问题,其中精确模式将是如果sqrt操作被中断,则会出错。
我正在处理的问题如下:x87 fsqrt
操作码在fpu寄存器的精度中执行正确舍入(按照IEEE 754)平方根操作,我假设它是扩展的(80位)精度。但是,我想用它来实现高效的单精度和双精度平方根函数,并且结果正确舍入(按照当前的舍入模式)。由于结果具有过高的精度,因此将结果转换为单精度或双精度的第二步再次轮回,可能会留下不正确舍入的结果。
通过一些操作,可以通过偏见来解决这个问题。例如,我可以通过以2的幂的形式添加偏置来避免过度结果的过度精度,该偏置将双精度值的52个有效位强制为63位扩展精度尾数的最后52位。但我没有看到任何明显的方法用平方根做这样的技巧。
任何聪明的想法?
(也标记为C,因为预期的应用程序是C sqrt
和sqrtf
函数的实现。)
答案 0 :(得分:14)
首先,让我们明白一点:你应该使用SSE而不是x87。 SSE sqrtss
和sqrtsd
说明完全符合您的要求,在所有现代x86系统上均受支持,并且速度也快得多。
现在,如果你坚持使用x87,我会从好消息开始:你不需要为浮动做任何事情。您需要2p + 2
位来以p位浮点格式计算正确舍入的平方根。因为80 > 2*24 + 2
,单精度的附加舍入将始终正确舍入,并且您具有正确的舍入平方根。
现在是坏消息:80 < 2*53 + 2
,所以没有双重精确的运气。我可以建议一些解决方法;这是一个很好的简单的一个。
y = round_to_double(x87_square_root(x));
a
和b
,以便y*y = a + b
精确计算。r = x - a - b
。if (r == 0) return y
if (r > 0)
,允许y1 = y + 1 ulp
,并计算a1
,b1
s.t. y1*y1 = a1 + b1
。将r1 = x - a1 - b1
与r
进行比较,并返回y
或y1
,具体取决于具有较小残差的残差(或具有零低位的零,如果残差为数量相等)。if (r < 0)
,对y1 = y - 1 ulp
执行相同的操作。此过程仅处理默认的舍入模式;但是,在定向舍入模式中,简单地舍入到目标格式是正确的。
答案 1 :(得分:3)
好的,我认为我有更好的解决方案:
y=sqrt(x)
)计算fsqrt
。0x400
,只需转换为双精度并返回。0x100-(fpu_status_word&0x200)
添加到扩展精度表示的低位字。步骤3基于以下事实:当且仅当fsqrt
的结果被舍入时,状态字的C1位(0x200)为1。这是有效的,因为由于步骤2中的测试,x
不是一个完美的正方形;如果它是一个完美的正方形,y
将没有超出双精度的位。
使用条件浮点运算执行步骤3可能会更快,而不是处理位表示和重新加载。
这是代码(似乎在所有情况下都有效):
sqrt:
fldl 4(%esp)
fsqrt
fstsw %ax
sub $12,%esp
fld %st(0)
fstpt (%esp)
mov (%esp),%ecx
and $0x7ff,%ecx
cmp $0x400,%ecx
jnz 1f
and $0x200,%eax
sub $0x100,%eax
sub %eax,(%esp)
fstp %st(0)
fldt (%esp)
1: add $12,%esp
fstpl 4(%esp)
fldl 4(%esp)
ret
答案 2 :(得分:0)
它可能不是您想要的,因为它没有利用387 fsqrt
指令,但在glibc中使用32位整数实现了令人惊讶的高效sqrtf(float)
算术。它甚至可以正确处理NaNs,Infs,subnormals - 可以用真正的x87指令/ FP控制字标志来消除这些检查。见:glibc-2.14/sysdeps/ieee754/flt-32/e_sqrtf.c
dbl-64/e_sqrt.c
代码不太友好。很难说出一目了然的假设。奇怪的是,库的i386 sqrt[f|l]
实现只调用fsqrt
,但加载值的方式不同。 SP为flds
,DP为fldl
。