sqrt函数的实现主要有三种方式:

  1. 二分法
  2. 牛顿法
  3. 卡马克方法

卡马克方法

这里主要介绍高效的卡马克方法。卡马克方法起源于《雷神之锤III竞技场》中使用的平方根倒数速算法,下列代码是平方根倒数速算法在《雷神之锤III竞技场》源代码中的应用实例。示例剥离了C语言预处理器的指令,但附上了原有的注释:

float Q_rsqrt( float number )
{long i;float x2, y;const float threehalfs = 1.5F;x2 = number * 0.5F;y  = number;i  = * ( long * ) &y;// evil floating point bit level hackingi  = 0x5f3759df - ( i >> 1 );               // what the fuck?y  = * ( float * ) &i;y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removedreturn y;
}

为计算平方根倒数的值,软件首先要先确定一个近似值,而后则使用某些数值方法不断计算修改近似值,直至达到可接受的精度。在1990年代初(也即该算法发明的大概时间),软件开发时通用的平方根算法多是从查找表中获取近似值,而这段代码取近似值耗时比之更短,达到精确度要求的速度也比通常使用的浮点除法计算法快四倍,虽然此算法会损失一些精度,但性能上的巨大优势已足以补偿损失的精度。由代码中对原数据的变量类型声明为float可看出,这一算法是针对IEEE 754标准格式的32位浮点数设计的,不过据Chris Lomont和后来的Charles McEniry的研究看,这一算法亦可套用于其他类型的浮点数上。
平方根倒数速算法在速度上的优势源自将浮点数转化为长整型以作整数看待,并用特定常数0x5f3759df与之相减。然而对于代码阅读者来说,他们却难以立即领悟出使用这一常数的目的,因此和其它在代码中出现的难以理解的常数一样,这一常数亦被称为“魔术数字”。如此将浮点数当作整数先位移后减法,所得的浮点数结果即是对输入数字的平方根倒数的粗略估计值,而后再进行一次牛顿迭代法,以使之更精确后,代码即执行完毕。由于算法所生成的用于输入牛顿法的首次近似值已经相当精确,此算法所得近似值的精度已可接受,而若使用与《雷神之锤III竞技场》同为1999年发布的Pentium III中的SSE指令rsqrtss计算,则计算平方根倒数的收敛速度更慢,精度也更低。

将浮点数转化为整数

