Вычислить (индекс x 0.19029) с низкой памятью с помощью таблицы поиска?

Я пишу программу C для микроконтроллера PIC, которая должна выполнять очень специфическую экспоненциальную функцию. Мне нужно рассчитать следующее:

A = k. (1 - (p/p0) ^ 0,19029)

k и p0 постоянны, поэтому все это довольно просто, кроме поиска x ^ 0.19029

(p/p0) всегда будет находиться в диапазоне 0-1.

Это хорошо работает, если я добавляю в math.h и использую функцию power, за исключением того, что использует всю доступную 16 Кбайт памяти программы. Расскажите о вирусах! (Остальная часть программы без функции питания = ~ 20% использования флеш-памяти, добавьте функцию math.h и power, = 100%).

Я хотел бы, чтобы программа сделала и другие вещи. Мне было интересно, могу ли я написать специальную реализацию случая для x ^ 0.19029, возможно, используя итерацию и какую-то таблицу поиска.

Моя идея состоит в том, чтобы создать справочную таблицу для функции x ^ 0.19029, возможно, 10-100 значений x в диапазоне 0-1. Код найдет близкое совпадение, затем (каким-то образом) итеративно уточнит его, повторно масштабируя значения таблицы поиска. Тем не менее, здесь я теряюсь, потому что мой крошечный мозг не может визуализировать математику.

Может ли этот подход работать?

В качестве альтернативы я рассмотрел использование Exp (x) и Ln (x), которые могут быть реализованы с расширением Тейлора. b ^ x можно найти с помощью:

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

(См.: Википедия - полномочия через логарифмы)

Это выглядит немного сложным и сложным для меня. Возможно ли, чтобы реализация была меньше, чем математическая библиотека компилятора, и могу ли я упростить ее для моего особого случая (т.е. Base = 0-1, показатель всегда 0.19029)?

Обратите внимание, что в настоящий момент использование ОЗУ в порядке, но на Flash (для хранения кода) я мало работал. Скорость не критична. Кто-то уже предположил, что я использую более крупный микрофон с большим количеством флэш-памяти, но это звучит как расточительная расточительность!

[EDIT] Я ленился, когда сказал, что соотношение "(p/p0) всегда будет в диапазоне 0-1". На самом деле он никогда не достигнет 0, и я сделал некоторые расчеты прошлой ночью и решил, что на самом деле диапазон 0,3 - 1 будет вполне адекватным! Это означает, что некоторые из более простых решений, приведенных ниже, должны быть подходящими. Кроме того, "k" в приведенном выше примере - 44330, и я хотел бы, чтобы ошибка в конечном результате была меньше 0,1. Я предполагаю, что это означает, что ошибка в (p/p0) ^ 0.19029 должна быть меньше 1/443300 или 2.256e-6

Ответы

Ответ 1

Используйте сплайны. Соответствующая часть функции показана на рисунке ниже. Он изменяется примерно как 5-й корень, поэтому проблемная зона близка к p / p0 = 0. Существует математическая теория о том, как оптимально разместить узлы сплайнов, чтобы свести к минимуму ошибку (см. Carl de Boor: Практическое руководство по сплайнам). Обычно одна конструкция сплайна в форме В заранее (с помощью наборов инструментов, таких как набор инструментов сплайна Matlab, также написанный C. de Boor), затем преобразуется в кусочно-полиномиальное представление для быстрой оценки.

В C. de Boor, PGS, функция g(x) = sqrt(x + 1) фактически взята в качестве примера (глава 12, пример II). Это именно то, что вам нужно здесь. Книга возвращается к этому случаю несколько раз, так как это, по общему признанию, трудная проблема для любой интерполяционной схемы из-за бесконечных производных в x = -1. Все программное обеспечение от PGS доступно бесплатно как PPPACK в netlib, и большая часть его также является частью SLATEC (также из netlib).

pow

Изменить (удалено)

(Умножение на x некорректно помогает, поскольку оно только упорядочивает первую производную, а все остальные производные в x = 0 все еще бесконечны.)

Изменить 2

Я чувствую, что оптимально сконструированные сплайны (после де Бура) будут лучше (и быстрее) для относительно низких требований к точности. Если требования к точности высоки (например, 1е-8), можно заставить вернуться к алгоритмам, которые математики изучали на протяжении веков. На этом этапе лучше всего просто загрузить источники glibc и скопировать (если GPL является приемлемым), что находится в

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

Поскольку нам не нужно включать в себя все math.h, проблема с памятью не должна быть проблемой, но мы будем иметь лишь незначительную прибыль от наличия фиксированного показателя.

Изменить 3

Вот адаптированная версия e_pow.c из netlib, как найдено @Joni. Это, кажется, дед более современной реализации, упомянутой выше. Старая версия имеет два преимущества: (1) Это общедоступный домен и (2) он использует ограниченное число констант, что полезно, если память является ограниченным ресурсом (glibc версия определяет более 10000 строк констант!). Ниже приведен полностью автономный код, который вычисляет x^0.19029 для 0 <= x <= 1 для двойной точности (я тестировал его на функцию мощности Python и обнаружил, что не более двух бит отличается):

#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;
}

Понятно, что прошло более 50 лет исследований, поэтому, вероятно, очень трудно сделать что-то лучше. (Нужно понимать, что существует 0 циклов, всего 2 деления и всего 6 if операторов во всем алгоритме!). Причина этого - опять-таки поведение в x = 0, где все производные расходятся, что делает очень сложно держать ошибку под контролем: у меня когда-то было сплайн-представление с 18 узлами, которое было до x = 1e-4, с абсолютными и относительными ошибками < 5e-4 повсюду, но переход к x = 1e-5 снова разрушил все.

