这个sqrt近似内联汇编函数如何工作?

时间:2017-01-21 22:54:06

标签: assembly optimization x86 square-root sar

通过 3D游戏编程大师的技巧阅读,我遇到了用内联汇编编写的这种排序函数:

inline float FastSqrt(float Value)
{
    float Result;

    _asm
    {
        mov eax, Value
        sub eax, 0x3F800000
        sar eax, 1
        add eax, 0x3F800000
        mov Result, eax
    }

    return(Result);
}

它是实际平方根的近似值,但准确度足以满足我的需要。

这实际上如何运作?这个神奇的0x3F800000价值是多少?我们如何通过减去,旋转和添加来实现平方根?

以下是C / C ++代码的外观:

inline float FastSqrt_C(float Value)
{
    float Result;

    long Magic = *((long *)&Value);
    Magic -= 0x3F800000;
    Magic >>= 1;
    Magic += 0x3F800000;
    Result = *((float *)&Magic);

    return(Result);
}

4 个答案:

答案 0 :(得分:10)

许多人指出0x3f8000001.0的代表。虽然这是事实,但它与计算的工作方式无关。要理解它,您需要知道如何存储非负浮点数。 f = (1+m)*2^x,其中0 <= m < 1m为尾数,x为指数。另请注意,x存储有偏差,因此二进制文件中的实际内容为x+127。 32位值由符号位(在我们的例子中为零)后跟8位指数存储x+127,最后由23位尾数m组成。 (见wikipedia article)。

应用一些基本的数学,

sqrt(f) = sqrt((1+m)*2^x)
        = sqrt(1+m)*sqrt(2^x)
        = sqrt(1+m)*2^(x/2)

因此,作为一个粗略的近似,我们需要将指数减半,但由于偏差,我们不能x/2我们需要(x-127)/2 + 127。转移到相应位位置的127是魔术0x3f800000

使用右移一位来实现除以2。由于它在整个浮子上运行,因此它对尾数也有副作用。

首先,假设原始指数是偶数。然后,移出的最低有效位为零。因此,尾数也减半,所以最终结果是:sqrt(f) = (1+m/2)*2^(x/2)。我们得到的指数是正确的,但尾数是(1+m/2)而不是sqrt(1+m)。最大相对误差为(1.5 - sqrt(2))/sqrt(2) ~ 6%,如果m几乎1意味着f接近,但小于2的奇数幂,则会出现f=7.99。以2.998为例。该公式为我们提供了2.827而不是6%,其确实存在1错误。

现在,如果指数是奇数,那么最低有效位将是sqrt(f) = (1.5+m/2)*2^((x-1)/2),当转移到尾数时,这将导致增加一半。因此,我们得到m=0。实际上,(1.5/sqrt(2)-sqrt(1))/sqrt(1)的最大错误是6%,而mRecyclerView.getRecycledViewPool().clear();也是public static String xmlChange(String xmlStr) { if(xmlStr!=null) { for (int i = 0; i < xmlStr.length();) { int index=xmlStr.indexOf("/>",i); if(index>0) { String tempStr = xmlStr.substring(i, index + 2); int leftIndex=tempStr.lastIndexOf("<"); String old= tempStr.substring(leftIndex); String _new=old.replace("/","")+old.replace("/","").replace("<","</"); i=index+2; xmlStr=xmlStr.replace(old,_new); } else { break; } } xmlStr=xmlStr.replaceAll("\r|\n", ""); } return xmlStr; } 。对于从上方接近奇数幂2的数字,会发生这种情况。

如果输入值恰好接近2的奇数幂,则两种情况相结合意味着最差的误差约为6%。对于偶数幂,结果是准确的。

答案 1 :(得分:0)

浮点数为0x3F800000为1.这是因为浮点数的存储方式。您可以在https://gregstoll.dyndns.org/~gregstoll/floattohex/看到可视化表示。

这是一个很好的近似值,我相信sqrt。这个来源是游戏Quake for inverse sqrt(https://en.wikipedia.org/wiki/Fast_inverse_square_root#Aliasing_from_floating_point_to_integer_and_back)。

答案 2 :(得分:0)

以下是这个实施机制的一个例子:

FastSqrt(4.0)== 2.0

4.0 to hex -> 0x40800000
0x40800000 - 0x3f800000 = 0x1000000
0x1000000 to binary -> 00000001 00000000 00000000 00000000
shift toward the lsb (sar) -> 00000000 10000000 00000000 00000000
00000000 10000000 00000000 00000000 back to hex -> 0x00800000
0x00800000 + 0x3f800000 = 0x40000000
0x40000000 to dec -> 2.0

答案 3 :(得分:0)

浮点数f =(1 + m)* [2 ^(e + 127)],其中m是尾数部分,e是指数部分。

因此:sqrt(f)=(f)^(1/2)=((1 + m)* [2 ^(e + 127)])^(1/2)

- &GT; ((1 + m)* [2 ^(e + 127)])^(1/2)=(1 + m)^(1/2)* 2 ^((e + 127)/ 2)

在指数部分,2 ^((e + 127)/ 2):

2 ^((e + 127)/ 2)= 2 ^((e-127/2)+ 127)

因此,在浮动表示中, 它是(e - 0x3F800000)/ 2 + 0x3F800000

在尾数部分,(1 + m)^(1/2):

来自二项式系列公式,(1 + x)^ r = 1 + r x +(r (r - 1)/ 2)*(x ^ 2)+ .... < / p>

因此,(1 + m)^(1/2)等于(1 + m / 2 - (m ^ 2)/ 8 + ...) 它近似等于1 + m / 2(典型的一阶近似值) 因此,尾数部分应该用2分割。

但是,尾数和指数组合为A数,右移除了指数和尾数BOTH。

要评估错误,您可以考虑二项式系列的第二项, - (m ^ 2)/ 8.

因为m总是小于1,我将m替换为0.9999(0.5 + 0.25 + 0.125 + ...)

(m ^ 2)/ 8 = 0.12497500125,这是最糟糕的情况。