要理解这段代码,首先需了解浮点数的存储格式。一个浮点数以32个二进制位表示一个有理数,而这32位由其意义分为三段:首先首位为符号位,如若是0则为正数,反之为负数;接下来的8位表示经过偏移处理(这是为了使之能表示-127-128)后的指数;最后23位表示的则是有效数字中除最高位以外的其余数字。将上述结构表示成公式即为\(x=(-1)^{S_i}·(1+m)·2^{(E-B)}\),其中\(m\)表示有效数字的尾数(此处\(0≤m<1\),偏移量\(B=127\),而指数的值\(E-B\)决定了有效数字代表的是小数还是整数。以上图为例,将描述带入有\(m=1×2^{-2}=0.250\),且\(E-B=124-127=-3\),则可得其表示的浮点数为\(x=(1+0.250)·2^{-3}=0.15625\)。

如上所述,一个有符号正整数在二进制补码系统中的表示中首位为0,而后面的各位则用于表示其数值。将浮点数取别名存储为整数时,该整数的数值即为\(I=E×2^{23}+M\),其中E表示指数,M表示有效数字;若以上图为例,图中样例若作为浮点数看待有\(E=124\),\(M=1·2^{21}\),则易知其转化而得的整数型号数值为\(I=124×2^{23}+2^{21}\)。由于平方根倒数函数仅能处理正数,因此浮点数的符号位(即如上的Si)必为0,而这就保证了转换所得的有符号整数也必为正数。以上转换就为后面的计算带来了可行性,之后的第一步操作(逻辑右移一位)即是使该数的长整形式被2所除

"魔术数字"

对猜测平方根倒数速算法的最初构想来说,计算首次近似值所使用的常数0x5f3759df也是重要的线索。为确定程序员最初选此常数以近似求取平方根倒数的方法,Charles McEniry首先检验了在代码中选择任意常数R所求取出的首次近似值的精度。回想上一节关于整数和浮点数表示的比较:对于同样的32位二进制数字,若为浮点数表示时实际数值为\(x=(1+m_x)2^{e_x}\),而若为整数表示时实际数值则为\(I_x=E_xL+M_x\),其中\(L=2^{n-1-b}\)。以下等式引入了一些由指数和有效数字导出的新元素:
\[ m_x=\frac{M_x}{L} \]

\[ e_x=E_x-B,其中B=2^{b-1}-1 \]

再继续看McEniry 2007里的进一步说明:
\[ y=\frac{1}{\sqrt{x}} \]
对等式的两边取二进制对数,有
\[ log_2(y)=-\frac{1}{2}log_2(x) \]

\[ log_2(1+m_y)+e_y=-\frac{1}{2}log_2(1+m_x)-\frac{1}{2}e_x \]

以如上方法,就能将浮点数x和y的相关指数消去,从而[将乘方运算化为加法运算。而由于\(log_2(x)\)与\(log_2(x^{-1/2})\)线性相关,因此在\(x\)与\(y_0\)(即输入值与首次近似值)间就可以线性组合的方式创建方程。在此McEniry再度引入新数\(\sigma\)描述\(log_2(1+x)\)与近似值R间的误差:由于\(0≤x<1\),有\(log_2(1+x)≈x\),则在此可定义\(\sigma\)与\(x\)的关系为\(log_2(1+x)=x+\sigma\),这一定义就能提供二进制对数的首次精度值(此处\(0≤\sigma≤1/3\);当R为0x5f3759df时,有\(\sigma=0.0450461875791687011756\))。由此将\(log_2(1+x)=x+\sigma\)代入上式,有:
\[ m_y+\sigma+e_y=-\frac{1}{2}m_x-\frac{1}{2}\sigma-\frac{1}{2}e_x \]
参照首段等式代入\(M_x\),\(E_x\),\(B\)与\(L​\),有:
\[ M_y+(E_y-B)L=-\frac{3}{2}\sigma L-\frac{1}{2}M_x-\frac{1}{2}(E_x-B)L \]
移项整理得:
\[ E_yL+M_y=\frac{3}{2}(B-\sigma)L-\frac{1}{2}(E_xL+M_x) \]
如上所述,对于以浮点规格存储的正浮点数x,若将其作为长整型表示则示值为\(I_x=E_xL+M_x\),由此即可根据x的整数表示导出\(y\)(在此\(y=\frac{1}{\sqrt{x}}\),亦即x的平方根倒数的首次近似值)的整数表示值,也即:
\[ I_y=E_yL+M_y=R-\frac{1}{2}(E_xL+M_x)=R-\frac{1}{2}I_x,其中R=\frac{3}{2}(B-\sigma)L. \]
最后导出的等式\(I_y=R-\frac{1}{2}I_x\)即与上节代码中i = 0x5f3759df - (i>>1);一行相契合,由此可见,在平方根倒数速算法中,对浮点数进行一次移位操作与整数减法,就可以可靠地输出一个浮点数的对应近似值。到此为止,McEniry只证明了,在常数R的辅助下,可近似求取浮点数的平方根倒数,但仍未能确定代码中的R值的选取方法。

牛顿法

在进行了如上的整数操作之后,示例程序再度将被转为长整型的浮点数回转为浮点数(对应x = *(float*)&i;),并对其进行一次浮点运算操作(对应x = x*(1.5f - xhalf*x*x);),这里的浮点运算操作就是对其进行一次牛顿法迭代,若以此例说明:

\(y=\frac{1}{\sqrt{x}}\)所求的是\(y\)的平方根倒数,以之构造以\(y\)为自变量的函数,有\(f(y)=\frac{1}{y^2}-x=0\),将其代入牛顿法的通用公式\(y_{n+1}=y_n-\frac{f(y_n)}{f'(y_n)}\)(其中\(y_n\)为首次近似值),有\(y_{n+1}=\frac{y_n(3-xy_n^2)}{2}\),其中\(f(y)=\frac{1}{y^2}-x\),\(f'(y)=\frac{-2}{y^3}\)。整理有\(y_{n+1}=\frac{y_n(3-xy_n^2)}{2}=y_n(1.5-\frac{xy_n^2}{2})\),对应的代码为x=x*(1.5f-xhalf*x*x);

在以上一节的整数操作产生首次近似值后,程序会将首次近似值作为参数送入函数最后两句进行精化处理,代码中的两次迭代正是为了进一步提高结果的精度,但由于雷神之锤III引擎的图形计算中并不需要太高的精度,所以代码中只进行了一次迭代,二次迭代的代码则被注释。

转载于:https://www.cnblogs.com/muyangshaonian/p/9649599.html

sqrt函数实现之卡马克方法相关推荐

  1. sqrt函数的几种实现方法

    Implement int sqrt(int x). Compute and return the square root of x. 1:二分查找 思路:要实现一个sqrt函数,可以使用二分法,首先 ...

  2. sqrt函数模拟实现的两种方法

    起因:在leetcode刷题时,有一道题目考察了有关sqrt的原理的题目,当时就去查了网上的文章,结果发现,一开始的时候看的很懵,最后也是搞定了两种方法,今天我就以最简单的方式写下这两种方式的思路讲解 ...

  3. MATLAB中abs和sqrt函数的使用方法

    MATLAB中abs和sqrt函数的使用方法 1.abs函数 ##作用:数值的绝对值和复数的幅值 ##基本用法:abs(x)函数是对数组元素进行绝对值处理的函数. 函数的定义域包括复数. 对于复数x= ...

  4. C++知识精讲9——sqrt函数函数基本使用方法以及实战讲解

    本文我们来讲C++知识精讲的第9篇,sqrt函数使用方法及实战讲解,此专栏会讲许多,各种各样的类型,如果喜欢此专栏请订阅持续关注,感谢大家的支持.接下来,进入今天的知识精讲. sqrt函数用来干什么的 ...

  5. c语言sqrt多个重载函数,“sqrt”: 对重载函数的调用不明确——解决方法

    #include #include using namespace std; int main(){ int i,j,k,flag; i = 2; while(i <= 100){ flag = ...

  6. python调用math函数_Python中sqrt函数使用方法

    MySQL的SQRT函数是用来计算出任何数量的平方根.可以使用SELECT语句找出方检定根的任意数如下: mysql select SQRT(16);+----------+| SQRT(16) |+ ...

  7. 求平方根sqrt()函数的底层算法效率问题

    我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢? 虽然 ...

  8. 转:一个Sqrt函数引发的血案

    转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/1844725.html 源码下载地址:http://diducoder.com/sotr ...

  9. 一个Sqrt函数引发的血案

    我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢? 虽然 ...

最新文章

  1. (005) java后台开发之Mac终端命令运行java
  2. SQL触发器实例讲解1
  3. 你能体会那种写 Python 时不用 import 的幸福吗?
  4. HarmonyOS之AI能力·文字图像超分
  5. java.lang.NoClassDefFoundError: org/apache/commons/codec/DecoderException 的解决办法
  6. android组建之间通信_Android各组件/控件间通信利器之EventBus
  7. Android应用开发(15)---字符串资源
  8. 抽象背景素材|纯粹为了视觉兴趣而存在
  9. 读书笔记五:TCP/IP详解之RARP逆地址解析协议
  10. selenium无界面chromedriver
  11. android8.1dolby,努比亚X刷杜比音效教程-按推理支持绝多数安卓8和安卓9系统
  12. 新浪微博发布文章html,微博网页版如何发布头条文章
  13. 【android 高德地图出现定位失败key鉴权失败,获取 SHA1,对比是否正确】
  14. yamada算法_脉宽调制中的颤振算法
  15. 怎样判断一个exe可执行程序是32位的还是64位的
  16. CPU/显卡GPU/CUDA/内存/缓存/SDK/API/DLL【转载整理】
  17. 金华驾驶员考试中心 科目二、科目三和科目四
  18. 深大计算机图形学大作业之虚拟场景建模
  19. 游戏里面的模型是怎么制作的?次世代场景建模有哪些特点?
  20. 汽车电子控制-汽油机电子控制QA(1)

热门文章

  1. 中山计算机专硕不用发sci,最新!专硕发84篇SCI遭质疑,本人回应了
  2. 为什么signed char的范围是-128~127
  3. 罗丹明PEG罗丹明,RB-PEG-RB
  4. redis的zset为什么用跳表不用红黑树
  5. 计算机组装前需要的准备工作,手把手教你攒电脑:组装电脑全过程
  6. 微风:什么是UI设计?
  7. 极验滑块识别-通用滑块识别
  8. python批量移动文件到指定文件夹_使用python批量将文件夹中的文件移动到某个文件夹下...
  9. 微信运动服务器多久同步一次,微信运动多久更新一次步数(微信运动刷新时间表)...
  10. 树莓派卸载系统自带应用增大硬盘空间