Итак, если требование о сколь угодно близком к нулю ослаблении, я рекомендую использовать приведенную выше адаптированную версию e_pow.c.

Изменить 4

Теперь, когда мы знаем, что область 0.3 <= x <= 1 является достаточной, и что у нас очень низкие требования к точности, Edit 3 явно переполняется. Как продемонстрировал @MvG, функция настолько хорошо себя ведет, что полином степени 7 достаточен для удовлетворения требований точности, который можно рассматривать как один сплайн-сегмент. Решение @MvG минимизирует интегральную ошибку, которая уже выглядит очень хорошо.

Возникает вопрос, насколько мы можем еще лучше справиться? Было бы интересно найти многочлен заданной степени, который минимизирует максимальную ошибку в интересующем интервале. Ответ: minimax полином, который можно найти с помощью алгоритма Ремеза, который реализован в библиотеке Boost. Мне нравится идея @MvG, чтобы закрепить значение в x = 1 до 1, что я также буду делать. Здесь 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], ",");
    }
}

Поскольку все части boost, которые мы используем, имеют только заголовок, просто создайте с помощью:

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++ может быть просто адаптирована для выплескивания нового оптимального приближения полиномов.

Ответ 2

Вместо таблицы поиска я бы использовал полиномиальное приближение:

1 - x 0.19029 ≈ - 1073365.91783x 15 + 8354695.40833x 14 - 29422576.6529x 13 + 61993794.537 x 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;
}

Я вычислил это в мудреце, используя метод наименьших квадратов:

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)

Вот график ошибки, которая повлечет за собой:

Error plot

Синий - это ошибка из моего 16-членного полинома, а красный - ошибка, которую вы получите от кусочно-линейной интерполяции с 16 эквидистантными значениями. Как вы можете видеть, обе ошибки довольно малы для большинства частей диапазона, но станут действительно огромными рядом с x = 0. Я на самом деле обрезал сюжет. Если вы можете каким-то образом ограничить диапазон возможных значений, вы можете использовать это как домен для интеграции и получить еще лучшее соответствие для соответствующего диапазона. Разумеется, из-за того, что он хуже подходит снаружи. Вы также можете увеличить количество терминов, чтобы получить более близкую посадку, хотя это также может привести к более высоким колебаниям.

Я думаю, вы также можете объединить этот подход с одним опубликованным Stefan: используйте его, чтобы разделить домен на несколько частей, а затем используйте мой, чтобы найти полином с близким низким уровнем для каждой части.

Update

Поскольку вы обновили спецификацию своего вопроса, касаясь как домена, так и ошибки, вот минимальное решение, соответствующее этим требованиям:

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;
}

Я интегрировал x от 0.293 до 1 или эквивалентно 1 - x от 0 до 0.707, чтобы сохранить худшие колебания вне соответствующего домена. Я также опустил постоянный член, чтобы обеспечить точный результат при x = 1. Максимальная погрешность для диапазона [0,3, 1] теперь имеет место при x = 0,3260 и составляет 0,0972 0,1. Вот график ошибок, который, конечно, имеет более высокие абсолютные ошибки, чем приведенные выше, из-за включенного здесь масштабного коэффициента k = 44330.

Error plot for update

Могу также утверждать, что первые три производные функции будут иметь постоянный знак над рассматриваемым диапазоном, поэтому функция монотонна, выпукла и, в общем, довольно хорошо себя ведет.

Ответ 3

Не предназначен для ответа на вопрос, но он иллюстрирует "Дорога не идти" и, следовательно, может быть полезен:

Этот быстрый и грязный код C вычисляет pow(i, 0.19029) за 0.000-1000 с шагом 0,01. Первая половина отображает ошибку, в процентах, при сохранении в виде 1/65536th (как это теоретически дает чуть более 4 десятичных значений точности). Вторая половина показывает как интерполированные, так и вычисленные значения с шагом 0,001, а разница между этими двумя.

Это выглядит нормально, если вы читаете снизу вверх, все 100 и 99.99s там, но около первых 20 значений от 0.001 до 0.020 бесполезны.

#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;
}

Ответ 4

Вы можете найти полную, правильную автономную реализацию pow в fdlibm. Это около 200 строк кода, около половины из которых касаются особых случаев. Если вы удалите код, который касается особых случаев, вас это не интересует. Я сомневаюсь, что у вас будут проблемы, включая его в вашей программе.

Ответ 5

Ответ LutzL действительно хорош. Рассчитайте свою мощность как (x ^ 1.52232) ^ (1/8), вычисляя внутреннюю мощность с помощью сплайновой интерполяции или другого метода. Восьмой корень имеет дело с патологическим недифференцируемым поведением вблизи нуля. Я взял на себя смелость осуществить эту реализацию. Тем не менее, ниже выполняется только линейная интерполяция x ^ 1.52232, и вам нужно будет получить полные коэффициенты, используя ваши любимые инструменты численной математики. Вы добавите почти 40 строк кода, чтобы получить необходимую мощность, а также множество узлов, которые вы решили использовать для своего сплайна, как указано вашей необходимой точностью.

Не бойтесь #include <math.h>; это просто для бенчмаркинга кода.

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

double my_sqrt(double x) {
  /* Newton 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 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;
}

EDIT: Если вас интересует небольшая область, например [0,1], еще проще удалить один sqrt (x) и вычислить x ^ 1.02232, что довольно хорошо себя ведет с использованием серии Тейлора:

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;
}

Это дает вам в пределах 1% от точной мощности примерно для [0.1,6], хотя получение сингулярности в точности правильное всегда является проблемой. Тем не менее, эта трехступенчатая серия Тейлора дает вам в пределах 2,3% при x = 0,001.