(解決)MathematicaのTableのバグ


Mathematica 10.1 for Windows (64-bit)で修正されました。

Mathematica 8.0.4, 9.0.1, 10.0.2 for Windows (64-bit)と10.0.0 for Linux ARM (32-bit)でのことです。

Mathematicaで確率分布から疑似乱数を作るRandomVariateを試していたら、こんなバグを見つけました。(RSSリーダーでは表示されないかもしれません。)

開発元に報告したら、「長さが250以上のリストを作るときにはコンパイルが自動的に試みられるが、そこに問題がある。以下のコマンドでそれを無効にせよ」とのことでした。

SetSystemOptions["CompileOptions" -> "TableCompileLength" -> Infinity]

バグが修正されたバージョンを受け取るために、プレミアユーザになっています。

(解決)Javaでサニタイズするときの注意—Apache Commons Lang 2.xと3.0の場合


Commons Lang 3.0.1で修正されています。

Java 5をサポートする「Apache Commons Lang 3.0」がリリース、というニュースがありました。正確には、Java 1.4以下をサポートしない「Apache Commons Lang 3.0」がリリース、だと思いますが、それはまあいいとして、これに関連する少し深刻な話を忘れないうちに書いておきましょう。

「Javaプログラマは文字列の操作方法を確認した方がいいかもしれない」のつづきです。

HTML文書を書く時には、「𠮷野家(>_<)」のような文字列は、「𠮷野家(&gt;_&lt;)」のように書かなければなりません。「<」や「>」、「&」などは特殊な文字なので、文書中にそのまま書くことはできないのです。

効率を気にしないなら、次のように書いて置換するのが簡単です。

str = "\uD842\uDFB7\u91CE\u5BB6(>_<)";
str = str.replace("&", "&amp;")
        .replace("<", "&lt;").replace(">", "&gt;");
System.out.println(str);
//𠮷野家(&gt;_&lt;)

しかし、このようなコードをいつも書くのは面倒ですし、順番を間違える危険もあります。実は「'」や「"」を置換したかったりもします。

そこで、HTML中に直接書けない文字を置換するためのライブラリを使います。Apache Commons Langはそのようなライブラリの一つで、拙著『Webアプリケーション構築入門』でも紹介しました。次のように使います。

まずは最近リリースされたCommons Lang 3.0の場合です。

String str = "文字列(>_<)";
str = org.apache.commons.lang3.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//文字列(&gt;_&lt;)

3.0は出たばかりですし、Java 5以上が必須なので、まだ2.xの方がほとんどでしょう。2.xでは次のようになります。

String str = "文字列(>_<)";
str = org.apache.commons.lang.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//&#25991;&#23383;&#21015;(&gt;_&lt;)

Commons Lang 2.xでは、ASCII以外の文字は「&#コードポイント(10進表記);」という形(数値文字参照)に置換されるので、人間にはわかりにくくなっていますが、ウェブブラウザでなら「文字列(>_<)」と表示されます。

2.xと3.0の間には、(1) パッケージ名(2.6はlang、3.0はlang3)(2) 数値文字参照の使用(2.6では使う、3.0では使わない)、に関して違いがあります。

ここまでは問題ありませんが、対象文字列がサロゲートペアを含むようになると、いやな感じになります。

まずは3.0の場合。

String str = "\uD842\uDFB7\u91CE\u5BB6(>_<)";
str = org.apache.commons.lang3.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//𠮷?野家(&gt;_&lt;

変なところに「?」が入って、最後の「)」が無くなっていますが、これは間違いです。数値文字参照を使わない3.0では、「𠮷野家(&gt;_&lt;)」にならなければなりません。

バグレポートを送ったので、この問題は3.0.1で解決されるはずです。(追記:修正されました。)

サロゲートペアを使う可能性が少しでもある人は、Commons Lang 3.0はスルーしましょう。

パッチを見てもらえばわかりますが、「1文字ずつ処理する」部分にバグがあったのです(参考:Javaプログラマは文字列の操作方法を確認した方がいいかもしれない)。

とりあえずの結論:Javaの文字列処理は難しすぎる。

次にCommons Lang 2.xの場合です。

str = "\uD842\uDFB7\u91CE\u5BB6(>_<)";
str = org.apache.commons.lang.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//&#55362;&#57271;&#37326;&#23478;(&gt;_&lt;)

「𠮷」が「&#55362;&#57271;」という2つの数値文字参照になっていますが、これは間違いです。「&#134071;」という1つの数値文字参照にならなければなりません(参考:Using character escapes in markup and CSS)。

