Más contenido relacionado
La actualidad más candente (20)
Similar a 高速な倍精度指数関数expの実装 (20)
Más de MITSUNARI Shigeo (20)
高速な倍精度指数関数expの実装
- 4. 最終実装コード
今回の目標はこのコードの意味を理解すること
2011/8/6 /204
union di {
uint64_t i;
double d;
};
double expd(double x) {
di di;
di.d = x * 2954.639443740597 + 6755399441055744ULL;
uint64_t iax = tbl[di.i & 2047];
double t = (di.d - 6755399441055744ULL)
* 0.0003384507717577858 - x;
uint64_t u = ((di.i + 2095104) >> 11) << 52;
double y = (3.0000000027955394 - t) * (t * t)
* 0.16666666685227835 - t + 1;
di.i = u | iax;
return y * di.d;
}
- 6. 単純和のベンチマーク
gcc 4.4.5 gcc 4.5.1 VC10 icc 12.0.5
std::exp 77.584 80.432 35.060 31.352
fmath::expd 12.680 13.067 12.842 11.582
2011/8/6 /206
Core i7-2600 3.4GHz + 64-bit (Linux/Windows7)
結果はどれも同一のsum=171828182.101727を出力
一つの関数あたりの処理クロック(clk)
gccは相変わらず遅い
- 7. 配列版のベンチマーク(1/2)
誤差と速度の評価(gcc 4.4.5)
平均0, 標準偏差1の正規分布から10^6個生成した乱数
Taylor, Remezは岡崎直観さんの実装
cf. http://www.chokkan.org/blog/archives/352
expdの精度はRemez9次より良くて11次より悪い
とはいえdoubleの精度は約16桁なのでかなりよい
2011/8/6 /207
関数 clk 最大誤差 二乗平均誤差
std::exp 67.17 0 0
fmath::expd 11.70 6.575812e-16 1.117645e-16
Taylor 11th 36.97 8.792464e-15 1.372958e-15
Remez 9th 16.29 1.909579e-14 9.909547e-15
Remez 11th 19.35 2.571683e-16 6.146323e-17
fmath::expd_v 8.27 6.575812e-16 1.117645e-16
配列版
(速度不利)
SSE版
(速度有利)
- 9. exp(𝑥)の性質
和のexpはそれぞれのexpの積
exp 𝑥 + 𝑦 = exp 𝑥 exp(𝑦)
テイラー展開
exp 𝑥 = 1 + 𝑥 +
𝑥2
2
+
𝑥3
6
+
𝑥4
24
+ …
戦略その1
入力値𝑥を,テーブル引きするための大雑把な値𝑠とテイ
ラー展開するための小さい値𝑡にわける
𝑥 = 𝑠 + 𝑡
exp 𝑥 = exp 𝑠 exp(𝑡)
2011/8/6 /209
テーブル引き テイラー展開
- 10. テイラー展開の項数
どこまで計算するか?
1 + 𝑥, 1 +
𝑥2
2
, 1 +
𝑥2
2
+
𝑥3
6
, 1 +
𝑥2
2
+
𝑥3
6
+
𝑥4
24
, …
2次ならaddが1回, mulが2回
3次はaddが3回, mulが3回
4次はaddが4回, mulが4回, 5次は...
精度からの要請
doubleの精度は53bit(1e-16のオーダー)
𝑥が12bitなら𝑥4/24は12*4+4=52なので3次でよさそう
4次までするなら𝑥は9bitでよい(𝑥5/120→9*5+7=52)
今回は3次を選択
2011/8/6 /2010
- 11. 𝑥を整数と小数部分にわける
𝑥 = round(𝑥) + (𝑥 – round(𝑥))
round(𝑥) = 𝑥に一番近い整数
𝑠 = round(𝑥)とおけば 𝑡 = 𝑠 − 𝑥で 𝑡 ≤ 1/2
𝑡 ≤ 1/212
にするには
𝑥の代わりにx' = x * 2048を考える
𝑥′ = 𝑠′ + 𝑡′とすると|𝑡′| ≤ 1/2で
𝑥 = round(𝑥 ∗ 2048) / 2048 + 𝑡′/2048
2011/8/6 /2011
input : x
x' = x * 2048
s' = round(x')
s = s' / 2048
t = x – s // このとき x = s + tで|t|<=1/4096
- 12. テーブル引き
𝑥 = exp 𝑠 exp 𝑡 の exp 𝑠 の部分
𝑠は整数𝑛 / 2048の形で𝑛はどれぐらい?
exp(𝑥) = 0となる𝑥はlog(DBL_MIN)=-708.396
exp(𝑥) = Infとなる𝑥はlog(DBL_MAX) = 709.782
とするとテーブルエントリサイズは
(709 + 708) ∗ 2048 = 2.9 ∗ 106
でかすぎ
2011/8/6 /2012
- 14. テーブル引き再度
𝛼 = 2048/log2とすると
exp 𝑛 ⁄ 𝛼 = 2^(𝑛/2048)
こうすれば
𝑛 = 2048(𝑛/2048) + (𝑛 % 2048) = 2048𝑛1 + 𝑛2
として
exp(𝑛/𝛼) = 2^(𝑛1)2^(𝑛2/2048))
テーブルエントリは2.9*10^6から2048に減った
これなら使える
2011/8/6 /2014
- 17. 浮動小数点数のトリックを使う(1/2)
doubleのフォーマット
𝑥 = −1 𝑠
× 2 𝑒−1023
× (1 + ⁄𝑓 252
)
すごく大きい値を足すと仮数部が丸められる
たとえば12.25は
12.25 = 23
∗ 1.53125 = 23
∗ (1 + 17/25
)
252
を足すと
12.25 + 252
= 252
∗ 1 + 2.72. . 10−15
= 252
∗ (1 + ⁄12 252
)
2011/8/6 /2017
符号ビットs(1bit) 指数部e(11bit) 仮数部f(52bit)
仮数部が丸められて整数になってる
- 19. 最終コード
マジックナンバーの意味
2011/8/6 /2019
double expd(double x) {
di di;
di.d = x * (2048 / log(2)) + (3ULL << 51);
uint64_t iax = tbl[di.i & 2047];
double t = (di.d – (3ULL << 51)) * (log(2) / 2048) - x;
uint64_t u = ((di.i + 2095104) >> 11) << 52;
double y = (3.0 - t) * (t * t) * (1.0/6) - t + 1;
di.i = u | iax;
return y * di.d;
}
α倍 round処理
テイラー展開
もとに
戻して1/α倍
2巾の簡単な方
2巾の表引き
- 20. おまけ(少しでも誤差を減らす)
テイラー展開の係数
1 + 𝑥 + ⁄𝑥2 2 + ⁄𝑥3 6 = 1 + 𝑥 + ⁄1 6 𝑥2 × 3 + 𝑥 で
1/6の代わりに0.16666666685227835
3.0の代わりに3.0000000027955394を利用
誤差がほんの少し減る(RMSが3%減)
係数の決め方 : 区間[0, t]でのexp(x)との二乗平均誤差
𝐼 = න
0
𝑡
exp 𝑥 − 𝑎 + 𝑏𝑥 + 𝑐𝑥2
+ 𝑑𝑥3 2
𝑑𝑥
が最小になるように
𝜕𝐼
𝜕𝑎
=
𝜕𝐼
𝜕𝑏
=
𝜕𝐼
𝜕𝑐
=
𝜕𝐼
𝜕𝑑
= 0を解き, 𝑡 = log(2)/4096を代入
2011/8/6 /2020