数值计算 --- 平方根倒数快速算法(中)

平方根倒数快速算法(中) --- 向Greg Walsh致敬!

        在前面的介绍中,我们已经知道了这段代码的作者Greg Walsh在函数的最后使用了NR-iteration,且只用了一次NR-iteration就能达到比较理想的精度。这样一来,选择正确的初值就显得尤为重要了。而选择初始值的关键又在于充分的利用浮点数x在计算机中的表示/编码方式

1,计算机浮点数的二进制表示IEEE-754

        在code中函数的输入number为一个float型的浮点数(注意:代码中的number就是我文中的x)。则根据标准IEEE 754,x的二进制浮点数表示如下(准确的说应该叫normal number的表示标准),其中,S表示符号位,E表示指数,T表示有效数字的尾数:

(-1)^{S}\times 2^{E-b}\times (1+T\cdot 2^{1-p})

         对1/\sqrt{x}中的开根号运算而言,x不可能是负数(负数没法进行开根号运算),所以默认符号位S恒为0。这样一来,浮点数x在计算机中的二进制可表示如下(其中:T_{x}E_{x}专门用于特指浮点数x所对应的T字段和E字段):

x= (-1)^{0}\times (1+T_{x}\cdot 2^{1-p})\times 2^{E_{x}-b}=(1+T_{x}\cdot 2^{1-p})\times 2^{E_{x}-b}

        在IEEE-754标准中,对于单精度float而言,精度p=24,指数部分的偏移量b=127,则:

x= (1+T_{x}\cdot 2^{-23})\times 2^{E_{x}-127}(式1)


2,二进制浮点数x的对数表达式 --- log2(x)

我们对x的二进制浮点数表示(式1)取以2为底的对数,得到:

{log_{2}}^{x}={log_{2}}^{(1+T_{x}\cdot 2^{-23})\times 2^{E_{x}-127}}={log_{2}}^{(1+T_{x}\cdot 2^{-23})}+{E_{x}-127}

{log_{2}}^{x}={log_{2}}^{(1+T_{x}\cdot 2^{-23})}+{E_{x}-127}(式2)

使用整体替换,令:

 M_{x}=T_{x}\cdot 2^{-23}(式3)

(式3)中的整体替换代入(式2),则(式2)变为:

 {log_{2}}^{x}={log_{2}}^{(1+T_{x}\cdot 2^{-23})}+{E_{x}-127}={log_{2}}^{(1+M_{x})}+{E_{x}-127}

得到:

{log_{2}}^{x}={log_{2}}^{(1+M_{x})}+{E_{x}-127}(式4)


3,log2(1+M)的近似

        根据IEEE-754的标准,T字段所保存的是trailing significand,即,放大一定精度后的有效数字的尾数或者说是放大一定精度后的有效数字的小数部分(不保存整数部位的1)。计算机在保存T时,是先把小数点右移了23位,即,乘以2^{23}之后才保存的。因此,在读取T时才有了上面的T_{x}\cdot 2^{-23}。这就是说上面的M_{x}实际上就是“1.xxxxx...”中的小数部分“0.xxxxx...”,是一个介于0~1之间的数。

为了更好的理解M,这里插播一个例子。比如说,数1/3是如何被保存成二进制浮点数的?

        计算机使用二进制浮点数表示小数时,采用的是IEEE 754浮点数标准。1/3是一个无限循环小数,计算机在保存他的时候不能都存下来,所以计算机只能保存到一定精度。

1. 十进制转二进制

        在十进制下,1/3​=0.33333...是一个循环小数。转换到二进制后为:

\frac{1}{3}_{10}=0.333..._{10}=0.01010101..._{2}

 

        也是一个二进制的循环小数,但由于计算机只能只能保存有限的位数,这个循环小数在保存时会被截断,得到一个近似值。

2. IEEE 754 浮点数表示

在 IEEE 754 单精度浮点数标准中,32位浮点数的表示结构如下:

  • 1 位符号位:表示正数或负数
  • 8 位指数:存储实际指数的偏移量(偏移 127)
  • 23 位尾数(有效数字):存储归一化的尾数,隐含首位为 1 的小数部分

        对于1/3,计算机会先将其转换为二进制表示,然后使用以下步骤:

        1,标准化二进制小数:将二进制小数表示成规范形式。规范形式要求小数点左侧只能有一位,且必须是1,因此:

