CUBは子供の白熊

Java SE 8 実践プログラミングの練習問題を解く

第7章 Nashorn JavaScript エンジンの活用 : 問題 3 : BigInteger と Number

問題

jjs を実行して、次の呼び出しを行え

var big = new java.math.BigInteger('1234567890987654321')
  1. big の値を表示するとどうなるか?
  2. big の下3桁big.mod(java.math.BigInteger('1000'))は何か?

解答

jjs> var big = new java.math.BigInteger('1234567890987654321')
jjs> big
1234567890987654400
jjs> big.mod(new java.math.BigInteger('1000'))
321

即ち、表示すると “有効桁数が足りなくて下3桁の値が変わっている” のに “内部的には指定した値を保持” している

さらに問題

なぜ奇妙な表示になるのか?

解答

big の内部形式はjava.math.BigIntegerだが

jjs> big.class
class java.math.BigInteger

JavaScript でのデータ型は Number でプライマリデータ型である

jjs> typeof big
number

JavaScript の Number 型は倍精度の浮動小数なので、big を表示する際にメソッド

public double doubleValue()

を呼び出してマーシャリングされるため

BigInteger big = new BigInteger("1234567890987654321");
double number = big.doubleValue();
System.out.println(number);
1.2345678909876544E18

倍精度では有効桁数が足りず17桁目以降が化けてしまう

さらに問題

実際の値はどうすれば表示できるか?

解答

toString()メソッド

最初に思いついたのはjava.math.BigIntegerクラスのtoString()メソッドを呼ぶこと

jjs> big.toString()
1234567890987654400
jjs> big.toString(10)
1234567890987654400

残念、駄目だった

実は、JavaScript の Number インスタンスtoString()メソッドを持っている
つまりbig.toString()は、JavaScriptメソッドが呼ばれているので、big はマーシャリングされて下3桁が欠けてしまう

Java.to()でキャスト

Java.to()メソッドを使ってjava.math.BigIntegerにキャストすれば、JavatoString()メソッドが呼べるのでは…

jjs> Java.to(big, java.math.BigInteger)
<shell>:1 TypeError: 1234567890987654400 is not an Object

これも駄目だった

■ 商と剰余に分ける

bigの場合だったら、以下のようにして商と剰余を求めればよい

jjs> var a = big.divide(new java.math.BigInteger('1000'))
jjs> var b = big.mod(new java.math.BigInteger('1000'))
jjs> a.toString() + b
1234567890987654321

どんな BigInteger でも正確な値を表示するには、次の関数を呼び出せばよい(これを1行で書くのは気が重いが…)

// BigInteger を文字列に変換
function bigToString(/* BigInteger */ num) {
    var result = "";
    // 符号
    if (num.signum() < 0) {
        result = "-";
        num = num.negate();
    }
    // 上位の桁から数字を求める
    for (var n = num.toString().length() - 1; n > 0; n--) {
        result += num.divide(java.math.BigInteger.TEN.pow(n)).toString();
        num = num.mod(java.math.BigInteger.TEN.pow(n));
    }
    result += num.toString();
    return result;
}