10億単位で演算する10進浮動小数点ライブラリ

C/C++のlong double型ってどんな仕様なのかな?と調べてみたら普段使ってるgccだと80bitらしい。でもsizeof()で調べてみるとメモリサイズとしては128bit(16byte)だしなんだか中途半端な感じ。さらに調べてみたら128bit型はIEEE754で仕様が規定されてた。

IEEE754の128bitデータ型であれば必要十分な有効桁数が確保できるけど80bitだと国家予算程度でぎりぎりだしIEEE754の128bitデータ型が使えたとしても標準ライブラリの対応状況がびみょーな気も...

それと2進浮動小数点型は小数部を正確に表すことができないことがあり10進浮動小数点型のほうが好ましいことが多いものの10進演算で1桁毎に演算したりすると効率が悪いのが気になってしまう。CPUも高速になってきているのでそう大きな問題でもなさそうではあるが...

そこで、ないものは作ればいいといつものいきおい(-_-;)で10進浮動小数点型で128bit以上を扱えて高速な演算が可能なデータ型を考えてみた。

高速な演算を行うには複数桁をまとめて演算する必要があるため32bitデータ型に収まる10億単位で演算する方式にしてみた。64bit-CPUならより大きい単位も考えられるが浮動小数点型となると小数点の位置が一桁単位ではなく複数桁単位になってしまうことから有効桁数が数値により増減してしまうデメリットもあるので10億単位あたりが実用的な上限ではないかと思う。

数値は最上位桁から下位桁に向かって格納する。そうすることで演算時の桁合わせ処理が必要なくなるのと最上位桁だけでゼロ判定ができるので何かと都合が良い。それにともない小数点位置は最上位桁が小数点位置の起点となる。

n乗根(root)が完成した段階でとりあえず公開することにしたが作りたてで試験もあまりしてないためバクバグしてる可能性があることに注意すること。

試しに512bitで2の平方根(sqrt)を求めてみた。ニュートン法で収束或いは反復するまで繰り返して求められた結果は136桁。512bitだと最大144桁であるが小数点位置の関係で144-8=136桁となる。

計算時間は下記の通り。

128bit 28桁 1.9us
256bit 64桁 11.4us
512bit 136桁 170.5us

1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641572735013846230912297024924836055850737

※ 浮動小数点型は表せる数値範囲は広いが有効桁が制限される。より大きな有効桁数を必要とするなら任意精度演算のほうが適している。

【仕様】

しかし、512bitで十億桁って一体どんな世界なんだろう?
想像もつきませーーーーーーーーーーーーーーーん。(笑)

【外部データ形式】

データは32bit値の配列[4/8/16]形式。

32bit[4] 128bit 有効桁数 28-36桁
32bit[8] 256bit 有効桁数 64-72桁
32bit[16] 512bit 有効桁数 136-144桁

[999999999],[999999999],[999999999],[999999999],…

最上位桁:[仮数部(LSB:30bit)+指数部(1bit)+符号部(1bit)]
中下位桁:[仮数部(LSB:30bit)+指数部(2bit)]

桁(32bit)の仮数部には10進9桁の数値をバイナリーで格納。指数部は各桁に分割格納される。この形式は外部保存用の形式。

【内部データ形式】

外部データ形式と同じであるが演算しやすいよう各桁の符号部と指数部は別変数にまとめられ保存時に各桁に振り分けられる。

【オペレータ・オーバー・ロード】

ほとんどのオペレータをオーバーロードしてみたので標準データ型と同じようにコードが書けるし標準データ型との混在(型変換)も可能だがダウンキャスト時の数値のオーバーフローやアンダーフローには注意しなければならない。

【除算のアルゴリズム】

ライブラリを作るにあたって一番悩んだのは除算及び剰余処理だがなんとか完成。アルゴリズムはよく知られている筆算方式で10進一桁あたり0~3回の減算と10倍するための加算で処理している。128bitサイズだと全桁で9×4=36桁格納できるが全桁計算したとしても0回~108回の減算と36回の加算で済む。

小数部を含む剰余は除算と同じ処理で行えるが剰余を求める場合に限り商は整数に制限される。でないと何が余りかわからないので...

【修正】
2024-12-23
root(),pow()の修正。

2024-12-22
ln()の符号がバグッてたので修正。

2024-12-17
加算と減算が思いっきりバグってたので修正。

2024-12-14
まさかの加算がバグってたので修正。(-_-;)
あとpow()を小数指数に対応&exp()/ln()/log2()/log10()/log(b)を追加。ほとんど試験してないので全体的にまだまだバグが取り切れてない気がする。

2024-12-12
ceil()を追加。

2024-12-11
floor(負数)とdiv()の剰余処理がまだバグってたので再修正したのとスタティック変数の初期化のためにbdfp.cppを追加。
ついでにround()に丸めモード引数を追加した。ROUND_HALF_EVENモードは累積誤差が一番少なくなるらしく銀行型丸めとも呼ばれることがあるという。初めて知りました。v(-_-;)

2024-12-10
丸め処理を行うround()の追加とdiv()の剰余処理がバグってたので修正。あとsqrt()とroot()の符号を無視するよう修正してみた。

2024-12-07
n乗根(root)の最下位桁に誤差が出るため最下位桁を削除。
旧: 9 root 2 -> 3.000………00001
新: 9 root 2 -> 3
除算の10倍処理(mul10)を改良。少しだけ早くなった。

【サンプルの実行形式(sample.exe:win-x64】
sample.zip

【サンプル・プログラム】

【ライブラリ】