2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「浮動小数点型に整数を代入した時は仮数部のビット数に入る数迄は絶対に誤差は出ないんだって!」の検証

Last updated at Posted at 2021-05-07

頂いたコメントより(そのうち)改良するので気長に待っててください

はじめに

記事のタイトルにある通りのことを友人に言ったのですが、信じてもらえませんでした=<
ちゃんと証明しようと思います。

そう考える理由

まず、 JavaScript では、ちゃんとそういう仕様になっています。
MDNによるドキュメントQiita のこの記事など見て貰えば分かるのですが、 倍精度浮動小数点数(主に double 型) を用いています。
コンピュータで整数型は簡単に表せられますが、 JavaScript の場合はその表現に double を用いているのです。
面白いことに、その安全な範囲は─── あ、やっぱこれ以上は上に貼ったページを読んでください。長くなってしまうのでね。

で、 C++doubleJavaScript の Number と同じ形式になっています(殆どの環境で)。なので、 C++ でも同じ様に処理する筈なのです(仮説1)。わざわざ表せられる範囲の整数を複雑に表現するメリットは無いですから。まあ環境によるかもしれないですけどね。

検証1:仮説1の通りになる(整数を正確に表すことができる)か

上記のソレは C++ でのお話なので、 C++ で検証します。
JavaScript の方は省略します。ちゃんと連番で整数が出力されます。

環境は Windows 10 Pro x64 + Visual Studio 2019 になります。

VC++ソースコード(長いので折りたたみ)
main.cpp
#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つ「仮数部のビット数に入る数迄」は表現できるという仮説があります。
同じ使い方をするならば、 JavaScriptNumber.MAX_SAFE_INTEGER は安全に正確に表現できる筈です。

main.cpp(変更部分ハイライト)
// ( 省略 )
#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 が最大で、それ以降はぶっ壊れました。

なお、どちらの検証も、マイナスでも同じ様に正確に表現されます。

まとめ

キャストコストを恐れず整数は整数型で使おう!

2
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?