デジタル信号処理では、カイザー窓の計算などに0次の第1種修正ベッセル関数 I0 がよく使われますが、 制御変数 αが大きくなってくると I0 の値が指数関数的に大きくなって精度が低下し、 計算誤差がサイドローブ特性を悪化させることがあります。 そこで、I0 関数のいくつかの実装を比較し、80ビット拡張倍精度関数として実装した場合の誤差を調べました。
多くの I0 実装では、list.1の Formula Used にあるように、ある閾値で式を区切り、 多項式の係数をチェビシェフやパデ近似で最適化しています。 しかし、その倍精度計算への最適化が、拡張倍精度で I0 を計算したい場合には裏目に出てしまいます。
list.1 [ besi0q.c ]
Formulas used
where
P32(x), P45(x) and PQ3(x) are polynomials of corresponding degrees.
相対誤差の比較
- gsl_sf_bessel_I0(x) (64-bit double) : GSL special functions Rev. 2.7.1 [4]
- nr3_i0(x) (80-bit extended-double) : Based on Numerical Recipes Third edition [5]
- _besi0p(x) (80-bit extended-double) : Based on Holoborodko's I0(x) of Double precision [1]
- _besi0q(x) (80-bit extended-double) : Based on Holoborodko's I0(x) of Quadruple precision [1]
上記計算範囲での最大誤差 (0 ≤ x ≤ 40.0) gsl_sf_bessel_I0 : 8.182e-16 (x = 19.0859375) nr3_i0 : 5.534e-16 (x = 15.0009765625) _besi0p : 6.353e-17 (x = 33.8134765625) _besi0q : 2.494e-18 (x = 38.1123046875)Note. gsl_sf_bessel_I0(x) overflows when x > 708.78271289338391628L.
I0(x) は x = 0 付近と x = ∞ 付近で無限級数として表現できるので、 2つの無限級数計算を切り替えるポイントを適切に選択すれば、 計算時間の多少の増加で直接計算で拡張倍精度の値を得ることができます。
list.2 [ besi0l.h, besi0l.c ]
NAME besi0l(x) the 0th-order modified Bessel function of the first kind I0 SYNOPSIS #include <math.h> #include <float.h> long double besi0l(long double x); RETURN VALUE besi0l(x) returns the 0th-order modified Bessel function of the first kind of real argument x.
Formulas used
Definition :
Approximation :
![]()
![]()
Download besi0l-1.4.3.tar.gz
— source code archive
相対誤差の比較(besi0l の無限級数部)
- _besi0q(x) (80-bit extended-double) : Based on Holoborodko's I0(x) of Quadruple precision [1]
- _besi0l(x) (80-bit extended-double, SX_CTRL = 128.0L)
上記計算範囲での最大誤差 (0 ≤ x ≤ 128.0) _besi0q : 7.373e-18 (x = 125.7890625) _besi0l : 4.879e-19 (x = 68.6015625)
計算速度の比較 _besi0l : 1283 [ms] (0 ≤ x ≤ 15.0, ittr = 10000000) _besi0q : 704 [ms] (0 ≤ x ≤ 15.0, ittr = 10000000) _besi0l : 1246 [ms] (0 ≤ x ≤ 128.0, ittr = 10000000) _besi0q : 1227 [ms] (0 ≤ x ≤ 128.0, ittr = 10000000)x がそれほど大きくない場合、I0(x) は無限級数を使って計算することができます。 無限級数加算におけるアンダーフローの影響を軽減するため、list.2 の _besi0l() の実装では、加算を主要部とマイナー部に分けています。 x が 64以下であれば I0(x) は 81回以下の繰り返しで計算できるので、 list.3 のように 81 * (3mul + 1div) の計算が浮動小数点ユニットで行われて、 コードがレベル1キャッシュの小さな部分に収まるのであれば、それは最新のプロセッサではそれほど悪いことではありません。 カイザー窓への応用という点では、 0 ≤ x ≤ 20π < 64 が α < 20 に相当し、ほとんどの実用的な目的には十分です。
list.3 [ 内側ループのアセンブリコード例 ]
L25: fxch %st(2) .L9: movl %eax, -128(%rsp) addl $1, %eax fildl -128(%rsp) fdivr %st(4), %st fmul %st(0), %st fmulp %st, %st(2) fxch %st(2) fadd %st(1), %st fld %st(0) fmul %st(3), %st fcomip %st(2), %st jb .L25
Relative error comparison (besi0l polynomial approximation part)
- _besi0q(x) (80-bit extended-double) : Based on Holoborodko's I0(x) of Quadruple precision [1]
- _besi0l(x) (80-bit extended-double, SX_CTRL = 24.0L)
相対誤差の比較 (expl() のレンジ・リダクションの効果)
Maximum errors in the calculations shown above. (0 ≤ x ≤ 768.0) _besi0q : 5.312e-17 (x = 759.74609375) _besi0l : 4.337e-19 (x = 68.6015625)list.1 の besi0q() で x > 15.5 での誤差が大きくなっているのは、主に最後の expl() 計算に起因するものです。 list.2 の_besi0l() では、x > 24.0 に対する最後の expl() のレンジ・リダクションにより、誤差が大きく減少しています。
besi0q() も同様に、上記のように最後の expl() のレンジ・リダクションにより、x ≥ 15.5 での誤差を大幅に縮小することができました。
I0 の応用の話として、カイザーウィンドウでよく使われる α が 3〜12 の範囲 — I0(x) の引数が π倍されて 9.42〜37.7 になる — でも、 テストした 64ビット倍精度版の I0(x) 関数すべての相対誤差は DBL_EPSILON(≒ 2.22 × 10-16 )の 16倍以上でした。
80ビット拡張倍精度の実装では、計算誤差が大きく改善されないものもありました。 しかし、4倍精度の有理数近似の係数を使用するものは、 倍精度の表現範囲を超える 0 ≤ x ≤ 768 の範囲において、LDBL_EPSILON の 6倍(≒1.084 × 10-19)以内の誤差であることが示されました。
一方、_besi0l() では、LDBL_EPSILON の約 4.5倍の最大誤差で、拡張倍精度には実はオーソドックスな実装が適していることがわかりました。
![]()
Download besi0l-1.4.3.js
— source code
参考文献I0 の参照値 I0(1) ≃ 1.266065877752008335598244625214718 I0(2) ≃ 2.279585302336067267437204440811533 I0(4) ≃ 11.30192195213633049635627018321710 I0(8) ≃ 427.5641157218047851773967913180829 I0(16) ≃ 893446.2279201050170708640309761884 I0(32) ≃ 5590908381350.873086500103776088605 I0(64) ≃ 3.115457918187897557650694682306942 ×1026 I0(128) ≃ 1.372222546128743649356014388474097 ×1054