処理系と数値演算の話
ある程度自由な型変換を動的にやってのけてしまうような Python とか Perl とか Ruby とかの動的型付け言語で頻出する問題として 『2項演算子の左右の型を揃える問題』 が存在する。
これは、例えば Java とか C/C++ とか C# とか Pascal/Delphi とかの静的型付け言語にとっては全く問題無く、コンパイル時に型を調べて変換を行ってしまえば良い。けど動的型付け言語においてはコンパイル時に調べるなんてことはできず、かつ実行時に念入りに調べていくと意外とオーバーヘッドが出てきちゃって困るよね的な問題です。
kuzha でも次回の拡張で多倍長演算サポート*1する予定ではあるんだけど、それ使うと数値の実体が Int32, Int64, Float32, Float64, BigInteger, BigDecimal の6種類になってしまって、型変換がややこしいことになってしまう。
必要用件は
- 同じ型どうしでの演算だけではなく、異なる型どうしの演算も行いたい
- できれば実体を気にせず 『数値』 としてひとまとめにあつかいたい
- 可能な限りオーバーヘッドを無くしたい
- コードサイズもできるかぎり小さくしたい
の4種類。それに加えて
- 精度はどうするか。Int32 で溢れたら自動的に Int64 に変換するか、など
の選択肢が加わってくる。これらの用件をどのように実装するかが注目されるところ。
Io
数値の実体は double 唯一つ。卑怯だが、ある意味上の用件を全て満たしている最適解。実に潔く、スマート。
Jython
数値の実体は PyInteger(int), PyLong(java.math.BigInteger), PyFloat(double), PyComplex(double, double) の4種類。PyInteger で桁あふれしたら自動的に PyLong へと切り替わるようになっている。
各クラスに数値演算を行うメソッドが定義されている。精度変換のトリックは
- 2項演算を担うのは org.python.core.PyObject._add 等各種演算メソッド
- left より right の精度のほうが低ければ left.__***__(right) 呼び出し
- __***__ メソッド内で left の精度に right の精度を合わせる
- left より right の精度のほうが高ければ right.__r***__(left) 呼び出し
- __***__ メソッド内で right の精度に left の精度を合わせる
- left より right の精度のほうが低ければ left.__***__(right) 呼び出し
JRuby
数値の実体が妙に多い。
- RubyNumeric
- RubyInteger
- RubyFixnum(long)
- RubyBignum(java.math.BigInteger)
- RubyFloat(double)
- RubyComplex(IRubyObject, IRubyObject)
- RubyBigDecimal(java.math.BigDecimal)
- RubyInteger
2項演算を担うのは、それぞれのオブジェクトに定義された各種演算メソッド。left の精度と right の精度をあわせることができれば left.op_***(right) を呼び出す。あわせることができなければ right.coerce(left).op_***(left) を呼び出す。RubyFixnum で桁あふれしたら自動的に RubyBignum に切り替わる。
kuzha
さて、どうしてみようかな。