使用查找表计算(x指数0.19029)低内存?

Jer*_*emy 14 c math lookup exponent approximation

我正在为PIC微控制器编写一个C程序,它需要做一个非常具体的指数函数.我需要计算以下内容:

A = k.(1 - (p/p0)^ 0.19029)

k和p0是常数,所以除了找到x ^ 0.19029之外,它们都非常简单

(p/p0)比率总是在0-1范围内.

如果我添加math.h并使用power函数,它会很好用,除了耗尽所有可用的16 kB程序存储器.谈论英国媒体报道!(没有电源功能的其余程序= ~20%闪存使用率;添加math.h和电源功能,= 100%).

我希望该程序还可以做其他一些事情.我想知道我是否可以为x ^ 0.19029编写一个特殊的案例实现,可能涉及迭代和某种查找表.

我的想法是为函数x ^ 0.19029生成一个查找表,在0-1范围内可能有10-100个x值.代码将找到一个紧密匹配,然后(不知何故)通过重新缩放查找表值来迭代地改进它.然而,这是我迷失的地方,因为我的小脑无法想象所涉及的数学.

这种方法有用吗?

或者,我已经看过使用Exp(x)和Ln(x),它可以用泰勒展开来实现.b ^ x可以找到:

b ^ x =(e ^(ln b))^ x = e ^(x.ln(b))

(参见:维基百科 - 通过对数的权力)

不过,这看起来有点棘手和复杂.我可能会比编译器的数学库更小的实现,并且我可以根据我的特殊情况简化它(即base = 0-1,exponent始终为0.19029)?

请注意,此刻RAM使用率还可以,但我在Flash上​​运行不足(用于代码存储).速度并不重要.有人已经建议我使用更大的微处理器和更多的闪存,但这听起来像挥霍无度!

[编辑]当我说"(p/p0)比率总是在0-1"范围内时,我很懒.实际上它永远不会达到0,我昨晚做了一些计算并确定实际上0.3到1的范围就足够了!这意味着下面的一些更简单的解决方案应该是合适的.另外,上面的"k"是44330,我希望最终结果中的误差小于0.1.我猜这意味着(p/p0)^ 0.19029中的误差需要小于1/443300或2.256e-6

Ste*_*fan 15

使用样条线.功能的相关部分如下图所示.它大致类似于第5根,因此有问题的区域接近p / p0 = 0.有数学理论如何最佳地放置花键结以最小化误差(参见Carl de Boor:Splines实用指南).通常,提前以B形式构造样条曲线(使用工具箱,如Matlab的样条工具箱 - 也由C. de Boor编写),然后转换为分段多项式表示以进行快速评估.

在C. de Boor,PGS中,该功能g(x) = sqrt(x + 1)实际上是作为一个例子(第12章,例II).这正是您需要的.这本书回到这个案例几次,因为对于任何插值方案来说,由于无穷大的导数,它无疑是一个难题x = -1.来自PGS的所有软件都可以作为netlib中的 PPPACK免费获得,其中大部分也是SLATEC(也来自netlib)的一部分.

POW

编辑(删除)

(乘以x一次并没有太大帮助,因为它只对一阶导数进行正则化,而所有其他导数x = 0仍然是无限的.)

编辑2

我的感觉是,对于相对较低的精度要求,最佳构造的样条(在de Boor之后)将是最好的(并且最快的).如果准确度要求很高(例如1e-8),人们可能会被迫回到数学家几个世纪以来一直在研究的算法.在这一点上,最好简单地下载glibc和复制的来源(如果GPL是可接受的),无论是什么

glibc-2.19/sysdeps/ieee754/dbl-64/e_pow.c

由于我们不必包括整体math.h,因此内存不应该存在问题,但我们只能从固定指数中获利.

编辑3

以下是来自netlib 的e_pow.c的改编版本,由@Joni发现.这似乎是glibc上面提到的更现代化实施的祖父.旧版本有两个优点:(1)它是公共域,(2)它使用有限数量的常量,如果内存是一个紧张的资源(glibc版本定义超过10000行的常量!),这是有益的.以下是完全独立的代码,其计算x^0.19029用于0 <= x <= 1对双精度(I测试它针对Python的幂函数,发现至多2位不同):

#define __LITTLE_ENDIAN

#ifdef __LITTLE_ENDIAN
#define __HI(x) *(1+(int*)&x)
#define __LO(x) *(int*)&x
#else
#define __HI(x) *(int*)&x
#define __LO(x) *(1+(int*)&x)
#endif

static const double
bp[] = {1.0, 1.5,},
dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */
dp_l[] = { 0.0, 1.35003920212974897128e-08,}, /* 0x3E4CFDEB, 0x43CFD006 */
zero = 0.0,
one = 1.0,
two =  2.0,
two53 = 9007199254740992.0, /* 0x43400000, 0x00000000 */
    /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */
