自然対数、指数関数 を実装

小さなマイコン(STM32F0シリーズ)向けの仕事をしていて、log を求める必要が出てきたのですが、mathライブラリを使ったら 容量オーバーとなってしまったので、自前で実装してみました。ついでに exp と pow も掲載。
もちろん三角関数やら何やらと発展させていくならmathライブラリを使えばよいのでそこまでやらない。

使用マイコンの都合で、IEEE754単精度のみの実装です。内部含めて単精度なので精度はそれなりです。


プログラム

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

#define FLT_NAN         (*(float*)(&_flt_nan__))
#define FLT_INF         (*(float*)(&_flt_inf_positive__))
#define IsNan(_value)   ((*(uint32_t*)&_value)==_flt_nan__)
#define IsInf(_value)   (((*(uint32_t*)&_value)&0x7fffffff) == _flt_inf_positive)

uint32_t _flt_nan__ = 0x7fffffffL;
uint32_t _flt_inf_positive__ = 0x7f800000L;
uint32_t _flt_inf_negative__ = 0xff800000L;

#define LOG2    (0.6931471805599453094172321214581765680755f)
#define SQRT2    (1.4142135623737f)

/**
 * @brief calculate the exponential.
 * @param [in] x   input value of exp(x)
 * @return the result of exp(x)
 * @note Taylor deployment of exp function.
 */
float _expf(float x) {
    int i, k, negative = 0;
    float a, e, prev;

    //  make x as -ln(2) < x < ln(2)
    k = (int)(x / LOG2 + (x >= 0) ? 0.5f : -0.5f);
    x -= k * LOG2;

    if (x < 0) {
        negative = 1;
        x = -x;
    }

    e = 1 + x;
    a = x;
    i = 2;
    do {
        a *= x / (i++);
        prev = e;
        e += a;
    } while (a > FLT_MIN);
    if (negative != 0)
        e = 1 / e;
    return _ldexpf(e, k);
}

/**
 * @brief personal version of ldexp
 * @param [in] x value x
 * @param [in] exp
 * @result   x * 2^exp
 */
float    _ldexpf(float x, int exp) {
    float    twos_exponent;
    if (exp == 0)
        return    x;
    exp += 127;
    if (exp >= 255 || exp <= 0)
        return    FLT_NAN;
    exp <<= 23;
    twos_exponent = *(float*)(&exp);
    return    x * twos_exponent;
}

/**
 * @brief separate the  single real value as fraction and exponent
 */
float _frexpf(float x, int *exp) {
    uint32_t    val = *(uint32_t*)(&x);
    int    e = (val & 0x7f800000L) >> 23;
    uint32_t    frac = val & 0x7fffffL;
    if (e == 0) {
        *exp = 0;
        return    *(float*)(&val);
    }
    else if (e == 255) {
        *exp = 0;
        return    FLT_NAN;
    }
    else {
        e -= 127;
        *exp = e + 1;
        uint32_t    result = frac | (val & 0x80000000L);
        result |= ((-1) + 127) << 23;
        return    *(float*)&result;
    }
}

/**
 * @brief personal version of fabs
 * @param [in] x value x
 * @return absolute value of x
 */
float    _fabsf(float x) {
    uint32_t    value = *(uint32_t*)(&x);
    value &= 0x7fffffffL;    //    make sign bit to zero
    return *(float*)(&value);
}


/**
 * @brief calculate ln
 * @param [in] x   input value of ln(x)
 * @return the result of ln(x)
 * @note Euler transform of Taylor deployment of log function.
 */
float    _logf(float x) {
    int    ix, k;
    float    u, u2, s, prev;
    if (x <= 0)
        return    FLT_NAN;    //  error value
    //  make x over than -1 and under than 1.0 to prevent emerging
    _frexpf(x / SQRT2, &k);   //  k = (int)(log2(y)-0.5) (we decrease the exponent by 0.5 to make easy to converge.)
    x /= _ldexpf(1.0f, k);    //  x = x / 2^k;
    u = (x - 1) / (x + 1);    //  get log u with u = (x - 1) / (x + 1)
    u2 = u * u;
    ix = 1;
    s = u;
    do {
        u *= u2;
        ix += 2;
        prev = s;
        s += u / ix;
    } while (_fabsf(prev - s) > FLT_MIN);
    return LOG2 * k + 2 * s;
}


/**
* @brief  calculate power of x^y
* @param [in] x  number to be raised
* @param [in] y  power of x^y 
* @return   y th power of x
*/
float _powf(float x, float y) {

    if (y == (int)y) {
        float r = 1;
        int n = (int)((y > 0) ? y : (-y));
        while (n != 0) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        if (y < 0)
            r = 1 / r;
        return    r;
    }
    if (x > 0) {
        return    _expf(y*_logf(x));
    }
    else if (x != 0 || y <= 0) {
        return FLT_NAN;
    }
    return    0;
}

#if defined(_WIN32) || defined(_WIN64)
/**
* @brief  Test program
*/
int main() {
    float    val;
    float    frac;
    for (int j = 0; j < 5; ++j) {
        for (int i = 0; i <= 20; ++i) {
            val = (float)i / 3.3f;
            frac = (float)j / 2.2f;
            printf("%.2f : %.2f : %.4f : %.4f\n", frac, val, powf(frac, val), _powf(frac, val));
        }
    }
    getchar();

}
#endif

 

 

_expf(float x)exp(x)を求める
_ldexpf(float x, int k)x * 2^k を求める
_frexpf(float x, int *exp)与えられた実数を、仮数部(0~0.5)と指数部に分解する
_fabsf(float x)与えられた実数の絶対値を求める
_logf(float x)与えられた実数の自然対数を求める
_powf(float x, float y) x^y を求める
※_expf は単独実装可能。 _logfは内部で、_ldexpf / _frexpf / _fabsf を使用してます。 _powf は内部で全て使ってます。

 

 

【参考文献】

C言語による最新アルゴリズム辞典 ・・・ 奥村晴彦著
理工系の数学入門コース1 微分積分 ・・・ 和達三樹著
Apple Numerics Manual Second Edition ・・・ Apple Computer.