頂いたコメントより(そのうち)改良するので気長に待っててください
はじめに
記事のタイトルにある通りのことを友人に言ったのですが、信じてもらえませんでした=<
ちゃんと証明しようと思います。
そう考える理由
まず、 JavaScript
では、ちゃんとそういう仕様になっています。
MDNによるドキュメントやQiita のこの記事など見て貰えば分かるのですが、 倍精度浮動小数点数(主に double 型)
を用いています。
コンピュータで整数型は簡単に表せられますが、 JavaScript
の場合はその表現に double
を用いているのです。
面白いことに、その安全な範囲は─── あ、やっぱこれ以上は上に貼ったページを読んでください。長くなってしまうのでね。
で、 C++
の double
も JavaScript
の Number と同じ形式になっています(殆どの環境で)。なので、 C++ でも同じ様に処理する筈なのです(仮説1)。わざわざ表せられる範囲の整数を複雑に表現するメリットは無いですから。まあ環境によるかもしれないですけどね。
検証1:仮説1の通りになる(整数を正確に表すことができる)か
上記のソレは C++
でのお話なので、 C++
で検証します。
JavaScript
の方は省略します。ちゃんと連番で整数が出力されます。
環境は Windows 10 Pro x64 + Visual Studio 2019
になります。
VC++ソースコード(長いので折りたたみ)
#include <iostream>
#include <string>
#include <iomanip>
using std::cout;
using std::endl;
std::string printBinary(int num);
std::string printBinary(float num);
std::string printBinary(double num);
int main() {
for (double i = 0.0; i < 1024.0; i += 1.0) {
cout << "number: " << std::fixed << std::setprecision(8)
<< i << endl;
cout << "binary: "
<< printBinary(i) << endl;
cout << endl;
}
}
// int を2進数ビット列の文字列にする
std::string printBinary(int num) {
std::string str = "";
for (int i = sizeof(int) * 8 - 1; 0 <= i; --i) { // 一番上の桁から表示
str += ((num >> i) & 0x1) ? '1' : '0' ;
}
return str;
}
// float を2進数ビット列の文字列にする
std::string printBinary(float num) {
std::string str = "";
std::int32_t &pNum = *(std::int32_t *)(&num);
for (int i = sizeof(float) * 8 - 1; 0 <= i; --i) { // 一番上の桁から表示
str += ((pNum >> i) & 0x1) ? '1' : '0' ;
}
return str;
}
// double を2進数ビット列の文字列にする
std::string printBinary(double num) {
std::string str = "";
std::int64_t &pNum = *(std::int64_t *)(&num);
for (int i = sizeof(double) * 8 - 1; 0 <= i; --i) { // 一番上の桁から表示
str += ((pNum >> i) & 0x1) ? '1' : '0' ;
}
return str;
}
number: 0.00000000
binary: 0000000000000000000000000000000000000000000000000000000000000000
number: 1.00000000
binary: 0011111111110000000000000000000000000000000000000000000000000000
number: 2.00000000
binary: 0100000000000000000000000000000000000000000000000000000000000000
number: 3.00000000
binary: 0100000000001000000000000000000000000000000000000000000000000000
( 中略 )
number: 1020.00000000
binary: 0100000010001111111000000000000000000000000000000000000000000000
number: 1021.00000000
binary: 0100000010001111111010000000000000000000000000000000000000000000
number: 1022.00000000
binary: 0100000010001111111100000000000000000000000000000000000000000000
number: 1023.00000000
binary: 0100000010001111111110000000000000000000000000000000000000000000
ほら!やっぱり!
勿論 float
でも同じ様な結果になります。違いといえば binary の方に並ぶ 0 の数が減る程度です。
気になるのはビットの使い方です。そもそもが整数とは違いますが、それでも仮数部を用いて
3.0 = 00000000...00000011
となると思っていたのですが、どうやらそうでも無い様です。
(詳細については知るべきならちゃんと調べようと思っています。)
検証2: JavaScript
と同じだけ表せられるか
タイトルには整数を表せられることに加え、もう1つ「仮数部のビット数に入る数迄」は表現できるという仮説があります。
同じ使い方をするならば、 JavaScript
の Number.MAX_SAFE_INTEGER
は安全に正確に表現できる筈です。
// ( 省略 )
#include <iomanip>
#include <bitset>
#include <cmath>
using std::cout;
using std::endl;
// ( 省略 )
int main() {
int cnt = 0;
for (double i = std::pow(2, 53) - 50.0; cnt < 100; i += 1.0f, ++cnt) {
cout << "number: " << std::fixed << std::setprecision(8)
<< i << endl;
// ( 省略 )
number: 9007199254740942.00000000
binary: 0100001100111111111111111111111111111111111111111111111111001110
number: 9007199254740943.00000000
binary: 0100001100111111111111111111111111111111111111111111111111001111
( 中略 )
number: 9007199254740989.00000000
binary: 0100001100111111111111111111111111111111111111111111111111111101
number: 9007199254740990.00000000
binary: 0100001100111111111111111111111111111111111111111111111111111110
number: 9007199254740991.00000000
binary: 0100001100111111111111111111111111111111111111111111111111111111
number: 9007199254740992.00000000
binary: 0100001101000000000000000000000000000000000000000000000000000000
number: 9007199254740992.00000000
binary: 0100001101000000000000000000000000000000000000000000000000000000
( 中略 )
number: 9007199254740992.00000000
binary: 0100001101000000000000000000000000000000000000000000000000000000
number: 9007199254740992.00000000
binary: 0100001101000000000000000000000000000000000000000000000000000000
仮説通り、9007199254740991
が最大で、それ以降はぶっ壊れました。
なお、どちらの検証も、マイナスでも同じ様に正確に表現されます。
まとめ
キャストコストを恐れず整数は整数型で使おう!