L1  =  5.99999999999994648725e-01, /* 0x3FE33333, 0x33333303 */
L2  =  4.28571428578550184252e-01, /* 0x3FDB6DB6, 0xDB6FABFF */
L3  =  3.33333329818377432918e-01, /* 0x3FD55555, 0x518F264D */
L4  =  2.72728123808534006489e-01, /* 0x3FD17460, 0xA91D4101 */
L5  =  2.30660745775561754067e-01, /* 0x3FCD864A, 0x93C9DB65 */
L6  =  2.06975017800338417784e-01, /* 0x3FCA7E28, 0x4A454EEF */
P1   =  1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */
P2   = -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */
P3   =  6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */
P4   = -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */
P5   =  4.13813679705723846039e-08, /* 0x3E663769, 0x72BEA4D0 */
lg2  =  6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */
lg2_h  =  6.93147182464599609375e-01, /* 0x3FE62E43, 0x00000000 */
lg2_l  = -1.90465429995776804525e-09, /* 0xBE205C61, 0x0CA86C39 */
ovt =  8.0085662595372944372e-0017, /* -(1024-log2(ovfl+.5ulp)) */
cp    =  9.61796693925975554329e-01, /* 0x3FEEC709, 0xDC3A03FD =2/(3ln2) */
cp_h  =  9.61796700954437255859e-01, /* 0x3FEEC709, 0xE0000000 =(float)cp */
cp_l  = -7.02846165095275826516e-09, /* 0xBE3E2FE0, 0x145B01F5 =tail of cp_h*/
ivln2    =  1.44269504088896338700e+00, /* 0x3FF71547, 0x652B82FE =1/ln2 */
ivln2_h  =  1.44269502162933349609e+00, /* 0x3FF71547, 0x60000000 =24b 1/ln2*/
ivln2_l  =  1.92596299112661746887e-08; /* 0x3E54AE0B, 0xF85DDF44 =1/ln2 tail*/

double pow0p19029(double x)
{
    double y = 0.19029e+00;
    double z,ax,z_h,z_l,p_h,p_l;
    double y1,t1,t2,r,s,t,u,v,w;
    int i,j,k,n;
    int hx,hy,ix,iy;
    unsigned lx,ly;

    hx = __HI(x); lx = __LO(x);
    hy = __HI(y); ly = __LO(y);
    ix = hx&0x7fffffff;  iy = hy&0x7fffffff;

    ax = x;
    /* special value of x */
    if(lx==0) {
        if(ix==0x7ff00000||ix==0||ix==0x3ff00000){
            z = ax;           /*x is +-0,+-inf,+-1*/
            return z;
        }
    }

    s = one; /* s (sign of result -ve**odd) = -1 else = 1 */

    double ss,s2,s_h,s_l,t_h,t_l;
    n  = ((ix)>>20)-0x3ff;
    j  = ix&0x000fffff;
    /* determine interval */
    ix = j|0x3ff00000;      /* normalize ix */
    if(j<=0x3988E) k=0;     /* |x|<sqrt(3/2) */
    else if(j<0xBB67A) k=1; /* |x|<sqrt(3)   */
    else {k=0;n+=1;ix -= 0x00100000;}
    __HI(ax) = ix;

    /* compute ss = s_h+s_l = (x-1)/(x+1) or (x-1.5)/(x+1.5) */
    u = ax-bp[k];           /* bp[0]=1.0, bp[1]=1.5 */
    v = one/(ax+bp[k]);
    ss = u*v;
    s_h = ss;
    __LO(s_h) = 0;
    /* t_h=ax+bp[k] High */
    t_h = zero;
    __HI(t_h)=((ix>>1)|0x20000000)+0x00080000+(k<<18); 
    t_l = ax - (t_h-bp[k]);
    s_l = v*((u-s_h*t_h)-s_h*t_l);
    /* compute log(ax) */
    s2 = ss*ss;
    r = s2*s2*(L1+s2*(L2+s2*(L3+s2*(L4+s2*(L5+s2*L6)))));
    r += s_l*(s_h+ss);
    s2  = s_h*s_h;
    t_h = 3.0+s2+r;
    __LO(t_h) = 0;
    t_l = r-((t_h-3.0)-s2);
    /* u+v = ss*(1+...) */
    u = s_h*t_h;
    v = s_l*t_h+t_l*ss;
    /* 2/(3log2)*(ss+...) */
    p_h = u+v;
    __LO(p_h) = 0;
    p_l = v-(p_h-u);
    z_h = cp_h*p_h;         /* cp_h+cp_l = 2/(3*log2) */
    z_l = cp_l*p_h+p_l*cp+dp_l[k];
    /* log2(ax) = (ss+..)*2/(3*log2) = n + dp_h + z_h + z_l */
    t = (double)n;
    t1 = (((z_h+z_l)+dp_h[k])+t);
    __LO(t1) = 0;
    t2 = z_l-(((t1-t)-dp_h[k])-z_h);

    /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */
    y1  = y;
    __LO(y1) = 0;
    p_l = (y-y1)*t1+y*t2;
    p_h = y1*t1;
    z = p_l+p_h;
    j = __HI(z);
    i = __LO(z);
    /*
     * compute 2**(p_h+p_l)
     */
    i = j&0x7fffffff;
    k = (i>>20)-0x3ff;
    n = 0;
    if(i>0x3fe00000) {            /* if |z| > 0.5, set n = [z+0.5] */
        n = j+(0x00100000>>(k+1));
        k = ((n&0x7fffffff)>>20)-0x3ff;      /* new k for n */
        t = zero;
        __HI(t) = (n&~(0x000fffff>>k));
        n = ((n&0x000fffff)|0x00100000)>>(20-k);
        if(j<0) n = -n;
        p_h -= t;
    } 
    t = p_l+p_h;
    __LO(t) = 0;
    u = t*lg2_h;
    v = (p_l-(t-p_h))*lg2+t*lg2_l;
    z = u+v;
    w = v-(z-u);
    t  = z*z;
    t1  = z - t*(P1+t*(P2+t*(P3+t*(P4+t*P5))));
    r  = (z*t1)/(t1-two)-(w+z*w);
    z  = one-(r-z);
    __HI(z) += (n<<20);
    return s*z;
}
Run Code Online (Sandbox Code Playgroud)

