三角関数・対数関数・指数関数の高速計算法


三角関数・対数関数・指数関数を高速に計算するコードを作成しました。いずれも,関数の変数の変域を小さい範囲に収めることにより,テーラー展開 の項の数を減らして高速化したものです。


三角関数
2倍角の公式を用いて三角関数を高速に計算する方法を考えました。c言語のdouble型で効果があります。 解説 cos.pdf
以下は,c言語でcosθを求める関数のコードで,math.hの三角関数より高速です。角の範囲が -2π≦θ≦2π ならば,誤差の問題はありません。
また,若干遅くなりますが、コメントアウトした行をコードに組み込めば、任意の角についても求められます。
sinθを求めるには,piを円周率として,cos_fast(x-pi/2) または cos_fast(pi/2-x) により求めるのがよいでしょう。

//高速三角関数
#define PAI 3.14159265358979323846
double cos_fast(double x)
{
    static double waru[5]={1.0/(3*4*5*6*7*8*9*10),-1.0/(3*4*5*6*7*8),1.0/(3*4*5*6),-1.0/(3*4),1.0};
    double y,*p=waru;
    int i,q;
    //q=(int)(x/(2.0*PAI));
    //x=x-q*(2.0*PAI);
    x=x/32.0;
    x=x*x;
    y=-1.0/(3*4*5*6*7*8*9*10*11*12);
    do{
        y=y*x+(*p);
        p++;
    }while (p<waru+5);
    y=y*x;
    for (i=0;i<5;i++) y=y*(4.0-y);
    return 1.0-y/2.0;
}

double sin_fast(double kaku) {return cos_fast(kaku-PAI/2);}
double tan_fast(double kaku) {return cos_fast(kaku-PAI/2)/cos_fast(kaku);}
#undef PAI


対数関数
以下は,c言語で log(x)  を求める関数のコードで,関数値のテーブル引きを用いてテーラー展開の項の数を減らしました。math.hの対数関数より高速です。x=1の近傍での相対 誤差を少なくする工夫をしました。速度とメモリはトレードオフであり,テーブルのデータ数を増やせばさらに高速化できます。
実装では,まず,double型を指数部と仮数部に分離し,仮数部 1≦w<2 に対して logw の値を求めます。
logx のマクローリン展開はh=(x-z)/(x+z)として
    log(x/z)=2(h+h3/3+h5/5+h7/7+h9/9 +…)
となることを用いています。

//高速対数関数
#define LOG2   0.693147180559945309417  //log(2)

double log_fast(double x)
{
    static double table[17]={
        .0                       ,  // log( 16 /16)
        .0606246218164348425806  ,  // log( 17 /16)
        .1177830356563834545387  ,  // log( 18 /16)
        .17185025692665922234    ,  // log( 19 /16)
        .2231435513142097557662  ,  // log( 20 /16)
        .2719337154836417588316  ,  // log( 21 /16)
        .3184537311185346158102  ,  // log( 22 /16)
        .3629054936893684531378  ,  // log( 23 /16)
        .405465108108164381978   ,  // log( 24 /16)
        .4462871026284195115325  ,  // log( 25 /16)
        .4855078157817008078017  ,  // log( 26 /16)
        .5232481437645478365168  ,  // log( 27 /16)
        .5596157879354226862708  ,  // log( 28 /16)
        .5947071077466927895143  ,  // log( 29 /16)
        .6286086594223741377443  ,  // log( 30 /16)
        .6613984822453650082602  ,  // log( 31 /16)
        .6931471805599453094172  ,  // log( 32 /16)
    };  
    unsigned long long w,kasuu16;
    int q;
    double y,h,z;
    w=*(unsigned long long*)&x;
    q=(((int)(w>>47)&0x1F)+1)>>1;
    kasuu16=(w & 0xFFFFFFFFFFFFFULL)^0x4030000000000000ULL;//仮数*16  16<=kasuu16<32
    h=*(double*)&kasuu16;
    z=(double)(q+16);
    h=(h-z)/(h+z);
    z=h*h;
    y=(2.0/9)*z+2.0/7;
    y=y*z+2.0/5;
    y=y*z+2.0/3;
    y=y*z+2.0;
    y=y*h;
    return ((int)(w>>52)-1023)*LOG2+table[q]+y;
}
#undef LOG2


