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

C 使用查找表以低内存计算(x指数0.19029)?,c,math,lookup,exponent,approximation,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并使用幂函数,它会工作得很好,只是会耗尽所有可用的16 kB程序内存。说说臃肿的东西!(没有幂函数的程序的剩余部分=~20%的闪存使用率;加上math.h和幂函数,=100%) 我希望这个程序也能做一些其他的事情。我想知道是否可以为x^

我正在为PIC微控制器编写一个C程序,它需要做一个非常特殊的指数函数。我需要计算以下各项:

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

k和p0是常数,所以除了找到x^0.19029之外,这一切都很简单

(p/p0)比率始终在0-1范围内

如果我加上math.h并使用幂函数,它会工作得很好,只是会耗尽所有可用的16 kB程序内存。说说臃肿的东西!(没有幂函数的程序的剩余部分=~20%的闪存使用率;加上math.h和幂函数,=100%)

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

我的想法是为函数x^0.19029生成一个查找表,其中x的值可能为10-100,范围为0-1。代码将找到一个接近的匹配项,然后(以某种方式)通过重新缩放查找表值来迭代地优化它。然而,这就是我迷路的地方,因为我小小的大脑无法想象所涉及的数学

这种方法行得通吗

另外,我还研究了使用Exp(x)和Ln(x),这可以通过泰勒展开来实现。b^x可以通过以下方式找到:

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

(见:)

不过,对我来说,这看起来有点棘手和复杂。我是否可能得到比编译器的数学库更小的实现,是否可以针对我的特殊情况简化它(即base=0-1,指数始终为0.19029)

请注意,RAM的使用目前还可以,但我的闪存(用于代码存储)已经用完了。速度不是关键。有人已经建议我使用更大的微型机和更多的闪存,但这听起来像是浪费


[编辑]当我说“(p/p0)比率将始终在0-1范围内时,我很懒。事实上,它永远不会达到0,我昨晚做了一些计算,决定事实上0.3-1的范围就足够了!这意味着下面一些简单的解决方案应该是合适的。另外,上面的“k”是44330,我希望最终结果中的误差小于0.1。我猜这意味着(p/p0)^0.19029中的错误需要小于1/443300或2.256e-6,这并不意味着回答问题,但它说明了不可走的路,因此可能有帮助:

此快速脏C代码以0.01的步长计算0.000到1.000的功率(i,0.19029)。当存储为1/65536ths时,前半部分以百分比为单位显示误差(理论上,精度略高于4位小数)。下半部分以0.001的步长显示插值值和计算值,以及两者之间的差值

如果你从下往上看,所有的100和99.99看起来都没问题,但是从0.001到0.020的前20个值是没有价值的

