SSブログ

Moscow MLの浮動小数点数除算のバグ [SML]

Moscow MLではrealを0.0で割るとDiv例外を投げる。

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つを組み合わせるとどうなるか。

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では次の通り。

- 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」を取り出したい。そこで次のようなコードを書く。

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ビット符号付き整数に変換しようとするとオーバーフローしてしまう。

- 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# 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する。

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を試してみる。

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/


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。