显然,50多年的研究已经深入研究,因此可能很难做得更好.(人们必须明白,if在整个算法中有0个循环,只有2个分区,并且只有6个语句!)这样做的原因同样是x = 0所有衍生物分歧的行为,这使得保持控制中的错误:我曾经有一个18节的样条表示很好x = 1e-4,< 5e-4到处都有绝对和相对的错误,但会再x = 1e-5破坏一切.

因此,除非放宽任意接近零的要求,否则我建议使用e_pow.c上面给出的改编版本.

编辑4

既然我们知道域名0.3 <= x <= 1已经足够,并且我们的准确度要求非常低,那么编辑3显然是过度的.正如@MvG已经证明的那样,该函数的表现非常好,7阶多项式足以满足精度要求,可以认为是单个样条段.@ MvG的解决方案可以最大限度地减少积分误差,这已经非常好了.

问题是我们还能做得多好吗?找到给定程度的多项式将最小化感兴趣区间中的最大误差将是有趣的.答案是极小 多项式,可以使用雷米兹"算法,这是Boost库中实现被发现.我喜欢@ MVG的想法在夹紧值x = 11,我会做的一样好.这是minimax.cpp:

#include <ostream>
#define TARG_PREC 64
#define WORK_PREC (TARG_PREC*2)

#include <boost/multiprecision/cpp_dec_float.hpp>
typedef boost::multiprecision::number<boost::multiprecision::cpp_dec_float<WORK_PREC> > dtype;
using boost::math::pow;

#include <boost/math/tools/remez.hpp>
boost::shared_ptr<boost::math::tools::remez_minimax<dtype> > p_remez;

dtype f(const dtype& x) {
    static const dtype one(1), y(0.19029);
    return one - pow(one - x, y);
}

void out(const char *descr, const dtype& x, const char *sep="") {
    std::cout << descr << boost::math::tools::real_cast<double>(x) << sep << std::endl;
}

int main() {
    dtype a(0), b(0.7);   // range to optimise over
    bool rel_error(false), pin(true);
    int orderN(7), orderD(0), skew(0), brake(50);

    int prec = 2 + (TARG_PREC * 3010LL)/10000;
    std::cout << std::scientific << std::setprecision(prec);

    p_remez.reset(new boost::math::tools::remez_minimax<dtype>(
        &f, orderN, orderD, a, b, pin, rel_error, skew, WORK_PREC));
    out("Max error in interpolated form: ", p_remez->max_error()); 

    p_remez->set_brake(brake);

    unsigned i, count(50);
    for (i = 0; i < count; ++i) {
        std::cout << "Stepping..." << std::endl;
        dtype r = p_remez->iterate();
        out("Maximum Deviation Found:                     ", p_remez->max_error());
        out("Expected Error Term:                         ", p_remez->error_term());
        out("Maximum Relative Change in Control Points:   ", r);
    }

    boost::math::tools::polynomial<dtype> n = p_remez->numerator();
    for(i = n.size(); i--; ) {
        out("", n[i], ",");
    }
}
Run Code Online (Sandbox Code Playgroud)

