|
|
||
実際のソースを見て、解析してみました。
// 問題のコード Double.parseDouble("2.2250738585072012e-308");
これが問題のコードですが、Double.parseDouble()の中身は以下のようになっています。
import sun.misc.FloatingDecimal; public final class Double extends Number implements Comparable<Double> { ... public static double parseDouble(String s) throws NumberFormatException { return FloatingDecimal.readJavaFormatString(s).doubleValue(); } ... }
実際にはsun.misc.FloatingDecimal が呼ばれています。
今回問題となるのは、後者のdoubleValue()になります。
まず前者のreadJavaFormatString() は文字列を解析し、符号、指数部、仮数部、指数部、異常な値かどうかを判断しています。
このメソッドが返すのは、FloatingDecimalのオブジェクトであり、問題の文字列では、以下のコンストラクタの値と同じ物を返します。
new FloatingDecimal(false, -307, "22250738585072012******", 17, false)
個々の引数は以下の内容です。
マイナスか? false 指数部 -307 仮数部の文字列 "22250738585072012******" ここで'*'は初期化されたままの文字'\u0000' 仮数部の文字数 17 異常な値か? false
すなわち、0.22250738585072012e-307 という数値をあらわしています。
次の doubleValue()は上記のオブジェクトから、doubleの値に変換しています。
変換は大きく分けて2段階で行っています。
後半は、より精度をあげるため、丸め幅(ulp:Units in the Last Place)の1/2になるまで、小さな値を足したり引いたりしています。
通常なら一度で誤差が1/2になり、ループから抜けます(多分)。
ところが、その判断の仕方が1ケタ間違っているため、無限にループを回っています。
問題の値では、以下のように足したり引いたりしながらいつまでも続きます。
2.2250738585072014E-308 2.225073858507201E-308 2.2250738585072014E-308 2.225073858507201E-308 2.2250738585072014E-308 2.225073858507201E-308 2.2250738585072014E-308 2.225073858507201E-308 ...
現象としては無限ループであり、CPU負荷があがり、処理が戻らなくなります。
そのプロセスが異常終了したり、メモリを食いつぶすことはないでしょう。
他のプロセスが誤動作することもないでしょう(CPU負荷を除けば)。
また、JavaVM上で動いているので、セキュリティ上の問題となることもないでしょう。
(保証はできませんが、ソースでの確認、および動作確認した感じではそうでした。)
コードを読んだ感じでは、他の値でも起こりうるのではないかと思います。
最初にtrim()しているので、前後に空白文字がある場合でも同様の現象が起きます。
例 " 2.2250738585072012e-308"
パターン2:指数部の文字がE/eの大文字小文字が異なる場合。
例 "2.2250738585072012E-308"
末尾にD,d,F,fがあっても読み飛ばしているだけなので、同様の現象が起きます。
例 "2.2250738585072012e-308D"
例 "-2.2250738585072012e-308"
例 "+2.2250738585072012e-308"
例 "0.22250738585072012e-307"
例 "22.250738585072012e-309"
例 "2.225073858507201200000000e-308"
例 "2.225073858507201244444444e-308"
(2011/03/02追記)KINさんのコメントにあるように、数字が続く場合なら常に起こるわけではなく、2進数にしたとき有効桁数に影響を及ぼさない程度の端数があるときです。
他にも同じ現象を起こすパターンがあるかもしれません。
しかし、Sun はテストしないで出荷してたのでしょうかね。コアな部分ですからもっと厳密なテストをして出荷されているものかと思っていましたが、案外抜けがあるものなのですね。
しかし低いとはいえ、影響範囲は広いので、悪用されないか心配してます。
ありがとうございます。
私も試しに再現させてみたのですが、パターン6については認識が違っていました。
例えば、以下の数値だと無限ループは発生しません。
"2.225073858507201245555555e-308"
私が色々と試行した結果、以下のような推測をしています。
・四捨五入して"2.2250738585072012e-308"になる範囲の値の場合に、無限ループが発生する。
(要するに、"2.22507385850720115e-308"以上、"2.22507385850720125e-308"未満の範囲の数値の場合に無限ループが発生する。)
パターン6の例は有効桁数の問題ではなくて、この数値の範囲に該当する数値だったため、無限ループが発生しているのではないかと思います。
もしお時間があれば、お試しください。
再度確認したところ、以下の範囲の値の場合に無限ループが発生することを確認しました。
"2.22507385850720114e-308"以上、"2.22507385850720126e-308"未満。
ですので、「四捨五入して〜」の下りは間違っています。
失礼いたしました。
パターン6については、再現する条件の記述が不十分でした。ご指摘ありがとうございます。
これについては2進数に変換した後の処理になるので、四捨五入ではなく、2進数にしたときの端数だと思います。