大きな整数で、PHP は黙って上限に張りつき、JavaScript は桁が化けた ― 同じ仕様、違う答え④

Go
B!

数字の文字列を受け取り、「1,000,000」のような3桁区切りと、「100万」のような万進の表記に直す。そんな計算を、4言語で書きました。

数字の文字列を数に変えるところでは、どの言語でも標準の方法を使っています。

言語使った変換
PHP(int) $value
Pythonint(value)
Gostrconv.ParseInt(value, 10, 64)
JavaScriptNumber(value)

ここに 1,000,000,000,000,000,000,000(10^21、10垓)を入れると、答えは3通りに割れました。

言語3桁区切りの答え
Python1,000,000,000,000,000,000,000正解
JavaScript1,000,000,000,000,000,000,000正解(たまたま)
PHP9,223,372,036,854,775,807不正解(エラーなし)
Goなしエラーで止まる

PHP: 64ビットの上限で、黙って頭打ちになる

PHP の整数は64ビットで、上限は 9,223,372,036,854,775,807(2^63 − 1)です。(int) でこれより大きな数字の文字列を変換すると、エラーも警告も出さずに、上限の値になります。

var_dump((int) "1000000000000000000000");  // int(9223372036854775807)
var_dump((int) "9223372036854775808");     // int(9223372036854775807)

10^21 も、35桁の数(c015)も、同じ「922京3372兆368億5477万5807」になりました。上限ちょうど + 1(c012)は、1少ない値になっています。どれも、それらしい大きな数が表示されるので、間違いに気づきにくい壊れ方です。

JavaScript: 2^53 を超えると、桁が化ける

JavaScript の Number は倍精度の小数です。整数を正確に表せるのは 2^53(9,007,199,254,740,992)までで、それを超えると近い値に丸められます。

Number("9007199254740993")  // 9007199254740992(2^53 + 1 が 1 減る)

2^53 + 1(c010)は、末尾が 993 から 992 に変わりました。35桁の数(c015)では、万進の表記が「1000溝8608兆5072億5248万5552」という、元の数とまったく違う値になっています。

64ビットの上限(c011)では、3桁区切りが 9,223,372,036,854,776,000 になりました。toLocaleString は、正確でない下の桁を 0 にして見せます。

10^21 が「たまたま正解」なのは、10^21 がちょうど倍精度で正確に表せる値だったからです。

Go: 止まるが、間違いは返さない

Go の strconv.ParseInt は、64ビットに入らない数をエラーにします。

strconv.ParseInt: parsing "1000000000000000000000": value out of range

答えは返せませんが、間違った答えも返しません。この中では一番安全な失敗です。前回の負の数のケースと同じく、Go は「止まる」側に倒れています。

Python は、そのまま正しい

Python の int には桁数の上限がありません。この題材では、よくある書き方がそのまま正解でした。

どう直したか ― 桁数に上限のない整数を使う

言語使ったもの
PHPGMP(gmp_init)
Pythonint のまま
Gomath/big の big.Int
JavaScriptBigInt
BigInt("9007199254740993").toString()  // "9007199254740993"

3桁区切りや万進の表記も、64ビットの整数に戻さずに作る必要があります。PHP の number_format は、64ビットを超える数を渡すと小数(float)に変えて処理するので、下の桁が正しく出ません。

echo number_format("99999999999999999999");  // 100,000,000,000,000,000,000

そこで、数字の文字列に自分でカンマを入れています。

直した後は、4言語とも16件中16件が正解と一致しました。

まとめ

  • 64ビットを超える数を入れると、PHP は黙って上限に張りつき、JavaScript は 2^53 を超えたところから桁が化け、Go は止まった
  • 一番見つけにくいのは、それらしい大きな数を黙って返す PHP と JavaScript。Go のように止まる方が安全
  • 桁数に上限のない整数(GMP・BigInt・big.Int)を使う。3桁区切りなどの表示用の関数も、大きな整数で確かめる

この記事のケースの正解表・4言語の結果・直す前と後のコードの違いは、次のページで見られます。

次回は、日本で動かしたときだけ、JavaScript の日付が1日ずれたケースを取り上げます。

連載「同じ仕様、違う答え」(全6回)
  1. 第1回:「四捨五入」と書いたのに、4言語で答えが割れた
  2. 第2回:1,100 ÷ 1.1 が 1,000 にならない
  3. 第3回:負の数を入れたら、Go は止まり、JavaScript は NaN を返した
  4. 第4回:大きな整数で、PHP は黙って上限に張りつき、JavaScript は桁が化けた(この記事)
  5. 第5回:日本で動かしたときだけ、JavaScript の日付が1日ずれた
  6. 第6回:1月31日の「1か月後」はいつか
B!
← 一覧へ戻る