指数関数
以下は,c言語で exp(x)  を求める関数のコードで,関数値のテーブル引きを用いてテーラー展開の項の数を減らしました。math.hの指数関数より高速です。

//高速指数関数
#define LOG2   0.693147180559945309417   //log(2)

double exp_fast(double x)
{
    static double waru[7]={1.0/(2*3*4*5*6),1.0/(2*3*4*5),1.0/(2*3*4),1.0/(2*3),1.0/2,1.0,1.0};
    static unsigned long long table[16]={
       0x059B0D3158574ull,  // 2^( 1 /32)-1
       0x11301D0125B51ull,  // 2^( 3 /32)-1
       0x1D4873168B9AAull,  // 2^( 5 /32)-1
       0x29E9DF51FDEE1ull,  // 2^( 7 /32)-1
       0x371A7373AA9CBull,  // 2^( 9 /32)-1
       0x44E086061892Dull,  // 2^( 11 /32)-1
       0x5342B569D4F82ull,  // 2^( 13 /32)-1
       0x6247EB03A5585ull,  // 2^( 15 /32)-1
       0x71F75E8EC5F74ull,  // 2^( 17 /32)-1
       0x82589994CCE13ull,  // 2^( 19 /32)-1
       0x93737B0CDC5E5ull,  // 2^( 21 /32)-1
       0xA5503B23E255Dull,  // 2^( 23 /32)-1
       0xB7F76F2FB5E47ull,  // 2^( 25 /32)-1
       0xCB720DCEF9069ull,  // 2^( 27 /32)-1
       0xDFC97337B9B5Full,  // 2^( 29 /32)-1
       0xF50765B6E4540ull,  // 2^( 31 /32)-1
       };
    double y=1.0/(2*3*4*5*6*7),*p=waru,z,r;
    int q;
    unsigned long long w;
    z=x*(16.0/LOG2);
    q=(int)z-(x<0);
    r=x-((q<<1)+1)*(LOG2/32.0);
    w=(unsigned long long)(1023+(q>>4))<<52 ^ table[q & 0xF];
    z=*(double*)&w;
    do{
        y=y*r+(*p);
        p++;
    }while (p<waru+7);
    return y*z;
}
#undef LOG2


余弦・正弦関数
以下は,c言語でcosθ,sinθを同時に求める関数のコードで,速度優先で作りました。角の範囲が -π≦θ≦π ならば,誤差の問題はありません。

//高速余弦正弦関数
void sincos(double kaku,double *x,double *y)
{
    static double waru[8]={-1.0/(3*4*5*6*7*8),-1.0/(2*3*4*5*6*7),1.0/(3*4*5*6),1.0/(2*3*4*5),-1.0/(3*4),-1.0/(2*3),1.0,1.0};
    double c=1.0/(3*4*5*6*7*8*9*10),s=1.0/(2*3*4*5*6*7*8*9),*p=waru,z;
    int i;
    kaku=kaku/32.0;
    z=kaku*kaku;
    do{
        c=c*z+(*p);
        p++;
        s=s*z+(*p);
        p++;
    }while (p<waru+8);
    c=c*z;
    s=s*kaku;
    for (i=0;i<5;i++){
        s=s*(2.0-c);
        c=c*(4.0-c);
    }
    *x=1.0-c/2.0;
    *y=s;  
}


ボックス・ミュラー法への応用 (Box–Muller transform)    seiki.c
ボックス・ミュラー法は,[0,1) の実数の一様乱数から,正規分布(ガウス分布)に従う擬似乱数を生成するのに応用される方法です。 解説 逆関数法とボックス・ミュラー法
高速な三角関数と対数関数のルーチンを用いることにより,math.hの組み込み関数を利用するときと比べて2倍ほど高速に正 規分布の乱数を発生できます。Box–Muller法のcos2πt,sin2πt の部分を三角関数を使わずに済ますPolar法も組み込んでありますから,速度を比較してみて下さい。
乱数は平均が0,標準偏差が1の正規分布に従 いますので,平均m,標準偏差sの正 規分布の乱数を得るには,s*ransuu_seiki()+m として下さい。
なお,カイ2乗分布,指数分布,ポアソン分布,二項分布も組み込んであります。

最初のページ に戻る