#包括
#包括
浮动权力[102];
内部主(空)
{
int i,as_int;
双倍于真实值、低值、高值、增量、近似值、计算值、差值;
printf(“计算和存储:\n”);

对于(i=0;i使用样条曲线。函数的相关部分如下图所示。它的变化近似于第5个根,因此问题区域接近
p/p0=0
。有数学理论说明如何最佳地放置样条曲线的节点以最小化误差(参见卡尔·德布尔:《样条曲线实用指南》)。通常,人们会提前构造B形式的样条曲线(使用工具箱,如Matlab的样条曲线工具箱-也是由C.de Boor编写的),然后将其转换为分段多项式表示以进行快速计算

在C.de Boor,PGS中,函数
g(x)=sqrt(x+1)
实际上被作为一个例子(第12章,示例二)。这正是您在这里需要的。本书多次提到这种情况,因为对于任何插值方案来说,由于
x=-1
处的无穷导数,这无疑是一个困难的问题。PGS的所有软件都以PPPACK in的形式免费提供,其中大部分也是SLATEC的一部分(也来自netlib)

编辑(已删除)

(乘以
x
一次并没有显著的帮助,因为它只正则化一阶导数,而
x=0
处的所有其他导数仍然是无限的。)

编辑2

我的感觉是,对于相对较低的精度要求,最佳构造的样条曲线(遵循de Boor)将是最好的(也是最快的)。如果精度要求较高(比如1e-8),人们可能会被迫回到数学家们研究了几个世纪的算法上。此时,最好只需下载
glibc
的源代码,然后复制(只要GPL是可接受的)其中的任何内容

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

因为我们不必包含整个
math.h
,所以内存应该不会有问题,但我们只会从固定指数中获得一点点好处

编辑3


这是@Joni发现的from netlib的一个改编版本。这似乎是上文提到的
glibc
更现代的实现的前身。旧版本有两个优点:(1)它是公共域,并且(2)它使用有限数量的常量,如果内存资源紧张,这是有益的(
glibc
的版本定义了10000多行常量!)。以下是完全独立的代码,它为
0计算
x^0.19029
,而不是查找表,我将使用多项式近似:

1-x0.19029≈ - 1073365.91783x15+8354695.40833x14-29422576.6529x13+61993794.537x12-87079891.4988x11+86005723.842x10-61389954.7459x9+32053170.1149x8-12253383.4372x7+3399819.97536x6-672003.142815x5+91817.6782072x4-8299.75873768x3+469.5302044x2-16.6572179869x+0.72
#include <stdio.h>
#include <math.h>

float powers[102];

int main (void)
{
    int i, as_int;
    double as_real, low, high, delta, approx, calcd, diff;

    printf ("calculating and storing:\n");

    for (i=0; i<=101; i++)
    {
        as_real = pow(i/100.0, 0.19029);
        as_int = (int)round(65536*as_real);
        powers[i] = as_real;

        diff = 100*as_real/(as_int/65536.0);
        printf ("%.5f %.5f %.5f ~ %.3f\n", i/100.0, as_real, as_int/65536.0, diff);
    }

    printf ("\n");
    printf ("-- interpolating in 1/10ths:\n");

    for (i=0; i<1000; i++)
    {
        as_real = i/1000.0;

        low = powers[i/10];
        high = powers[1+i/10];

        delta = (high-low)/10.0;
        approx = low + (i%10)*delta;

        calcd = pow(as_real, 0.19029);
        diff = 100.0*approx/calcd;

        printf ("%.5f ~ %.5f = %.5f +/- %.5f%%\n", as_real, approx, calcd, diff);
    }

    return 0;
}
#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;
}
#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], ",");
    }
}
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;
}
#include <stdio.h>
#include <math.h>

double my_sqrt(double x) {
  /* Newton's method for a square root. */
  int i = 0;
  double res = 1.0;

  if (x > 0) {
    for (i = 0; i < 10; i++) {
      res = 0.5 * (res + x / res);
    }
  } else {
    res = 0.0;
  }

  return res;
}

double my_152232(double x) {
  /* Cubic spline interpolation for x ** 1.52232. */
  int i = 0;
  double res = 0.0;
  /* coefs[i] will give the cubic polynomial coefficients between x =
     i and x = i+1. Out of laziness, the below numbers give only a
     linear interpolation.  You'll need to do some work and research
     to get the spline coefficients. */

  double coefs[3][4] = {{0.0, 1.0, 0.0, 0.0},
            {-0.872526, 1.872526, 0.0, 0.0},
            {-2.032706, 2.452616, 0.0, 0.0}};

  if ((x >= 0) && (x < 3.0)) {
    i = (int) x;
    /* Horner's method cubic. */
    res = (((coefs[i][3] * x + coefs[i][2]) * x) + coefs[i][1] * x) 
      + coefs[i][0];
  } else if (x >= 3.0) {
    /* Scaled x ** 1.5 once you go off the spline. */
    res = 1.024824 * my_sqrt(x * x * x);
  }

  return res;
}

double my_019029(double x) {
  return my_sqrt(my_sqrt(my_sqrt(my_152232(x))));
}

int main() {
  int i;
  double x = 0.0;

  for (i = 0; i < 1000; i++) {
    x = 1e-2 * i;
    printf("%f %f %f \n", x, my_019029(x), pow(x, 0.19029));
  }

  return 0;
}
double my_152232(double x) {
  double part_050000 = my_sqrt(x);
  double part_102232 = 1.02232 * x + 0.0114091 * x * x - 3.718147e-3 * x * x * x;

  return part_102232 * part_050000;
}