cyycoish 发表于 2015-9-26 14:31:33

【C】不使用sqrt函数计算开平方

不使用sqrt函数计算开平方
零、介绍
@元始天尊
和天尊兄第一次见面,他就给我出了一道难题:
不使用Sqrt函数,只能使用+-*/和流程控制,实现sqrt函数。
一、开始
我们先来实验一个数:sqrt(2)


然后设初始值为1,将1的代入公式:

得到x=1.5,将1.5再次代入公式:

得到x = 1.416666666,再次带入
第四次,所得到的x值已经为:x=1.414213562和同等小数精度的sqrt值相等
至此我们已经得到sqrt(2)的迭代公式:

由此,我们来推广sqrt(A),A属于任意实数的迭代公式:

我们来试一下:
假设A=5,求sqrt(A)的值:

反复带入:
第2次sqrt(5) = 2.333333.....
第3次sqrt(5) = 2.238095238
第4次sqrt(5) = 2.236068896
第5次sqrt(5) = 2.236067978
第6次sqrt(5) = 2.236067977
第7次sqrt(5) = 2.236067977
.........
所以,对于任意实数A,其sqrt(A)的迭代公式为:

二、关于迭代精度问题
假设迭代初始值都从1开始,我们分别对2、3、4、5、6、7、8、9进行计算
当迭代结果精度与sqrt函数返回值小数第九位的精度相等,表示我们已经达到目的
开始测试:
对象2
Sqrt(2) ~=1.414213562
1.Sqrt(2) ~=1.5
2. Sqrt(2) ~=1.416666667
3. Sqrt(2) ~=1.414215686
4. Sqrt(2) ~=1.414213562(OK)
对象 3
Sqrt(3) ~=1.732050808
1. Sqrt(3) ~=2
2. Sqrt(3) ~=1.75
3. Sqrt(3) ~=1.732142857
4. Sqrt(3) ~=1.73205081
5. Sqrt(3) ~=1.732050808(OK)
对象 4
Sqrt(4) =2
1. Sqrt(4) ~=2.5
2. Sqrt(4) ~=2.05
3. Sqrt(4) ~=2.000609756
4. Sqrt(4) ~=2.000000093
5. Sqrt(4) ~=2.000000000(OK)
对象 5
Sqrt(5) ~=2.236067977
1. Sqrt(5) ~=3
2. Sqrt(5) ~=2.333333333
3. Sqrt(5) ~=2.238095238
4. Sqrt(5) ~=2.236068896
5. Sqrt(5) ~=2.236067978
6. Sqrt(5) ~=2.236067977(OK)
对象 6
Sqrt(6) ~=2.449489743
1. Sqrt(6) ~=3.5
2 Sqrt(6) ~=2.607142857
3. Sqrt(6) ~=2.454235636
4. Sqrt(6) ~=2.449494372
5. Sqrt(6) ~=2.449489743(OK)
对象 7
Sqrt(7) ~=2.645751311
1. Sqrt(7) ~=4
2. Sqrt(7) ~=2.875
3. Sqrt(7) ~=2.654891304
4.Sqrt(7) ~=2.645767044
5. Sqrt(7) ~=2.645751311(OK)
对象 8
Sqrt(8) ~=2.828427125
1. Sqrt(8) ~=4.5
2. Sqrt(8) ~=3.138888889
3. Sqrt(8) ~=2.843780728
4. Sqrt(8) ~=2.828468572
5. Sqrt(8) ~=2.828427125(OK)
对象 9
Sqrt(9) =3
1.Sqrt(9) ~=5
2. Sqrt(9) ~=3.4
3. Sqrt(9) ~=3.023529412
4. Sqrt(9) ~=3.000091554
5. Sqrt(9) ~=3.000000001
6. Sqrt(9) ~=3.000000000(OK)
结论,可见,要使sqrt函数精确到小数点后第九位,迭代初始数为1,迭代次数5次以上即可。
三、算法的优化
我们说,迭代初始值从1开始还是太慢,计算量太大了。因为,如果我们计算sqrt到小数点后1千位呢?所以,这一小节我们试着探索优化迭代初始值。
那么,经过猜测迭代初始值应该越接近sqrt函数的返回值(也就是x的开平方)越好:
考虑到算法的复杂性要求,我们将x迭代初始值设为(x/2)。因为:对于任一实数r有sqrt(r)恒大于等于r/2(几何原理)。
那么根据迭代公式:

