[Swift]快速反向平方根 | Fast inverse square root
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ?
曲面法线广泛用于光照和阴影计算,需要计算矢量的范数。这里显示了垂直于表面的矢量场。
使用法线C从入射角求出反射角的二维例子;? 在这种情况下,在从曲面镜反射的光上。快速反平方根用于将该计算推广到三维空间。 ? ? ? ?浮点数的倒数平方根用于计算标准化向量。程序可以使用标准化向量来确定入射角和反射角。3D图形程序必须每秒执行数百万次这些计算以模拟光照。当代码在20世纪90年代早期开发时,大多数浮点处理能力都落后于整数处理的速度。在专门用于处理变换和照明的硬件出现之前,这对3D图形程序来说很麻烦。 ? ? ? ?通过计算其欧几里德范数:矢量分量的平方和的平方根来确定矢量的长度。当向量的每个分量除以该长度时,新向量将是指向相同方向的单位向量。在3D图形程序,所有的载体在三维空间中,所以v将是一个向量(v?1,v?2,v?3)。?
是向量的欧几里德范数。?
是使用||的归一化(单位)向量?||v||?2代表v1^2?+?v2^2?+?v3^2。?
它将单位矢量与距离分量的平方根相关联。反平方根可用于计算v,因为该等式等价于?
其中分数项是的平方根?||v||?2。当时,与乘法相比,浮点除法通常很昂贵;?快速逆平方根算法绕过了除法步骤,赋予其性能优势。 ? ? ? ?为了计算平方根倒数的一般方法是计算一个近似为1?√x,然后通过另一种方法修改该近似直到它来实际结果的可接受的误差范围之内。20世纪90年代早期的常用软件方法从查找表中得出近似值。快速平方根的关键是通过利用浮点数的结构直接计算近似,证明比表查找更快。该算法比使用另一种方法计算平方根并计算倒数通过浮点除法快大约四倍。该算法的设计考虑了IEEE 754-1985?32位浮点规范,但研究表明它可以在其他浮点规范中实现。 ? ? ? ?快速反平方根的速度优势来自于将包含浮点数的长字视为整数,然后从特定常数0x5F3759DF中减去它。查看代码的人不会立即清楚常量的目的,因此,与代码中的其他此类常量一样,它通常被称为幻数。该整数减法和位移产生长字,当作为浮点数处理时,该长字是输入数的反平方根的粗略近似。执行牛顿方法的一次迭代以获得一定的准确性,并且代码完成。该算法使用Newton方法的唯一第一近似值生成相当准确的结果;?然而,它比在1999年发布的rsqrtss x86处理器上使用SSE指令要慢得多且准确度低得多。 Talk is cheap.Show me your code:(1)、平方根函数:方法1-牛顿迭代法 1 func sqrt1(_ x: Float) -> Float { 2 //判断x是否为0 3 if x == 0 {return 0} 4 let high:Float = Float(x) 5 let mid:Float = high/2 6 var y:Float = (mid>1) ? mid : 1 7 while(true) 8 { 9 let dy:Float = (y * y + high) / Float(y)/2 10 //处理float类型的取绝对值fabsf(float i) 11 if fabsf(y - dy) <= 0.00000001 12 { 13 return dy 14 } 15 else 16 { 17 y = dy 18 } 19 } 20 } (2)、平方根函数:方法2? 1 func sqrt2(_ x:Float) -> Float 2 { 3 var y:Float 4 var delta:Float 5 var maxError:Float 6 if x <= 0 {return 0} 7 // initial guess 8 y = x / 2 9 // refine 10 maxError = x * 0.00000001 11 repeat { 12 delta = ( y * y ) - x 13 y -= delta / ( 2 * y ) 14 } while ( delta > maxError || delta < -maxError ) 15 return y 16 } (3)、快速反向平方根:方法1-使用memcpy() 注意开头的导入:import Foundation。 invSqrt(x)比1.0/sqrt(x)快速增加了约40%, 最大相对误差低于0.176%? 1 import Foundation 2 func invSqrt1(_ x: Float) -> Float { 3 let halfx = 0.5 * x 4 var y = x 5 var i : Int32 = 0 6 memcpy(&i,&y,4) 7 i = 0x5f3759df - (i >> 1) 8 memcpy(&y,&i,4) 9 y = y * (1.5 - (halfx * y * y)) 10 return y 11 }
从Swift3开始,memcpy()可以用bitPattern:方法替换Float相应的构造函数UInt32: 1 func invSqrt2(_ x: Float) -> Float { 2 let halfx = 0.5 * x 3 var i = x.bitPattern 4 i = 0x5f3759df - (i >> 1) 5 var y = Float(bitPattern: i) 6 y = y * (1.5 - (halfx * y * y)) 7 return y 8 }
1 let x:Float = 10.0 2 let ans1:Float = sqrt1(x) 3 let ans2:Float = sqrt2(x) 4 let ans3:Float = invSqrt1(x) 5 let ans4:Float = invSqrt2(x) 6 print(1.0/ans1) 7 //Print 0.31622776 8 print(1.0/ans2) 9 //Print 0.31622776 10 print(ans3) 11 //Print 0.31568578 12 print(ans4) 13 //Print 0.31568578 常量0x5f3759df来自哪里?为什么代码有效? 快速逆平方根:有时被称为快速InvSqrt()或由十六进制常数0x5F3759DF的估计1?√x算法的倒数(或乘法逆)的的平方根的32位的浮点数X在IEEE 754浮点格式。此操作用于数字信号处理以标准化矢量,即将其缩放为长度1.例如,计算机图形程序使用反平方根来计算入射角和反射对照明和阴影。 ? ? ? ?该算法接受32位浮点数作为输入,并存储一半的值供以后使用。然后处理代表浮点数作为一个32位的整数的比特,一个逻辑移位一个位权执行,并且结果从减去幻数 0X 5F3759DF,这是一个近似的浮点表示√2127。这导致输入的平方根的第一近似。再次将这些位作为浮点数处理,它运行牛顿方法的一次迭代,产生更精确的近似。 一个有效例子:作为一个例子,数x = 0.15625可用于计算1?√x ≈ 2.52982.。该算法的第一步如下所示:? 1 0011_1110_0010_0000_0000_0000_0000_0000 Bit pattern of both x and i 2 0001_1111_0001_0000_0000_0000_0000_0000 Shift right one position: (i >> 1) 3 0101_1111_0011_0111_0101_1001_1101_1111 The magic number 0x5F3759DF 4 0100_0000_0010_0111_0101_1001_1101_1111 The result of 0x5F3759DF - (i >> 1)
1 0_02222200_01000000000000000000000 1.25 × 2?3 2 0_00222220_00100000000000000000000 1.125 × 2?65 3 0_10222220_01101110101100111022222 1.432430... × 263 4 0_10000000_01001110101100111022222 1.307430... × 21
算法描述:快速反向平方根算法计算1?√x,通过执行以下步骤。 (1)、将参数x别名为整数,作为计算?log2(x)近似值的方法 (2)、使用这种近似计算的近似对数log2(1?√x)= ?1/2 log2(x) (3)、别名返回浮点数,作为计算base-2指数近似值的方法 (4)、使用牛顿方法的单次迭代来细化近似。 准确度:下图显示启发式快速反平方根与libstdc提供的平方根直接反转之间差异的图表。注意两个轴上的对数刻度。如上所述,近似值令人惊讶地准确。右边的图表描绘了函数的误差(即,通过运行牛顿方法的一次迭代后的近似值的误差),对于从0.01开始的输入,其中标准库给出10.0作为结果,而InvSqrt()给出9.982522,使差值为0.017479,即真值的0.175%。绝对误差仅从此开始下降,而相对误差保持在所有数量级的相同范围内。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |