C++ 如何改进小值的不动点平方根

C++ 如何改进小值的不动点平方根,c++,embedded,fixed-point,sqrt,square-root,C++,Embedded,Fixed Point,Sqrt,Square Root,我正在使用Dobb博士的文章“”中描述的Anthony Williams的固定点库来计算两个地理点之间的距离 当点之间的距离很大(大于几公里)时,这种方法效果很好,但在较小距离时效果很差。最坏的情况是,当两个点相等或接近相等时,结果是距离为194米,而在距离>=1米时,我需要至少1米的精度 通过与双精度浮点实现进行比较,我发现问题出在fixed::sqrt()函数上,该函数在小值时性能较差: x std::sqrt(x) fixed::sqrt(x) error ------

我正在使用Dobb博士的文章“”中描述的Anthony Williams的固定点库来计算两个地理点之间的距离

当点之间的距离很大(大于几公里)时,这种方法效果很好,但在较小距离时效果很差。最坏的情况是,当两个点相等或接近相等时,结果是距离为194米,而在距离>=1米时,我需要至少1米的精度

通过与双精度浮点实现进行比较,我发现问题出在
fixed::sqrt()
函数上,该函数在小值时性能较差:

x       std::sqrt(x)    fixed::sqrt(x)  error
----------------------------------------------------
0       0               3.05176e-005    3.05176e-005
1e-005  0.00316228      0.00316334      1.06005e-006
2e-005  0.00447214      0.00447226      1.19752e-007
3e-005  0.00547723      0.0054779       6.72248e-007
4e-005  0.00632456      0.00632477      2.12746e-007
5e-005  0.00707107      0.0070715       4.27244e-007
6e-005  0.00774597      0.0077467       7.2978e-007
7e-005  0.0083666       0.00836658      1.54875e-008
8e-005  0.00894427      0.00894427      1.085e-009
fixed::sqrt(0)
的结果视为特例进行纠正是微不足道的,但这并不能解决小非零距离的问题,其中误差从194米开始,并随着距离的增加向零收敛。我可能需要至少一个数量级的精度提高到零

fixed::sqrt()
算法在上面链接的文章的第4页中简要介绍了它,但我正在努力遵循它,更不用说确定是否有可能改进它了。该功能的代码如下所示:

fixed fixed::sqrt() const
{
    unsigned const max_shift=62;
    uint64_t a_squared=1LL<<max_shift;
    unsigned b_shift=(max_shift+fixed_resolution_shift)/2;
    uint64_t a=1LL<<b_shift;

    uint64_t x=m_nVal;

    while(b_shift && a_squared>x)
    {
        a>>=1;
        a_squared>>=2;
        --b_shift;
    }

    uint64_t remainder=x-a_squared;
    --b_shift;

    while(remainder && b_shift)
    {
        uint64_t b_squared=1LL<<(2*b_shift-fixed_resolution_shift);
        int const two_a_b_shift=b_shift+1-fixed_resolution_shift;
        uint64_t two_a_b=(two_a_b_shift>0)?(a<<two_a_b_shift):(a>>-two_a_b_shift);

        while(b_shift && remainder<(b_squared+two_a_b))
        {
            b_squared>>=2;
            two_a_b>>=1;
            --b_shift;
        }
        uint64_t const delta=b_squared+two_a_b;
        if((2*remainder)>delta)
        {
            a+=(1LL<<b_shift);
            remainder-=delta;
            if(b_shift)
            {
                --b_shift;
            }
        }
    }
    return fixed(internal(),a);
}
结论 根据Justin Peel的解决方案和分析,并与中的算法进行比较,我对后者进行了如下调整:

fixed fixed::sqrt() const
{
    uint64_t a = 0 ;            // root accumulator
    uint64_t remHi = 0 ;        // high part of partial remainder
    uint64_t remLo = m_nVal ;   // low part of partial remainder
    uint64_t testDiv ;
    int count = 31 + (fixed_resolution_shift >> 1); // Loop counter
    do 
    {
        // get 2 bits of arg
        remHi = (remHi << 2) | (remLo >> 62); remLo <<= 2 ;

        // Get ready for the next bit in the root
        a <<= 1;   

        // Test radical
        testDiv = (a << 1) + 1;    
        if (remHi >= testDiv) 
        {
            remHi -= testDiv;
            a += 1;
        }

    } while (count-- != 0);

    return fixed(internal(),a);
}
fixed::sqrt()常量
{
uint64\u t a=0;//根累加器
uint64_t remHi=0;//部分余数的高部分
uint64_t remLo=m_nVal;//部分余数的低部分
uint64_t testDiv;
int count=31+(固定分辨率移位>>1);//循环计数器
做
{
//获取2位arg

remHi=(remHi>62)许多年前,我为我们公司制造的一台小型计算机制作了一个演示程序。这台计算机有一个内置的平方根指令,我们制作了一个简单的程序来演示计算机在TTY上做16位的加/减/乘/除/平方根。唉,原来平方根指令中有一个严重的错误,但我们承诺演示该函数。因此,我们创建了一个值为1-255的平方数组,然后使用简单的查找将键入的值与其中一个数组值进行匹配。索引是平方根。

给定
sqrt(ab)=sqrt(a)sqrt(b)
,那么你就不能在数字很小的情况下将其上移给定的位数,然后计算根并将其下移一半以得到结果吗


例如,对于任何小于2^8的n,选择k=28。

我不确定您是如何从表中所示的
fixed::sqrt()
中获取数字的

我是这样做的:

#include <stdio.h>
#include <math.h>

#define __int64 long long // gcc doesn't know __int64
typedef __int64 fixed;

#define FRACT 28

#define DBL2FIX(x) ((fixed)((double)(x) * (1LL << FRACT)))
#define FIX2DBL(x) ((double)(x) / (1LL << FRACT))

// De-++-ified code from
// http://www.justsoftwaresolutions.co.uk/news/optimizing-applications-with-fixed-point-arithmetic.html
fixed sqrtfix0(fixed num)
{
    static unsigned const fixed_resolution_shift=FRACT;

    unsigned const max_shift=62;
    unsigned __int64 a_squared=1LL<<max_shift;
    unsigned b_shift=(max_shift+fixed_resolution_shift)/2;
    unsigned __int64 a=1LL<<b_shift;

    unsigned __int64 x=num;

    unsigned __int64 remainder;

    while(b_shift && a_squared>x)
    {
        a>>=1;
        a_squared>>=2;
        --b_shift;
    }

    remainder=x-a_squared;
    --b_shift;

    while(remainder && b_shift)
    {
        unsigned __int64 b_squared=1LL<<(2*b_shift-fixed_resolution_shift);
        int const two_a_b_shift=b_shift+1-fixed_resolution_shift;
        unsigned __int64 two_a_b=(two_a_b_shift>0)?(a<<two_a_b_shift):(a>>-two_a_b_shift);
        unsigned __int64 delta;

        while(b_shift && remainder<(b_squared+two_a_b))
        {
            b_squared>>=2;
            two_a_b>>=1;
            --b_shift;
        }
        delta=b_squared+two_a_b;
        if((2*remainder)>delta)
        {
            a+=(1LL<<b_shift);
            remainder-=delta;
            if(b_shift)
            {
                --b_shift;
            }
        }
    }
    return (fixed)a;
}

// Adapted code from
// http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation
fixed sqrtfix1(fixed num)
{
    fixed res = 0;
    fixed bit = (fixed)1 << 62; // The second-to-top bit is set
    int s = 0;

    // Scale num up to get more significant digits

    while (num && num < bit)
    {
        num <<= 1;
        s++;
    }

    if (s & 1)
    {
        num >>= 1;
        s--;
    }

    s = 14 - (s >> 1);

    while (bit != 0)
    {
        if (num >= res + bit)
        {
            num -= res + bit;
            res = (res >> 1) + bit;
        }
        else
        {
            res >>= 1;
        }

        bit >>= 2;
    }

    if (s >= 0) res <<= s;
    else res >>= -s;

    return res;
}

int main(void)
{
    double testData[] =
    {
        0,
        1e-005,
        2e-005,
        3e-005,
        4e-005,
        5e-005,
        6e-005,
        7e-005,
        8e-005,
    };
    int i;

    for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
    {
        double x = testData[i];
        fixed xf = DBL2FIX(x);

        fixed sqf0 = sqrtfix0(xf);
        fixed sqf1 = sqrtfix1(xf);

        double sq0 = FIX2DBL(sqf0);
        double sq1 = FIX2DBL(sqf1);

        printf("%10.8f:  "
               "sqrtfix0()=%10.8f / err=%e  "
               "sqrt()=%10.8f  "
               "sqrtfix1()=%10.8f / err=%e\n",
               x,
               sq0, fabs(sq0 - sqrt(x)),
               sqrt(x),
               sq1, fabs(sq1 - sqrt(x)));
    }

    printf("sizeof(double)=%d\n", (int)sizeof(double));

    return 0;
}
编辑:

我忽略了一个事实,即上面的
sqrtfix1()
不能很好地处理大参数。它可以通过在参数中添加28个零并基本上计算其精确的整数平方根来修复。这是以在128位算术中进行内部计算为代价的,但它非常简单:

fixed sqrtfix2(fixed num)
{
    unsigned __int64 numl, numh;
    unsigned __int64 resl = 0, resh = 0;
    unsigned __int64 bitl = 0, bith = (unsigned __int64)1 << 26;

    numl = num << 28;
    numh = num >> (64 - 28);

    while (bitl | bith)
    {
        unsigned __int64 tmpl = resl + bitl;
        unsigned __int64 tmph = resh + bith + (tmpl < resl);

        tmph = numh - tmph - (numl < tmpl);
        tmpl = numl - tmpl;

        if (tmph & 0x8000000000000000ULL)
        {
            resl >>= 1;
            if (resh & 1) resl |= 0x8000000000000000ULL;
            resh >>= 1;
        }
        else
        {
            numl = tmpl;
            numh = tmph;

            resl >>= 1;
            if (resh & 1) resl |= 0x8000000000000000ULL;
            resh >>= 1;

            resh += bith + (resl + bitl < resl);
            resl += bitl;
        }

        bitl >>= 2;
        if (bith & 1) bitl |= 0x4000000000000000ULL;
        if (bith & 2) bitl |= 0x8000000000000000ULL;
        bith >>= 2;
    }

    return resl;
}

最初的实现显然存在一些问题。我对试图用当前代码的方式修复这些问题感到沮丧,最终用了不同的方法。我现在可能可以修复最初的实现,但无论如何我更喜欢我的方式

我将输入数字视为Q64开始,这与移位28相同,然后再移位14相同(sqrt将其减半)但是,如果你只是这样做,那么精度将限制在1/2^14=6.1035e-5,因为最后14位将是0。为了解决这个问题,我将正确地移位
a
余数
,并继续填充数字,我将再次循环。代码可以变得更高效、更干净,但我将把它留给其他人。精度下图所示与Q36.28中的情况基本相同。如果在输入数字被定点截断后(将其转换为定点并返回),将定点sqrt与浮点sqrt进行比较,则错误大约为2e-9(我在下面的代码中没有这样做,但需要一行更改).这与Q36.28的最佳精度一致,即1/2^28=3.7529e-9

顺便说一句,原始代码中的一个大错误是,从未考虑m=0的术语,因此无法设置位。总之,代码如下。请欣赏

#include <iostream>
#include <cmath>

typedef unsigned long uint64_t;

uint64_t sqrt(uint64_t in_val)
{
    const uint64_t fixed_resolution_shift = 28;
    const unsigned max_shift=62;
    uint64_t a_squared=1ULL<<max_shift;
    unsigned b_shift=(max_shift>>1) + 1;
    uint64_t a=1ULL<<(b_shift - 1);

    uint64_t x=in_val;

    while(b_shift && a_squared>x)
    {
        a>>=1;
        a_squared>>=2;
        --b_shift;
    }

    uint64_t remainder=x-a_squared;
    --b_shift;

    while(remainder && b_shift)
    {
        uint64_t b_squared=1ULL<<(2*(b_shift - 1));
        uint64_t two_a_b=(a<<b_shift);

        while(b_shift && remainder<(b_squared+two_a_b))
        {
            b_squared>>=2;
            two_a_b>>=1;
            --b_shift;
        }
        uint64_t const delta=b_squared+two_a_b;
        if((remainder)>=delta && b_shift)
        {
            a+=(1ULL<<(b_shift - 1));
            remainder-=delta;
            --b_shift;
        }
    }
    a <<= (fixed_resolution_shift/2);
    b_shift = (fixed_resolution_shift/2) + 1;
    remainder <<= (fixed_resolution_shift);

    while(remainder && b_shift)
    {
        uint64_t b_squared=1ULL<<(2*(b_shift - 1));
        uint64_t two_a_b=(a<<b_shift);

        while(b_shift && remainder<(b_squared+two_a_b))
        {
            b_squared>>=2;
            two_a_b>>=1;
            --b_shift;
        }
        uint64_t const delta=b_squared+two_a_b;
        if((remainder)>=delta && b_shift)
        {
            a+=(1ULL<<(b_shift - 1));
            remainder-=delta;
            --b_shift;
        }
    }

    return a;
}

double fixed2float(uint64_t x)
{
    return static_cast<double>(x) * pow(2.0, -28.0);
}

uint64_t float2fixed(double f)
{
    return static_cast<uint64_t>(f * pow(2, 28.0));
}

void finderror(double num)
{
    double root1 = fixed2float(sqrt(float2fixed(num)));
    double root2 = pow(num, 0.5);
    std::cout << "input: " << num << ", fixed sqrt: " << root1 << " " << ", float sqrt: " << root2 << ", finderror: " << root2 - root1 << std::endl;
}

main()
{
    finderror(0);
    finderror(1e-5);
    finderror(2e-5);
    finderror(3e-5);
    finderror(4e-5);
    finderror(5e-5);
    finderror(pow(2.0,1));
    finderror(1ULL<<35);
}

Clifford-文章的URL被套住了(要求UBM Techweb登录,你可能已经登录了&没有经历过这种情况)。我试图在别处找到这篇文章,但没有找到-谷歌缓存似乎是最好的选择。感谢您首先引用了这篇文章。@Dan,我访问了原始链接,没有遇到任何问题。我从未使用过UBM,我认为我没有登录到DDJ,所以我不知道为什么会出现问题。我使用该代码已有一段时间了几年前,我下载了这个库,可能是在需要登录之前。从@markransem获取它-好的,我必须研究一下这个…我从drdobbs/ubm/ddj获得了cookies,只要我删除它们,它就让我通过,而不必强迫我登录。很好,给你的注册用户更多的障碍。你到底是如何从
fixe获得这些数字的d::sqrt()
如表所示?您使用了什么编译器+操作系统?除了0的平方根,我没有得到相同的数字。无论是使用gcc(DJGPP/DOS),还是使用Open Watcom(Windows)。对于
x
,我的结果与表中的结果相差10^-5到10^-6左右,而不是10^-7或10^-9。填写表时是否使用了超过28个小数位?您是如何将
fixed
转换为/从
fixed
的?浮点类型的大小是多少(顺便说一句,是双精度还是长双精度)?非常聪明和高效的解决方案。不幸的是
0.00000000:  sqrtfix0()=0.00003052 / err=3.051758e-05  sqrt()=0.00000000  sqrtfix1()=0.00000000 / err=0.000000e+00
0.00001000:  sqrtfix0()=0.00311279 / err=4.948469e-05  sqrt()=0.00316228  sqrtfix1()=0.00316207 / err=2.102766e-07
0.00002000:  sqrtfix0()=0.00445557 / err=1.656955e-05  sqrt()=0.00447214  sqrtfix1()=0.00447184 / err=2.974807e-07
0.00003000:  sqrtfix0()=0.00543213 / err=4.509667e-05  sqrt()=0.00547723  sqrtfix1()=0.00547720 / err=2.438148e-08
0.00004000:  sqrtfix0()=0.00628662 / err=3.793423e-05  sqrt()=0.00632456  sqrtfix1()=0.00632443 / err=1.262553e-07
0.00005000:  sqrtfix0()=0.00701904 / err=5.202484e-05  sqrt()=0.00707107  sqrtfix1()=0.00707086 / err=2.060551e-07
0.00006000:  sqrtfix0()=0.00772095 / err=2.501943e-05  sqrt()=0.00774597  sqrtfix1()=0.00774593 / err=3.390476e-08
0.00007000:  sqrtfix0()=0.00836182 / err=4.783859e-06  sqrt()=0.00836660  sqrtfix1()=0.00836649 / err=1.086198e-07
0.00008000:  sqrtfix0()=0.00894165 / err=2.621519e-06  sqrt()=0.00894427  sqrtfix1()=0.00894409 / err=1.777289e-07
sizeof(double)=8
fixed sqrtfix2(fixed num)
{
    unsigned __int64 numl, numh;
    unsigned __int64 resl = 0, resh = 0;
    unsigned __int64 bitl = 0, bith = (unsigned __int64)1 << 26;

    numl = num << 28;
    numh = num >> (64 - 28);

    while (bitl | bith)
    {
        unsigned __int64 tmpl = resl + bitl;
        unsigned __int64 tmph = resh + bith + (tmpl < resl);

        tmph = numh - tmph - (numl < tmpl);
        tmpl = numl - tmpl;

        if (tmph & 0x8000000000000000ULL)
        {
            resl >>= 1;
            if (resh & 1) resl |= 0x8000000000000000ULL;
            resh >>= 1;
        }
        else
        {
            numl = tmpl;
            numh = tmph;

            resl >>= 1;
            if (resh & 1) resl |= 0x8000000000000000ULL;
            resh >>= 1;

            resh += bith + (resl + bitl < resl);
            resl += bitl;
        }

        bitl >>= 2;
        if (bith & 1) bitl |= 0x4000000000000000ULL;
        if (bith & 2) bitl |= 0x8000000000000000ULL;
        bith >>= 2;
    }

    return resl;
}
0.00000000:  sqrtfix0()=0.00003052 / err=3.051758e-05  sqrt()=0.00000000  sqrtfix2()=0.00000000 / err=0.000000e+00
0.00001000:  sqrtfix0()=0.00311279 / err=4.948469e-05  sqrt()=0.00316228  sqrtfix2()=0.00316207 / err=2.102766e-07
0.00002000:  sqrtfix0()=0.00445557 / err=1.656955e-05  sqrt()=0.00447214  sqrtfix2()=0.00447184 / err=2.974807e-07
0.00003000:  sqrtfix0()=0.00543213 / err=4.509667e-05  sqrt()=0.00547723  sqrtfix2()=0.00547720 / err=2.438148e-08
0.00004000:  sqrtfix0()=0.00628662 / err=3.793423e-05  sqrt()=0.00632456  sqrtfix2()=0.00632443 / err=1.262553e-07
0.00005000:  sqrtfix0()=0.00701904 / err=5.202484e-05  sqrt()=0.00707107  sqrtfix2()=0.00707086 / err=2.060551e-07
0.00006000:  sqrtfix0()=0.00772095 / err=2.501943e-05  sqrt()=0.00774597  sqrtfix2()=0.00774593 / err=3.390476e-08
0.00007000:  sqrtfix0()=0.00836182 / err=4.783859e-06  sqrt()=0.00836660  sqrtfix2()=0.00836649 / err=1.086198e-07
0.00008000:  sqrtfix0()=0.00894165 / err=2.621519e-06  sqrt()=0.00894427  sqrtfix2()=0.00894409 / err=1.777289e-07
2.00000000:  sqrtfix0()=1.41419983 / err=1.373327e-05  sqrt()=1.41421356  sqrtfix2()=1.41421356 / err=1.851493e-09
34359700000.00000000:  sqrtfix0()=185363.69654846 / err=5.097361e-06  sqrt()=185363.69655356  sqrtfix2()=185363.69655356 / err=1
.164153e-09
#include <iostream>
#include <cmath>

typedef unsigned long uint64_t;

uint64_t sqrt(uint64_t in_val)
{
    const uint64_t fixed_resolution_shift = 28;
    const unsigned max_shift=62;
    uint64_t a_squared=1ULL<<max_shift;
    unsigned b_shift=(max_shift>>1) + 1;
    uint64_t a=1ULL<<(b_shift - 1);

    uint64_t x=in_val;

    while(b_shift && a_squared>x)
    {
        a>>=1;
        a_squared>>=2;
        --b_shift;
    }

    uint64_t remainder=x-a_squared;
    --b_shift;

    while(remainder && b_shift)
    {
        uint64_t b_squared=1ULL<<(2*(b_shift - 1));
        uint64_t two_a_b=(a<<b_shift);

        while(b_shift && remainder<(b_squared+two_a_b))
        {
            b_squared>>=2;
            two_a_b>>=1;
            --b_shift;
        }
        uint64_t const delta=b_squared+two_a_b;
        if((remainder)>=delta && b_shift)
        {
            a+=(1ULL<<(b_shift - 1));
            remainder-=delta;
            --b_shift;
        }
    }
    a <<= (fixed_resolution_shift/2);
    b_shift = (fixed_resolution_shift/2) + 1;
    remainder <<= (fixed_resolution_shift);

    while(remainder && b_shift)
    {
        uint64_t b_squared=1ULL<<(2*(b_shift - 1));
        uint64_t two_a_b=(a<<b_shift);

        while(b_shift && remainder<(b_squared+two_a_b))
        {
            b_squared>>=2;
            two_a_b>>=1;
            --b_shift;
        }
        uint64_t const delta=b_squared+two_a_b;
        if((remainder)>=delta && b_shift)
        {
            a+=(1ULL<<(b_shift - 1));
            remainder-=delta;
            --b_shift;
        }
    }

    return a;
}

double fixed2float(uint64_t x)
{
    return static_cast<double>(x) * pow(2.0, -28.0);
}

uint64_t float2fixed(double f)
{
    return static_cast<uint64_t>(f * pow(2, 28.0));
}

void finderror(double num)
{
    double root1 = fixed2float(sqrt(float2fixed(num)));
    double root2 = pow(num, 0.5);
    std::cout << "input: " << num << ", fixed sqrt: " << root1 << " " << ", float sqrt: " << root2 << ", finderror: " << root2 - root1 << std::endl;
}

main()
{
    finderror(0);
    finderror(1e-5);
    finderror(2e-5);
    finderror(3e-5);
    finderror(4e-5);
    finderror(5e-5);
    finderror(pow(2.0,1));
    finderror(1ULL<<35);
}
input: 0, fixed sqrt: 0 , float sqrt: 0, finderror: 0
input: 1e-05, fixed sqrt: 0.00316207 , float sqrt: 0.00316228, finderror: 2.10277e-07
input: 2e-05, fixed sqrt: 0.00447184 , float sqrt: 0.00447214, finderror: 2.97481e-07
input: 3e-05, fixed sqrt: 0.0054772 , float sqrt: 0.00547723, finderror: 2.43815e-08
input: 4e-05, fixed sqrt: 0.00632443 , float sqrt: 0.00632456, finderror: 1.26255e-07
input: 5e-05, fixed sqrt: 0.00707086 , float sqrt: 0.00707107, finderror: 2.06055e-07
input: 2, fixed sqrt: 1.41421 , float sqrt: 1.41421, finderror: 1.85149e-09
input: 3.43597e+10, fixed sqrt: 185364 , float sqrt: 185364, finderror: 2.24099e-09