この問題を解決するパッチを送りましたが、「3.0で修正されている」としてスルーされてしまいました。間違いが2.xで修正されるとしたら、動作をそろえるためにStringEscapeUtils.unescapeXml(str)にも修正が必要だと思い、そのためのパッチも送ったのですが、こちらは現時点では間違った動作はしていないので、やはりスルーされてしまいました。

「3.0で修正されている」とは言っても、先に述べたように、2.xと3.0ではサポートするJavaのバージョンが違いますし、パッケージ名も変わっているので、2.xでも直しておいた方がいいと思ったのですが、まあ、仕方ありません。

Commons Lang 2.xを使っている人は、「サロゲートペアはサポートしない」と仕様書に明記しておきましょう。

最終結論:Javaの文字列処理は難しすぎる。

(解決)64ビット版Java仮想マシンのおかしな振る舞い(整数編)


1.7.0ではこの現象は発生しませんでした。よかった。

64ビット版Java仮想マシンは、整数がオーバーフローする(可能性がある?)場合に、おかしな振る舞いをするようです。

例1

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6980767で報告されている例です。

public class Bug1 {

  public static void main(String[] args) {
    System.out.println(System.getProperty("java.runtime.version"));
    int foobar = Integer.MAX_VALUE;
    for (int value = 0; value <= foobar; value++) {
      if (value % 100000000 == 0) {
        System.out.println("At " + value);
      }
    }
    System.out.println("DONE");
  }
}

私の環境では、次のような実行結果になりましたが、これは適切なものではありません。

1.6.0_22-b04
At 0
DONE

ちなみに、32ビット版Java仮想マシンなら適切な結果になります。(略)

例2

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6979077で報告されている例です。

public class Bug2 {

  public static void main(String[] args) {
    System.out.println(System.getProperty("java.runtime.version"));
    for (int i = 0; i < 20; i++) {
      work(2147463647, Integer.MAX_VALUE);
    }
  }

  static void work(int l, int u) {
    int runs = 0;
    for (int i = l; (0 < i) && (i <= u); i++) {
      runs++;
    }
    System.out.println(runs);
  }
}

私の環境では、次のような実行結果になりましたが、これは適切なものではありません。

1.6.0_22-b04
20001
20001
20001
20001
20000
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025

ちなみに、32ビット版Java仮想マシンなら適切な結果になります。(略)

例3

フェルマーの最終定理の「反例」(C言語編)で紹介した例は、Javaでも有効なはず、つまりフェルマーの最終定理の反例を間違って表示して終了するはずですが、以下のコードは65ビット版Java仮想マシンでは止まりません(数学的には正しいけれど)。

public class Bug3 {

  public static void main(String[] args) {
    System.out.println(System.getProperty("java.runtime.version"));
    for (int c = 1; true; c++) {
      for (int a = 1; a < c; a++) {
        for (int b = a; b < c; b++) {
          if (a * a * a + b * b * b == c * c * c) {
            System.out.println("a = " + a + ", b = " + b + ", c = " + c);
            System.exit(0);
          }
        }
      }
    }
  }
}

「true」を「c < Integer.MAX_VALUE」に変えると適切な結果になります。

ちなみに、32ビット版Java仮想マシンなら適切な結果になります。(略)

どういうことなのかわかる人がいたら教えてほしいです。原因がわかるまでは、64ビット版Java仮想マシンは、ちょっとこわくて使えません。

1.6.0_26-b03でも確認しました。32ビット版は大丈夫で64ビット版はおかしいです。

(解決)バグフィックスと機能追加を同時にすること


7.0.1で修正済み。

Mathematica 7.0の新機能の一つであるDeleteDuplicatesにはバグがあり、次のような簡単なコードで確認できます。

While[True, DeleteDuplicates@{1, 1}]

実行すると、Mathematicaのメモリ使用量がどんどん増大し、短時間でプログラムがクラッシュします。メモリリークがあるようです。

重複を取り除いて並び替えるUnionは大丈夫ですが、並び替えてはいけない場合もあるので、代用することはできません。

Mathematicaのドキュメントに次のような代案がありますが、Unionより遅いという欠点があります。

unsortedUnion[x_] := Reap[Sow[1, x], _, #1 &][[2]]

苦心して見つけたバグを報告したら、「新しいバージョンでは直ってるからそれを買ってね」と言われた学生の頃の苦い経験を思い出します。

今は、Premier Serviceに登録しているので、最新版が出たら無料でアップデートできます(学生版2本買うよりも高いのですが)。実際、最近出た7.0.1で試してみると、このバグはちゃんと修正されていることが確認できました。

学生のことを思うと、自由でないソフトウェアで、バグフィックスと機能追加を同時にするのはやめてほしいものです。