0.01010101..._{2}=1.01010101..._{2}\times 2^{-2}

        2,计算指数E:指数部分需要加上偏移量(127)。所以,计算机所保存的指数E等于上面的实际指数−2加上127。−2+127=125,再转换为二进制后为 01111101​。

        3,有效数字的尾数T:有效数字尾数的精度共 23 位,因此我们在保存小数部分时,去掉整数部分的1不保存:

1.01010101..._{2}\Rightarrow 0.01010101..._{2}

        然后再把小数点右移23位,得到:

0.01010101..._{2}\Rightarrow 01010101010101010101010_{2}

        4,最终的存储形式:将符号位(0,正数)、指数(125 的二进制表示01111101)和有效数字的尾数组合起来,得到:

00111110101010101010101010101010_{2}

        这就是1/3在IEEE 754标准下的单精度二进制浮点数表示。

        由于M_{x}是一个0~1之间的小数。以M为自变量,当M=0~1时,函数y=log2(1+M)与y=M的函数值差异很小。

Matlab code:

close all
clear allM=0.01:0.01:pi/2;
f1=log2(1+M);
f2=M;
plot(M,f1,M,f2);
grid on;
legend("y=log2(1+M)","y=M")diff=abs(f1-f2);
figure
plot(M,diff)
legend("diff")

        基于上面二者的微弱差异,我们暂且认为在M_{x}=0~1之间有下面的近似:

 {log_{2}}^{(1+M_{x})}\approx M_{x}(式5)

        基于(式5)的近似,(式4)变为:

{log_{2}}^{x}={log_{2}}^{(1+M_{x})}+E-127\approx M_{x}+E_{x}-127

{log_{2}}^{x}\approx M_{x}+E_{x}-127(式6)

        若再把之前的整体替换(式3)代回到(式6)中,则: 

 {log_{2}}^{x}\approx T_{x}\cdot 2^{-23}+E_{x}-127=1/2^{-23}\cdot (E_{x}\times 2^{23}+T_{x})-127

 得到:

{log_{2}}^{x}\approx 1/2^{-23}\cdot (E_{x}\times 2^{23}+T_{x})-127(式7)

        又因为(式7)括号中的E\times 2^{23}+T,正好是浮点数x在计算机中的存储形式(我们这里用x_{B}来表示),即:

x_{B}=E_{x}\times 2^{23}+T_{x}(式8)

        这里,我们再补充说明一下。如果还是以上面插播的1/3为例的话。我文章中的x就是十进制的1/3,而x_{B}就是上面那个例子中最终保存的00111110101010101010101010101010_{2}。他们是同一个数,只不过一个是平常看到的十进制数,一个是这个十进制数在计算机中保存的样子。


4,二进制浮点数x对数表达式的通式

        如此一来,我们利用浮点数x在计算机中默认的二进制存储方式(也就是(式8)),再度改写了浮点数x的对数表达式(式7)(最初的对数表达式为(式4)):

 {log_{2}}^{x}=1/2^{-23}\cdot (E\times 2^{23}+T)-127=1/2^{-23}\cdot x_{B}-127

 {log_{2}}^{x}=x_{B}/2^{23}-127(式9)


5,神秘数字 --- “5f3759df”

        现在我们再回到计算1/\sqrt{x}的近似值问题。在上一篇文章的开篇,为了转化1/\sqrt{x}的计算问题,我们假设1/\sqrt{x}的值为a,得到:

a=1/\sqrt{x}=x^{-1/2}

a=x^{-1/2}(式10)

 对(式10)两边同时取以2为底的对数,得到:

{log_{2}}^{a}={log_{2}}^{x^{-1/2}}

\Rightarrow {log_{2}}^{a}=-1/2\cdot {log_{2}}^{x}(式11)

        根据前面推导出的log2(x)的表示方式(式9),则(式11)的左右两边分别变为:(注意前面推导出(式9)的是一个通式,即可以表示log2(x),也可以表示log2(a),log2(b),log2(c)。。。等)

 {log_{2}}^{a}=a_{B}/2^{23}-127-1/2\cdot {log_{2}}^{x}=-1/2\cdot (x_{B}/2^{23}-127)

         最终(式11)变为:

 a_{B}/2^{23}-127=-1/2\cdot (x_{B}/2^{23}-127)

 a_{B}=381\times 2^{22}-x_{B}/2(式12)

