Moscow MLの浮動小数点数除算のバグ [SML]
Moscow MLではrealを0.0で割るとDiv例外を投げる。
これは本来InfとNaNになるべきもののはずだ。
Moscow ML version 2.10 Enter `quit();' to quit. - 1.0 / 0.0; ! Uncaught exception: ! Div - 0.0 / 0.0; ! Uncaught exception: ! Div
これは本来InfとNaNになるべきもののはずだ。
Standard ML of New Jersey v110.74 [built: Mon Oct 29 11:48:41 2012] - 1.0 / 0.0; val it = inf : real - 0.0 / 0.0; val it = nan : real
SML/NJの浮動小数点数の不審な挙動(非正規数に対するReal.toManExp) [SML]
またSML/NJの浮動小数点数の処理で怪しいのを見つけてしまった。
SMLではReal.minPosという値が定義されている。これはIEEE754で表現できる0の隣の一番小さい正数なのだが、浮動小数点の世界では非正規化数というカテゴリに入る。(簡潔に説明できないのでWikipediaなどを参照ください)
Real.toManExpという関数はrealを仮数部と指数部に分ける関数である。
この2つを組み合わせるとどうなるか。
SML/NJ
Poly/ML
SML#
MLton
またSML/NJだけ仲間外れになった。SML/NJ以外のほうが明らかにわかりやすいんだけどtoManExpは単にmanに基数のexp乗をかけると元の数になるって言っているだけだから、そういうのが複数あるというだけかもしれない。
次にこういうのを書く。
どうかな…
SML#
SML/NJ
うーん…
SMLではReal.minPosという値が定義されている。これはIEEE754で表現できる0の隣の一番小さい正数なのだが、浮動小数点の世界では非正規化数というカテゴリに入る。(簡潔に説明できないのでWikipediaなどを参照ください)
Real.toManExpという関数はrealを仮数部と指数部に分ける関数である。
この2つを組み合わせるとどうなるか。
fun main () = let val {man=man, exp=exp} = Real.toManExp Real.minPos in print ("man=" ^ Real.toString man ^ "\n"); print ("exp=" ^ Int.toString exp ^ "\n") end val _ = main ()
SML/NJ
Standard ML of New Jersey v110.76 [built: Sat Sep 7 21:22:34 2013] - use "minpos.sml"; [opening minpos.sml] [autoloading] [library $SMLNJ-BASIS/basis.cm is stable] [autoloading done] man=2.22044604925E~16 exp=~1022 val main = fn : unit -> unit val it = () : unit
Poly/ML
> use "minpos.sml"; man=0.5 exp=~1073 val main = fn : unit -> unit val it = () : unit
SML#
# use "minpos.sml"; man=0.5 exp=~1073 val main = fn : unit -> unit
MLton
man=0.5 exp=~1073
またSML/NJだけ仲間外れになった。SML/NJ以外のほうが明らかにわかりやすいんだけどtoManExpは単にmanに基数のexp乗をかけると元の数になるって言っているだけだから、そういうのが複数あるというだけかもしれない。
次にこういうのを書く。
fun f r = let val {man=man, exp=exp} = Real.toManExp r val r' = man * (Math.pow (2.0, Real.fromInt exp)) in Real.== (r, r') end val eq = f Real.minPos
どうかな…
Poly/ML 5.2 Release > use "minpos2.sml"; val eq = true : bool val f = fn : Real.real -> bool val it = () : unit >
SML#
# use "minpos2.sml"; val eq = true : bool val f = fn : real -> bool
SML/NJ
Standard ML of New Jersey v110.76 [built: Sat Sep 7 21:22:34 2013] - use "minpos2.sml"; [opening minpos2.sml] [autoloading] [library $SMLNJ-BASIS/basis.cm is stable] [autoloading done] val f = fn : real -> bool val eq = false : bool val it = () : unit
うーん…
SML#の整数変換のバグ [SML]
[2014/4/5追記] この記事に記載の事象はSML#2.0.0では修正されていることを確認しました。
SMLのWORDのtoIntとtoIntXは元の整数値をそのまま正数とみるか負数とみるかの違いがある。たとえばWord8で0xffだったら前者が255、後者が-1になる。
http://www.standardml.org/Basis/word.html#SIG:WORD.toInt:VAL
SML/NJでは次の通り。
他のSML実装でも仕様通りに動く。
しかしSML#ではWord8.toIntとWord8.toIntXが同じ結果を出す。
これは困る…ちなみにWordで同様のことをすると下記のようになる。toIntとtoIntXが同じ実装ってわけでもないようだ。
これもよくわからない動き。訂正:これはこれでよかった。
SMLのWORDのtoIntとtoIntXは元の整数値をそのまま正数とみるか負数とみるかの違いがある。たとえばWord8で0xffだったら前者が255、後者が-1になる。
http://www.standardml.org/Basis/word.html#SIG:WORD.toInt:VAL
SML/NJでは次の通り。
- Word8.toInt 0wxff; val it = 255 : int - Word8.toIntX 0wxff; val it = ~1 : int
他のSML実装でも仕様通りに動く。
しかしSML#ではWord8.toIntとWord8.toIntXが同じ結果を出す。
SML# version 1.2.0 (2012-11-14 18:25:26 JST) for x86-linux # Word8.toInt 0wxff; val it = ~1 : int # Word8.toIntX 0wxff; val it = ~1 : int
これは困る…ちなみにWordで同様のことをすると下記のようになる。toIntとtoIntXが同じ実装ってわけでもないようだ。
# Word.toInt 0wxffffffff; uncaught exception: Overflow # Word.toIntX 0wxffffffff; val it = ~1 : int
SML/NJの浮動小数点処理のバグ(だと思う) [SML]
円周率はIEEE754の64ビット浮動小数点数で表すと「40 09 21 fb 54 44 2d 18」というバイト列に相当する。
SMLで、PackRealストラクチャを使わずに、この最後の2バイト「2d 18」を取り出したい。そこで次のようなコードを書く。
cは11544=0x2d18が入ることが期待されている。さまざまなSML実装で試した結果は次の通り。環境はすべてx86 Linuxである。
SML/NJ
Poly/ML
SML#
MLton
SML/NJだけ仲間はずれであり、今回の目的にもそぐわない。
バグか?というとバグだと断言できるほど浮動小数点計算の仕様に通じていないのだが、素人考えとしては、ここで行っている計算は2の階乗の掛け算と2の階乗による余りの算出だけなので、それぞれ指数部に対する足し算と、仮数部に対するビット演算だけでできそうだ。そういうのは誤差が出ないでくれればいいのではないかと思う。
こんなことやる場合あるのか?というと、SMLの仕様の範囲でrealのビット表現を直接得る方法はPackRealストラクチャしかない。しかしPackRealは必須ではなく、提供しているのは上記のSML実装のうちMLtonだけなのだ。
SMLで、PackRealストラクチャを使わずに、この最後の2バイト「2d 18」を取り出したい。そこで次のようなコードを書く。
fun main () = let fun println s = print (s ^ "\n") val pi' = #man (Real.toManExp Math.pi) * Math.pow (2.0, 53.0) val a = Math.pow (2.0, 16.0) val b = Real.rem (pi', a) val c = Real.toInt IEEEReal.TO_NEAREST b in println ("Real.precision=" ^ Int.toString Real.precision); println ("c=" ^ Int.toString c) end val _ = main ()
cは11544=0x2d18が入ることが期待されている。さまざまなSML実装で試した結果は次の通り。環境はすべてx86 Linuxである。
SML/NJ
- use "divreal.sml"; [opening divreal.sml] [autoloading] [library $SMLNJ-BASIS/basis.cm is stable] [autoloading done] Real.precision=53 c=11541 val main = fn : unit -> unit val it = () : unit
Poly/ML
> use "divreal.sml"; Real.precision=53 c=11544 val main = fn : unit -> unit val it = () : unit
SML#
# use "divreal.sml"; Real.precision=53 c=11544 val main = fn : unit -> unit
MLton
Real.precision=53 c=11544
SML/NJだけ仲間はずれであり、今回の目的にもそぐわない。
バグか?というとバグだと断言できるほど浮動小数点計算の仕様に通じていないのだが、素人考えとしては、ここで行っている計算は2の階乗の掛け算と2の階乗による余りの算出だけなので、それぞれ指数部に対する足し算と、仮数部に対するビット演算だけでできそうだ。そういうのは誤差が出ないでくれればいいのではないかと思う。
こんなことやる場合あるのか?というと、SMLの仕様の範囲でrealのビット表現を直接得る方法はPackRealストラクチャしかない。しかしPackRealは必須ではなく、提供しているのは上記のSML実装のうちMLtonだけなのだ。
AliceMLの整数変換のバグ [SML]
符号付き整数(2の補数表現)が31ビットのシステムで最小の負数は-1073741824で、これはC0 00 00 00のバイト列に対応する。
AliceML 1.4ではこの値を32ビット符号なし整数から31ビット符号付き整数に変換しようとするとオーバーフローしてしまう。
SML/NJでは、整数の精度は同じだが正しく変換できる。
仕様では「They raise Overflow if the target integer value cannot be represented as an Int.int. 」となっている。
http://www.standardml.org/Basis/word.html#SIG:WORD.toIntX:VAL
「if and only if」とは書いていないから表現できるときもOverflowを投げていいのだと解釈できないこともないけど…。
AliceMLは2007年のリリースが最後で、もうメンテナンスされていないのかもしれない。BTSにもアクセスできない。
https://www.ps.uni-saarland.de/alice/contact.html#bugzilla
AliceML 1.4ではこの値を32ビット符号なし整数から31ビット符号付き整数に変換しようとするとオーバーフローしてしまう。
- Int.precision; val it : int option = SOME 31 - LargeWord.wordSize; val it : int = 32 - LargeWord.toIntX (LargeWord.<< (Word.toLarge 0wxc0, 0w24)); Uncaught exception Overflow
SML/NJでは、整数の精度は同じだが正しく変換できる。
- Int.precision; val it = SOME 31 : int option - LargeWord.wordSize; val it = 32 : int - LargeWord.toIntX 0wxc0000000; val it = ~1073741824 : int - LargeWord.toIntX (LargeWord.<< (Word.toLarge 0wxc0, 0w24)); val it = ~1073741824 : int
仕様では「They raise Overflow if the target integer value cannot be represented as an Int.int. 」となっている。
http://www.standardml.org/Basis/word.html#SIG:WORD.toIntX:VAL
「if and only if」とは書いていないから表現できるときもOverflowを投げていいのだと解釈できないこともないけど…。
AliceMLは2007年のリリースが最後で、もうメンテナンスされていないのかもしれない。BTSにもアクセスできない。
https://www.ps.uni-saarland.de/alice/contact.html#bugzilla
SML#の整数除算のバグ [SML]
[2014/4/5追記] この記事に記載の事象はSML#2.0.0では修正されていることを確認しました。
SML#ではInt.divとInt.quotが同じ丸めを行っているようだ。
SMLの仕様ではi div jは「returns the greatest integer less than or equal to the quotient of i by j」、Int.quotは「quot rounds towards zero」となっていて違う動作であるべきだ。
http://www.standardml.org/Basis/integer.html#SIG:INTEGER.div:VAL
他の処理系での結果。
modとremについても同様のようだ。
でもSML#のバグ報告先ってウェブサイト上には書いていなくてよくわからないけど…
SML#ではInt.divとInt.quotが同じ丸めを行っているようだ。
SML# version 1.2.0 (2012-11-14 18:25:26 JST) for x86-linux # ~1 div 2; val it = 0 : int # Int.quot (~1, 2); val it = 0 : int
SMLの仕様ではi div jは「returns the greatest integer less than or equal to the quotient of i by j」、Int.quotは「quot rounds towards zero」となっていて違う動作であるべきだ。
http://www.standardml.org/Basis/integer.html#SIG:INTEGER.div:VAL
他の処理系での結果。
Standard ML of New Jersey v110.74 [built: Mon Oct 29 11:48:41 2012] - ~1 div 2; val it = ~1 : int - Int.quot (~1, 2); [autoloading] [library $SMLNJ-BASIS/basis.cm is stable] [autoloading done] val it = 0 : int
Poly/ML 5.2 Release > ~1 div 2; val it = ~1 : int > Int.quot (~1, 2); val it = 0 : Int.int
Moscow ML version 2.10 Enter `quit();' to quit. - ~1 div 2; > val it = ~1 : int - Int.quot (~1, 2); > val it = 0 : int
modとremについても同様のようだ。
でもSML#のバグ報告先ってウェブサイト上には書いていなくてよくわからないけど…
SML#の非分割コンパイル(ができない) [SML]
いま取り組んでいる個人的なSMLのプロジェクトで、できるだけ多くの複数のSMLコンパイラでビルドできるようにしたいと思っている。
SMLの処理系はビルドシステムもばらばらだし分割コンパイルもすべての処理系でできるわけではない。
ビルド方法の違いはsconsスクリプトを書いて吸収し、分割コンパイルについては低いバーに合わせなければならないから一括コンパイルで統一する。
いまのところMltonとPoly/MLに対応できている。Mltonはソースのリストからいったんmlbファイルを作ってビルドする。Poly/MLで実行ファイルを作る方法はちょっと変わっていて、対話シェル上でコンパイルされたものPolyML.exportという関数を使って吐き出すと実行ファイルになるので、sconsから標準入力経由でuseを流し込んで最後にexportする。
それで、次にSML#にも対応したいと思っているのだけどこれが以外に厄介だ。
SML#は「真の分割コンパイル」をサポートしている。だから、あるソースを他のコンパイル単位とは独立にコンパイルできる。このとき他のコンパイル単位内の関数なりがどのようなプロトタイプ(C言語風にいうと)を持っているかわからないとバイナリを生成できないので、その情報は提供してあげる必要があって、smiファイルにそういう情報を書く。これはCでいうヘッダファイル(の中のプロトタイプ宣言)に相当する。
ここまでは別によくて、そういうものなんだろうと思う。しかし今私は分割コンパイルという贅沢品をあきらめているので、一括でコンパイルできればよい。分割コンパイルという要件がなければ理屈上はsmiファイルを書く必要はないはずだ。コンパイラは自分が今のセッション中にコンパイルした関数のプロトタイプを知っているので、その関数に依存する関数をコンパイルするときにはその情報が利用できるはずだ。
しかしSML#はどうもそのようなsmiファイルを書かずに一括コンパイルするというやり方には対応していないようだ。たとえばsmlsharpコマンドに複数のsmlファイルを引数として与えると「cannot specify multiple .sml/.smi files in link mode」と返ってきてしまう。複数指定できないとなると1つずつ指定しなければならないが、それはつまり分割コンパイルということなのでsmiが必要になる。smiファイルに必要な_require文を書くことによってそれらをたどってリンクしてくれるという機能もあるんだけど_requireできるのはsmlでなくsmiだけなのでやっぱりsmiが必要になる。
しかしsmiファイルを書くのは正直言って面倒くさいのだ。分割コンパイルのために必要だというのはいいけど、分割コンパイルをあきらめたら書かなくてもいいようにしてほしい。大体SMLで書かれたライブラリをSML#から利用しようと思ったらそのライブラリの全部のソースにsmiを書かなければならないのだろうか。せめて自動生成してくれれば…
一応最後の手段として、ソースを全部catで連結したバンドルファイルを作ってそれをコンパイラに渡すというのはある。でも、コンパイラが出すエラーの行番号はバンドルファイルのものになってしまうので、あまり役に立たなくなってしまうし、なによりスマートじゃない。
SMLの処理系はビルドシステムもばらばらだし分割コンパイルもすべての処理系でできるわけではない。
ビルド方法の違いはsconsスクリプトを書いて吸収し、分割コンパイルについては低いバーに合わせなければならないから一括コンパイルで統一する。
いまのところMltonとPoly/MLに対応できている。Mltonはソースのリストからいったんmlbファイルを作ってビルドする。Poly/MLで実行ファイルを作る方法はちょっと変わっていて、対話シェル上でコンパイルされたものPolyML.exportという関数を使って吐き出すと実行ファイルになるので、sconsから標準入力経由でuseを流し込んで最後にexportする。
def compile_poly(env, target, source): p = Popen(shlex.split('poly -q --error-exit'), stdin=PIPE, stdout=PIPE) script = '' for s in source: script += 'use "%s";\n' % s script += 'PolyML.export("%s", main);' % target[0] p.communicate(script) return None
それで、次にSML#にも対応したいと思っているのだけどこれが以外に厄介だ。
SML#は「真の分割コンパイル」をサポートしている。だから、あるソースを他のコンパイル単位とは独立にコンパイルできる。このとき他のコンパイル単位内の関数なりがどのようなプロトタイプ(C言語風にいうと)を持っているかわからないとバイナリを生成できないので、その情報は提供してあげる必要があって、smiファイルにそういう情報を書く。これはCでいうヘッダファイル(の中のプロトタイプ宣言)に相当する。
ここまでは別によくて、そういうものなんだろうと思う。しかし今私は分割コンパイルという贅沢品をあきらめているので、一括でコンパイルできればよい。分割コンパイルという要件がなければ理屈上はsmiファイルを書く必要はないはずだ。コンパイラは自分が今のセッション中にコンパイルした関数のプロトタイプを知っているので、その関数に依存する関数をコンパイルするときにはその情報が利用できるはずだ。
しかしSML#はどうもそのようなsmiファイルを書かずに一括コンパイルするというやり方には対応していないようだ。たとえばsmlsharpコマンドに複数のsmlファイルを引数として与えると「cannot specify multiple .sml/.smi files in link mode」と返ってきてしまう。複数指定できないとなると1つずつ指定しなければならないが、それはつまり分割コンパイルということなのでsmiが必要になる。smiファイルに必要な_require文を書くことによってそれらをたどってリンクしてくれるという機能もあるんだけど_requireできるのはsmlでなくsmiだけなのでやっぱりsmiが必要になる。
しかしsmiファイルを書くのは正直言って面倒くさいのだ。分割コンパイルのために必要だというのはいいけど、分割コンパイルをあきらめたら書かなくてもいいようにしてほしい。大体SMLで書かれたライブラリをSML#から利用しようと思ったらそのライブラリの全部のソースにsmiを書かなければならないのだろうか。せめて自動生成してくれれば…
一応最後の手段として、ソースを全部catで連結したバンドルファイルを作ってそれをコンパイラに渡すというのはある。でも、コンパイラが出すエラーの行番号はバンドルファイルのものになってしまうので、あまり役に立たなくなってしまうし、なによりスマートじゃない。
Poly/MLのFFIでTclを呼ぶ [SML]
Standard MLの実装の1つであるPoly/MLにおけるFFIを試してみる。
引数の数と種類に応じてcallnという関数を挟んでMLの型とCの型の橋渡しをしてあげる必要がある。
インタラクティブシェルもあるが、ここではpolycというコマンドでコンパイルして動かす。
参考
[1] http://www.polyml.org/docs/CInterface.html
open CInterface fun main () = let val lib = load_lib "/usr/lib/x86_64-linux-gnu/libtcl8.5.so.0" val symCreateInterp = load_sym lib "Tcl_CreateInterp" val createInterp = call0 symCreateInterp () POINTER val symEvalEx = load_sym lib "Tcl_EvalEx" val evalEx = call4 symEvalEx (POINTER, STRING, INT, INT) INT val interp = createInterp () val script = "puts hello" in ignore(evalEx (interp, script, String.size script, 0)) end
引数の数と種類に応じてcallnという関数を挟んでMLの型とCの型の橋渡しをしてあげる必要がある。
インタラクティブシェルもあるが、ここではpolycというコマンドでコンパイルして動かす。
$ polyc main.sml $ ./a.out hello
参考
[1] http://www.polyml.org/docs/CInterface.html
SML# 0.20 から Tcl を呼ぶ [SML]
SML# 0.20 [1] が Windows バイナリで配布されるようになったと聞いて試してみた。しかも FFI が強力らしいので Tcl のコマンドを解釈してみた。
G:\>smlsharp SML# 0.20 (2007-03-30 10:47:08 JST) # open DynamicLink; open DynamicLink # val tcl = dlopen "C:\\Tcl\\bin\\tcl84.dll"; val tcl = 0x10000000 : unit ptr # val Tcl_CreateInterp = dlsym (tcl, "Tcl_CreateInterp") : _import () -> unit ptr; val Tcl_CreateInterp = fn : unit -> unit ptr # val Tcl_Eval = dlsym (tcl, "Tcl_Eval") : _import (unit ptr, string) -> int; val Tcl_Eval = fn : unit ptr * string -> int # val Tcl_GetStringResult = dlsym (tcl, "Tcl_GetStringResult") : _import unit ptr -> UnmanagedString.unmanagedString; val Tcl_GetStringResult = fn : unit ptr -> UnmanagedString.unmanagedString # val interp = Tcl_CreateInterp (); val interp = 0x06892d38 : unit ptr # Tcl_Eval (interp, "puts {Hello World}"); Hello World val it = 0 : int # Tcl_Eval (interp, "puts xxx {Hello World}"); val it = 1 : int # Tcl_GetStringResult interp; val it = 0x06892e50 : UnmanagedString.unmanagedString # UnmanagedString.import it; val it = "can not find channel named \"xxx\"" : string #
すばらしい。ネイティブコンパイラが完成したら OCaml より便利かもしれない。
[1] http://www.pllab.riec.tohoku.ac.jp/smlsharp/ja/