改变迭代初始值为:

我们继续试验:
这里我用5,9,3来进行测试,因为上面测试结果中5和9的最大精确迭代次数都超过了平均值,而数字3则是从先前测试结果大于等于平均值的对象中随机选取的。
对象 5
迭代初始值=5/2
Sqrt(5) ~=2.236067977
1. Sqrt(5) ~=2.25
2. Sqrt(5) ~=2.236111111
3. Sqrt(5) ~=2.236067978
4. Sqrt(5) ~=2.236067977(OK)
对象 9
迭代初始值=9/2
Sqrt(9) =3
1.Sqrt(9) ~=4.5
2. Sqrt(9) ~=3.25
3. Sqrt(9) ~=3.009615385
4. Sqrt(9) ~=3.00001536
5. Sqrt(9) ~=3(OK)
对象 3
迭代初始值=1.5
Sqrt(3) ~=1.732050808
1. Sqrt(3) ~=1.75
2. Sqrt(3) ~=1.732142857
3. Sqrt(3) ~=1.732050801
4. Sqrt(3) ~=1.732050808(OK)
那么根据以上结果,三个测试数最小精确迭代次数均<=先前平均值。
从而证明:迭代初始值设为x/2能够起到一部分减小运算量的效果。
因为测试数据量不够,所以此结果仅仅作为猜想。
四、算法的C语言表示:
#include <stdio.h>
#include <math.h>

#define ITERATIONTIMES (128) //最大精确迭代次数

double CustomSquare(double number)
{
        int i;
        if (number == 0.0)
                return 0.0;
        double result = number / 2.0; //迭代初始数
        for (i = 1; i <= ITERATIONTIMES; i++)
        {
                result = 0.5 * (result + number / result);
        }
        return result;
}

int main(void)
{
        double di;
        printf("Input number:");
        scanf("%lf", &di);
        printf("Result by sqrt      function:%1.9f\n", sqrtf(di));
        printf("Result by custom sqrt function:%1.9f\n", CustomSquare(di));
}

乘简 发表于 2017-9-28 15:38:45

本帖最后由 乘简 于 2017-9-28 15:40 编辑

网上抄了个代码,不用循环,也可以计算sqrt,精度与库函数一样,号称比库函数更快的算法。。。
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 hacking
    i= 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 removed

    return y;
}

唐凌 发表于 2017-10-16 10:17:56

其实可以参考下牛顿迭代法

令狐公子 发表于 2018-5-15 00:44:42

乘简 发表于 2017-9-28 15:38
网上抄了个代码,不用循环,也可以计算sqrt,精度与库函数一样,号称比库函数更快的算法。。。


这个代码中的 0x5f3759df 大有来头,有兴趣可以搜索一下,建议加个关键词 约翰卡马克

0xAA55 发表于 2018-11-14 18:18:02

乘简 发表于 2017-9-28 15:38
网上抄了个代码,不用循环,也可以计算sqrt,精度与库函数一样,号称比库函数更快的算法。。。


然而你这个计算的不是sqrt,而是rsqrt,也就是reciprocal of the square root——倒数平方根(平方根倒数)。可用于向量的“标准化”

玫瑰花葬礼 发表于 2018-12-6 10:55:04

很好的选择

Cer 发表于 2020-3-10 11:27:58

学习一下。
页: [1]
查看完整版本: 【C】不使用sqrt函数计算开平方