此处的a_{B},表示浮点数a在计算机中基于IEEE-754标准的存储形式。

        (式12)中的381\times 2^{22}这个数,如果用十六进制来表示的话就是:

 381\times 2^{22}=1598029824_{10}=5f400000_{16}

        则(式12)可改写为:

 a_{B}=5f400000-x_{B}/2(式13)

        这里的十六进制数“5f400000”和code中的那个神秘数字“5f3759df”已经比较接近了,而5f3759df表示成十进制是1597463007

两者在十进制上相差了566817。 


6,锐评"what the fuck?"的上下两句在干啥?

        这里,我们暂时先不讨论这两个十六进制常数的差异,先看看(式13)究竟表示什么意思:

 a_{B}=5f400000-x_{B}/2(式13)

        首先,我们知道a就是我们要求的十进制数x的平方根倒数,而我们又知道不论十进制数a或x是多少,他在计算机中都要以二进制浮点数的方式被保存为a_{B}x_{B}的形式。因此,(式13)的意思是说,对于一个已经按照IEEE 754标准被保存好的十进制浮点数x或a,他在计算机中换了个样子,变成了x_{B}a_{B},他们仍然等于x和a。

        而要想求得x_{B}的平方根倒数,只需按照(式13)就能快速求出近似值a_{B},这个a_{B}等于十进制浮点数a,只不过保存在计算机中的样子是a_{B}。而要想把a_{B}再变成a,只需按照浮点数的编码方式解析出来即可。

        现在让我们再回到原代码,请注意,被评论为WTF的上下两句所做的,正是我上文中描述的过程。所不同的是代码中的y是我文中的x,代码中的i是我文中的x_{B},代码中的经过神秘数字“5f3759df”计算后新得到的i是我文中的a_{B},而把新i重新解码后的浮点数y是我文中的a

        现在,我们有了能够快速求出较为精确的1/\sqrt{x}的初值公式(式13),再加上之前根据牛顿拉夫逊法求得的a_{n+1}=a_{n}(1.5-x{a_{n}}^{2}/2)。至此,我们基本上复现了平方根倒数快速算法的全部过程,且和原始code一致(除了magic number之外)。


7,小试牛刀

       我们来试试我们现有的快速算法,看看他的效果究竟怎么样,还是以x=1为例,求1/\sqrt{1}

C code:

# include <stdio.h>
# include <math.h>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;
}float myQ_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 = 0x5f400000 - (i >> 1);y = *(float*)&i;y = y * (threehalfs - (x2 * y * y));   // 1st iteration// y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removedreturn y;
}int main() {float x = 4.0f;float y = 0,yy=0;y=Q_rsqrt(x);yy = myQ_rsqrt(x);printf("input x=%f\n", x);printf("ideal result=%f\n", 1/sqrt(x));printf("calc with 5f3759df=%f\n", y);printf("calc with 5f400000=%f\n", yy);return 0;
}

code相应的输出为:

        就本例而言,二者的计算结果都非常接近准确值1。5f400000的精度更高,几乎没有精度误差,5f3759df的误差约为0.002。 

        如果以x=4为例,参考准确值为0.5,再看看二者的表现:

        结果还是5f400000的准确性更高,同样是几乎等于标准答案。但5f3759df的误差约为0.0009。上面的两个例子都是平方根的结果正好是整数的情况,例如\sqrt{1}=1\sqrt{4}=2

        如果碰到平方根为无理数的情况呢?我们分别试试x=2和x=3的情况。

        有趣的是,在这两个例子中基于5f3759df的计算结果要比5f400000的精度高。其中,对于x=2而言,5f400000的误差约为0.004 > 5f3759df的误差约为0.0002。 对于x=3而言,5f400000的误差约为0.006 > 5f3759df的误差约为0.0005。 

        这样看来,不论是采取哪种常数去估算初值,基本上都已经能够得到较为准确的结果。如果还需要得到更精确的结果,可用牛顿拉夫逊法再迭代一次即可。关于平方根倒数快速算法code的分析至此就全部写完了,后续我会再写一些关于神秘数---5f3759df的思考。


(全文完) 

--- 作者,松下J27

参考文献(鸣谢):

1,https://en.wikipedia.org/wiki/Newton%27s_method#Examples

