博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
sqrt函数实现
阅读量:6124 次
发布时间:2019-06-21

本文共 2934 字,大约阅读时间需要 9 分钟。

感谢杨工,让我更加认识到自己技术薄弱,这道题源自于和杨工的非正式面试,当时根本没思路,甚至没和查找有丝毫的联系,看来做自己想做的还是要付出努力的。sqrt()即开平方运算,y=x*x,已知Y的情况下求解X的值,基本的思路是找个区间,逐步计算逼近,知道需要的精度。

(1)二分查找

并不是严格的二分查找,设定寻找的区间,在这个区间中一直取中点,计算中点的平方和Y的查找,逐步逼近,直到自己需要的精度:

#define  ABS_FLOAT 0.000001bool eqs(double val1 , double val2){    double diff = fabs(val1 - val2) ;    if(diff < ABS_FLOAT)    {        return true ;    }    else    {        return false ;    }}//获取开方值,二分查找的方法double SqrtBybisection(double _value){    if (_value <= 0 )        return 0 ;    double low  = 0.0;    double high = 0.0 ;    if (_value > 0 && _value < 1)    {        low = _value;        high = 1.0 ;    }    else    {        low  = 1.0 ;        high =  _value  ;    }    double mid  = (low + high)/2.0 ;    double last = 0.0 ;    do     {        if (mid * mid > _value)        {            high =  mid ;        }        else         {            low = mid ;        }        last = mid ;        mid = (high + low )/ 2.0 ;        //std::cout << mid << std::endl ;    }while(! eqs( last , mid)) ;    return mid ;}

  

    牛顿迭代法通过泰勒公司展开,通过切线逐步逼近,具体推到可以参考: , sqrt实现的代码:

//牛顿迭代法求解  /* f(x) = x^2 - v --> x = x0 - f(x0)/2x0 -->x = (x0 + v / x0) / 2  ;   -->*/double SqrtByNewton(const double& _val){    double nrt = _val ;     double last_nrt = 0 ;     while (! eqs( nrt , last_nrt))     {         last_nrt = nrt ;         nrt = (nrt + _val / nrt) / 2.0 ;     }     return  nrt ;}

3 技巧算法

看到这种解法,我也很惊讶,程序员真是无底线啊~~

先看看浮点数表示,浮点数不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。

数学中浮点用S=M*2^N, 在计算机中 主要由三部分构成:符号位+指数位(N)+尾数(M),符号位:0为正1为负,指数位:2^M ,移位存储,尾数:即有效数字,规定整数部分为1

float 浮点数内存分布:

 

31 30~23 22~0
1 位 符号位 8位 指数位 23位 尾数
double型浮点内存分布:
63 62~52 51~0
1 位 符号位 11位 指数位 52位 尾数
 
比如 float类型8.5,二进制表示为1000.1 ,标准表达为1.0001*2^3 , OK ,该数的指数位:127+3=130,即10000010 ,符号位 0, 尾数去掉1为0001 ,填充后为0001 0000 0000 0000 0000 000,这个数的表示为 1 1000010 0001  0000 0000 0000 0000 0000 000
符号位 指数位 尾数
0 10000010 0001 0000 0000 0000 0000 000
了解这些之后,再来看一下快速的技巧:
 
float sqrtinv(float x){    float xhalf = 0.5f*x;    int i = *(int*)&x; // get bits for floating VALUE     i = 0x5f375a86- (i>>1); // gives initial guess y0    x = *(float*)&i; // convert bits BACK to float    x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy    x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy    x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy    return 1/x;}
 

 

这个算法速度据说比系统函数还要快,确实,迭代的步骤少来很多,具体解释和浮点的表示有关,可以参考下文:一般而言,一个float数据 x 共32个bit,和int数据一样。其中前23位为有效数字 M_x ,后面接着一个8位数据 E_x 表示指数,最后一位表示符号,由于这里被开方的数总是大于0,所以我们暂不考虑最后一个符号位。此时

x=1.M_x 2^{E_x-127}

如果我们把计算机内的浮点数 x 看做一个整数 I_x ,那么

I_x = 2^{23}E_x+M_x

现在开始逐步分析函数。这个函数的主体有四个语句,分别的功能是:

int i = *(int*)&x; 这条语句把 x 转成 i=I_x

i = 0x5f3759df - (i>>1); 这条语句从 I_x 计算 I_{1/\sqrt{x}}

y = *(float*)&i; 这条语句将 I_{1/\sqrt{x}} 转换为 1/\sqrt{x}

y = y*(1.5f - xhalf*y*y); 这时候的y是近似解;此步就是经典的牛顿迭代法。迭代次数越多越准确。关键是第二步 i = 0x5f3759df - (i>>1); 这条语句从 I_x 计算 I_{1/\sqrt{x}} 原理:

 y=1/\sqrt{x}

x=(1+m_x)2^{e_x}y=(1+m_y)2^{e_y} 带入之后两边取对数,再利用近似表示

 \log_2(1+z)\sim z+\delta

算一算就得到:

I_y = \frac{2}{3}(127-\delta)2^{23}-I_x/2

若取 \delta=0.0450465679168701171875\frac{2}{3}(127-\delta)2^{23} 就是程序里所用的常量0x5f3759df。至于为何选择这个 \delta ,则应该是曲线拟合实验的结果。

4 测试结果

转载于:https://www.cnblogs.com/zsb517/p/3821073.html

你可能感兴趣的文章
会计基础_001
查看>>
ajax请求拿到多条数据拼接显示在页面中
查看>>
小程序: 查看正在写的页面
查看>>
dedecms生成文档数据库崩溃 mysql daemon failed to start
查看>>
Linux的50个基本命令
查看>>
Objective-C中创建单例方法的步骤
查看>>
Jenkins持续集成环境部署
查看>>
emoji等表情符号存mysql的方法
查看>>
检查磁盘利用率并且定期发送告警邮件
查看>>
MWeb 1.4 新功能介绍二:静态博客功能增强
查看>>
linux文本模式和文本替换功能
查看>>
Windows SFTP 的安装
查看>>
摄像机与绕任意轴旋转
查看>>
rsync 服务器配置过程
查看>>
预处理、const与sizeof相关面试题
查看>>
爬虫豆瓣top250项目-开发文档
查看>>
Elasticsearch增删改查
查看>>
oracle归档日志增长过快处理方法
查看>>
有趣的数学书籍
查看>>
teamviewer 卸载干净
查看>>