OCamlの浮動小数点数演算時の丸め方向の設定
OCamlの浮動小数点数には整数への丸め関数は存在するが、演算時の丸め方向を設定することはできない。
丸め方向まで気にすることは通常はないが、精度保証を要求されるプログラムを書く場合には、演算時の丸め方向まで考慮する必要がある。
ちなみにC (C99) *1 やC++11 *2 にはfesetroundという関数があり、この関数を用いることで浮動小数点数の演算時の丸め方向を指定することができる。
丸め方向を指定した演算の実装
そこで今回、OCamlで丸め方向を指定した演算を実装してみた。
実装方法は以下の通り。
- OCamlのCを呼び出す機能 *3 によりCの関数を呼び出す
この際演算したい2つの浮動小数変数と演算時の丸め方向を引数としてCの関数に渡す - 呼び出されたCの関数内で浮動小数点数の演算の丸め方向をfesetroundにより指定
- 続けてCの関数内で演算を行い、演算結果を返す (この際浮動小数点数の演算の丸め方向を元に戻すことに注意)
- 呼び出し元のOCamlで演算結果を受け取る
以下、加算を例に簡単に実装方法を紹介する。
丸め方向を指定した演算の実装の詳細
OCamlからCを呼び出すラッパー部分
まずCを呼び出すラッパー部分のOCamlは以下のようになる:
round_mode が丸め方向用の型定義であり、external から始まる文でOCamlで add 関数が呼び出された際に呼び出すCの関数 addc と、呼び出す際に渡す引数に丸め方向と演算する2つの浮動小数点数を指定している。
なお、round_modeのそれぞれの説明を以下に示す:
- NearestTiesToEven
最近接への丸め、等しいなら偶数へ - ToZero
0方向への丸め - Up
+∞方向への丸め - Down
-∞方向への丸め
本当なら NearestTiesToAway (最近接への丸め、等しいなら奇数へ) もサポートしたかったが、fesetroundの仕様 (IEEE-754) により断念した。
OCamlから呼び出されるCの丸め指定付き演算
次に呼び出されたCの関数は以下のようになる:
OCamlから呼び出されるため少し書き方が異なるが、addc 関数内で演算の前に change_round_mode 関数で丸め方向を設定し、演算後は丸め方向を元に戻し、演算結果を返している。
演算後は丸め方向を元に戻さないと、OCamlでの演算に影響を及ぼすことがあるので注意が必要である。
なお、OCamlからCを呼び出す方法は以下を参考にした:
Calling C libraries – OCaml https://ocaml.org/learn/tutorials/calling_c_libraries.html#WrappingcallstoClibraries
丸め指定付き演算のサンプル
次に実際に丸め指定付きで加算演算した結果を表示してみる簡単なプログラムを以下に示す:
それぞれの丸め方向について演算を行い、表示するプログラムである。
Makefile
正直OCamlのMakefileの書き方は良くわかってないので解説はしない。OCamlMakefile *4 を使うと簡潔に書けるみたいだが、Cのラッパー部分の書き方がよくわからない。分かる人は教えて下さい。
コンパイル方法は以下を参考にした:
C関数をラップしてOCamlに接続する方法 (How to wrap C functions to OCaml)
http://www.geocities.jp/harddiskdive/ocaml-wrapping-c/ocaml-wrapping-c.html
Makefileは以下のようになる:
make すると実行ファイルとして、バイトコードコンパイルした test と、ネイティブコードコンパイルした testopt が生成される。
丸め指定付き演算のサンプルの実行結果
サンプルの実行結果は以下のようになる:
丸め方向付きで正しく演算できていることが確認出来る。
まとめ
OCamlの浮動小数点数演算時の丸め方向を指定することができるようになった。やったね!
なお、今回のプログラムはGitHubにあげてある。四則演算に加えて平方根までサポートしている。
https://github.com/tyabu12/ocaml-round
もしバグや改良できる点などがありましたら教えてください。
*1:fegetround, fesetround – cppreference.com
http://en.cppreference.com/w/c/numeric/fenv/feround
*2:std::fegetround, std::fesetround – cppreference.com
http://en.cppreference.com/w/cpp/numeric/fenv/feround
*3:Calling C libraries – OCaml
http://ocaml.org/learn/tutorials/calling_c_libraries.html#WrappingcallstoClibraries
*4:GNU make でのコンパイル – OCaml
http://ocaml.org/learn/tutorials/compiling_with_gnu_make.ja.html