2,什么代码让程序员之神感叹“卧槽”?改变游戏行业的平方根倒数算法_哔哩哔哩_bilibili

3,[算法] 平方根倒数速算法中的魔数0x5f3759df的来源 | GeT Left

4,https://i.hsfzxjy.site/uncover-the-secret-of-fast-inverse-square-root-algorithm/

5,https://www.youtube.com/watch?v=p8u_k2LIZyo

6,计算机中的浮点数(一)_浮点表示法-CSDN博客

7,计算机中的浮点数(二)-CSDN博客 

8,Beyond3D - Origin of Quake3's Fast InvSqrt()

9,Beyond3D - Origin of Quake3's Fast InvSqrt() - Part Two

10,The Fast Inverse Square Root

11,How Fast Inverse Square Root actually works

版权声明:所有的笔记,可能来自很多不同的网站和说明,在此没法一一列出,如有侵权,请告知,立即删除。欢迎大家转载,但是,如果有人引用或者COPY我的文章,必须在你的文章中注明你所使用的图片或者文字来自于我的文章,否则,侵权必究。 ----松下J27  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/432465.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生|浅谈云原生中的对象存储之MinIO 的使用

一、什么是对象储存 对象存储&#xff08;Object Storage&#xff09;以对象的形式存储和管理数据&#xff0c;这些对象可以是任何类型的数据&#xff0c;例如 PDF&#xff0c;视频&#xff0c;音频&#xff0c;文本或其他文件类型。对象存储使用分布式存储架构&#xff0c;数据…

C语言贪吃蛇小游戏演示和说明

C语言贪吃蛇小游戏演示和说明 设计贪吃蛇游戏的主要目的是让大家夯实C语言基础&#xff0c;训练编程思维&#xff0c;培养解决问题的思路&#xff0c;领略多姿多彩的C语言。 游戏开始后&#xff0c;会在中间位置出现一条只有三个节点的贪吃蛇&#xff0c;并随机出现一个食物&am…

数据结构练习题————(二叉树)——考前必备合集!

今天在牛客网和力扣上带来了数据结构中二叉树的进阶练习题 1.二叉搜索树与双向链表———二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com) 2.二叉树遍历————二叉树遍历_牛客题霸_牛客网 (nowcoder.com) 3.二叉树的层序遍历————102. 二叉树的层序遍历 - 力扣&am…

寿司检测系统源码分享

寿司检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

一文了解高速工业相机

超高速相机是工业相机的一种&#xff0c;一般高速相机指的是数字工业相机&#xff0c;其一般安装在机器流水线上代替人眼来做测量和判断&#xff0c;通过数字图像摄取目标转换成图像信号&#xff0c;传送给专用的图像处理系统。 超高速工业相机的采集速率> 50Gb/s&#xff…

window系统DockerDesktop 部署windows容器

目录 参考文献1、安装Docker Desktop1.1 下载安装包1.2 安装教程1.3 异常解决 2、安装windows容器2.1 先启动DockerDesktop 软件界面2.2 检查docker版本2.3 拉取windows镜像2.4 网盘下载windows镜像 参考文献 windows容器docker中文官网 Docker: windows下跑windows镜像 1、安…

软件测试标准流程(思维导图版)

一套标准的流程在实际工作落地并执行起来&#xff0c;针对管理可起到很好的作用。 针对效率可在工作中不断的执行&#xff0c;执行后不断的进行优化&#xff0c;再次执行&#xff0c;在不断的工作实践中慢慢完善最终适用于整个团队。 这就是标准流程的作用与实际的好处&#…

华为申请鸿蒙甄选、鸿蒙优选商标,加词的注意!

近日华为在35类广告销售上申请鸿蒙智选、鸿蒙优选、鸿蒙精品&#xff0c;鸿蒙甄选等商标&#xff0c;后面所加的词智选、优选、精品、甄选等基本上是属于通用词。 这样在35类拿到鸿蒙通用词商标&#xff0c;需要先拿到“鸿蒙“商标&#xff0c;经普推知产商标老杨检索发现&…

【Linux】yum、vim、gcc使用(超详细)

目录 yum 安装软件 卸载软件 查看安装包 安装一下好玩的命令 vim vim基本操作 模式切换 命令集 vim批量注释 vim配置 gcc 函数库 小知识点&#xff1a; Linux中常见的软件安装方式 --------- 下载&&安装 a、yum/apt b、rpm安装包安装 c、源码安装 y…