由于我们使用的boost的所有部分都是header-only,因此只需构建:

c++ -O3 -I<path/to/boost/headers> minimax.cpp -o minimax

我们最终得到系数,乘以44330:

24538.3409, -42811.1497, 34300.7501, -11284.1276, 4564.5847, 3186.7541, 8442.5236, 0.

下面的误差图表明这是最好的7度多项式逼近,因为所有极值都是相等的量值(0.06659):

abserr

如果要求发生变化(同时仍然保持远离0!),上面的C++程序可以简单地适应吐出新的最优多项式近似.

  • 如果要实现平方根函数,则可以将结果计算为x ^(8*0.19029)= x ^ 1.52232的八个根.并使用一些查找方法如上所述的电源.这应该将步长斜率重现为零. (2认同)

MvG*_*MvG 8

而不是查找表,我使用多项式近似:

1 - x0.19029≈ - 1073365.91783x 15 + 8354695.40833x 14 - 29422576.6529x 13 + 61993794.537x 12 - 87079891.4988x 11 + 86005723.842x 10 - 61389954.7459x 9 + 32053170.1149x 8 - 12253383.4372x 7 + 3399819.97536x 6 - 672003.142815x 5 + 91817.6782072x 4 - 8299.75873768x 3 + 469.530204564x 2 - 16.6572179869x + 0.722044145701

或者在代码中:

double f(double x) {
  double fx;
  fx =      - 1073365.91783;
  fx = fx*x + 8354695.40833;
  fx = fx*x - 29422576.6529;
  fx = fx*x + 61993794.537;
  fx = fx*x - 87079891.4988;
  fx = fx*x + 86005723.842;
  fx = fx*x - 61389954.7459;
  fx = fx*x + 32053170.1149;
  fx = fx*x - 12253383.4372;
  fx = fx*x + 3399819.97536;
  fx = fx*x - 672003.142815;
  fx = fx*x + 91817.6782072;
  fx = fx*x - 8299.75873768;
  fx = fx*x + 469.530204564;
  fx = fx*x - 16.6572179869;
  fx = fx*x + 0.722044145701;
  return fx;
}
Run Code Online (Sandbox Code Playgroud)

我使用最小二乘法在圣人中计算出来:

f(x) = 1-x^(19029/100000) # your function
d = 16                    # number of terms, i.e. degree + 1
A = matrix(d, d, lambda r, c: integrate(x^r*x^c, (x, 0, 1)))
b = vector([integrate(x^r*f(x), (x, 0, 1)) for r in range(d)])
A.solve_right(b).change_ring(RDF)
Run Code Online (Sandbox Code Playgroud)

这是一个错误的图表:

错误图

蓝色是我的16项多项式的误差,而红色是你从具有16个等距值的分段线性插值得到的误差.正如您所看到的,对于该范围的大多数部分,两个误差都非常小,但是在接近x = 0时会非常大.我实际上修剪了那里的情节.如果您可以以某种方式缩小可能值的范围,则可以将其用作集成的域,并获得更适合相关范围的值.当然,以外面更糟糕的为代价.你也可以增加术语的数量以获得更接近的拟合,尽管这也可能导致更高的振荡.

我想你也可以将这种方法与Stefan发布的方法相结合:使用他将域分成几个部分,然后使用我的方法为每个部分找到一个接近的低次多项式.

更新

由于您更新了问题的规范,关于域和错误,以下是满足这些要求的最小解决方案:

44330(1 - x 0.19029)≈+ 23024.9160933(1-x)7 - 39408.6473636(1-x)6 + 31379.9086193(1-x)5 - 10098.7031260(1-x)4 + 4339.44098317(1-x)3 + 3202.85705860 (1-x)2 + 8442.42528906(1-x)

double f(double x) {
  double fx, x1 = 1. - x;
  fx =       + 23024.9160933;
  fx = fx*x1 - 39408.6473636;
  fx = fx*x1 + 31379.9086193;
  fx = fx*x1 - 10098.7031260;
  fx = fx*x1 + 4339.44098317;
  fx = fx*x1 + 3202.85705860;
  fx = fx*x1 + 8442.42528906;
  fx = fx*x1;
  return fx;
}
Run Code Online (Sandbox Code Playgroud)

我将x从0.293整合到1或等效1 - x从0到0.707,以保持相关域外的最差振荡.我也省略了常数项,以确保x = 1时的精确结果.范围[0.3,1]的最大误差现在出现在x = 0.3260并且等于0.0972 <0.1.这是一个误差图,当然由于比例因子k = 44330(这里包含的因子),它具有比上述更大的绝对误差.

更新错误图

我还可以说该函数的前三个导数将在所讨论的范围内具有常数符号,因此该函数是单调的,凸的,并且通常表现得非常好.