周家庄智慧旅游小程序

项目概述 周家庄智慧旅游小程序将通过数字化手段提升游客的旅游体验&#xff0c;依托周家庄的自然与文化资源&#xff0c;打造智慧旅游新模式。该小程序将结合虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;和人工智能等技术&#xff0c;提供丰富的…

vue嵌套路由刷新页面空白问题

问题描述 在vue项目开发中遇到这样一个问题&#xff0c;在history模式下通过页面点击路由跳转可以打开页面&#xff0c;但是在当前页面刷新就空白了&#xff0c;如下&#xff1a; 点击路由跳转页面是有的 刷新页面就空白 代码 {path: "/home",name: "home&qu…

SQL常用数据过滤 - EXISTS运算符

SQL查询中的EXISTS运算符用于检查查询子句是否存在满足特定条件的记录&#xff0c;如果有一条或者多条记录存在&#xff0c;则返回True&#xff0c;否则返回False。 语法结构 SELECT column_name(s)FROM table_nameWHERE EXISTS(SELECT column_name FROM table_name WHERE co…

React学习笔记(2.0)

React事件绑定 语法&#xff1a;在对应标签上书写on事件&#xff08;比如onClick,onChange&#xff09;&#xff0c;注意和原生的事件区分&#xff0c;React的事件首字母要大写。 const handleChange(e:any)>{console.log(e);console.log(change事件触发);// e不是原生事件…

Acwing 最小生成树

最小生成树 最小生成树:由n个节点&#xff0c;和n-1条边构成的无向图被称为G的一棵生成树&#xff0c;在G的所有生成树中&#xff0c;边的权值之和最小的生成树&#xff0c;被称为G的最小生成树。&#xff08;换句话说就是用最小的代价把n个点都连起来&#xff09; Prim 算法…

初识Jenkins持续集成系统

随着软件开发复杂度的不断提高&#xff0c;团队成员之间如何更好地协同工作以确保软件开发的质量&#xff0c;已经慢慢成为开发过程中不可回避的问题。Jenkins 自动化部署可以解决集成、测试、部署等重复性的工作&#xff0c;工具集成的效率明显高于人工操作;并且持续集成可以更…

Java线程池详解

目录 前言 线程池概述 线程池的实现 线程池的构造 拒绝策略 任务队列 线程池的工作原理 线程池的监控 Executors线程池工厂 自定义线程池 使用线程池的好处 应用场景 总结 本文详细探讨了线程池在并发编程领域的应用&#xff0c;介绍了ThreadPoolExecutor的核心组…

MySQL的登录、访问、退出

一、登录&#xff1a; 访问MySQL服务器对应的命令&#xff1a;mysql.exe ,位置&#xff1a;C:\Program Files\MySQL\MySQL Server 8.0\bin &#xff08;mysql.exe需要带参数执行&#xff0c;所以直接在图形界面下执行该命令会自动结束&#xff09; 执行mysql.exe命令的时候出…

2024年9月26日历史上的今天大事件早读

1620年9月26日 大明皇帝朱常洛驾崩 1815年9月26日 俄、普、奥三国在巴黎发表缔结“神圣同盟” 1841年9月26日 清代思想家、诗人龚自珍逝世 1849年9月26日 “生理学之父”巴甫洛夫诞生 1909年9月26日 云南陆军讲武堂创办 1953年9月26日 画家徐悲鸿逝世 1980年9月26日 国际…

VulnHub-Narak靶机笔记

Narak靶机笔记 概述 Narak是一台Vulnhub的靶机&#xff0c;其中有简单的tftp和webdav的利用&#xff0c;以及motd文件的一些知识 靶机地址&#xff1a; https://pan.baidu.com/s/1PbPrGJQHxsvGYrAN1k1New?pwda7kv 提取码: a7kv 当然你也可以去Vulnhub官网下载 一、nmap扫…

ChatGPT高级语音助手正式上线!OpenAI:50多种语言、9种声线可选

①OpenAI终于要面向其所有付费用户开放ChatGPT的类人高级人工智能&#xff08;AI&#xff09;语音助手功能——“高级语音模式”&#xff08;AVM&#xff09;&#xff1b; ②所有付费订阅ChatGPT Plus和Team计划的用户&#xff0c;都将可以使用新的AVM功能&#xff0c;不过该模…