目次
サンプル・プログラム
大きな整数
1972年(昭和47年)、インテルは8ビットCPU 8008 を発表した。その後、汎用レジスタやアドレスの基本幅は16ビット、32ビット、64ビットへ広がった。64ビットの並びは $2^{64}=18,446,744,073,709,551,616$ 通りあるが、符号の有無、命令、データ型によって表せる範囲は異なる。CPUのビット数とJavaScriptのNumber型の精度は、同じ意味ではない。
では、プログラムで扱える整数の大きさは、CPUのビット数に左右されるのかというと、そうであるときと、そうでないときがある。
JavaScriptの場合、Number.MAX_SAFE_INTEGER は、Number型で正確に区別できる「安全な整数」の最大値を表す。処理系ごとに変わる値でも、Number型で表せる最大の整数でもない。
minmax1.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: document.getElementById('var1').innerHTML = Number.MAX_SAFE_INTEGER;
23: };
24: </script>
浮動小数点数
JavaScriptで安全に扱える整数の最大値 Number.MAX_SAFE_INTEGER は $2^{53}-1=9,007,199,254,740,991$ である。これを超える整数もNumber型で表せるが、隣り合う整数を区別できなくなる場合がある。
安全な整数の最小値は Number.MIN_SAFE_INTEGER で定義され、$-(2^{53}-1)=-9,007,199,254,740,991$ となる。
Number型で表せる有限値の最大絶対値は約 1.7976931348623157×10308 である。最小の正の正規化数は約2.225×10-308だが、非正規化数を含めると0より大きい最小値は Number.MIN_VALUE、約4.94×10-324である。
整数の計算誤差
minmax2.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: let a = Number.MAX_SAFE_INTEGER;
23: document.getElementById('let1').innerHTML = 'a = ' + a.toLocaleString() + '...' + typeof a;
24:
25: //Numberの最大値+1
26: let b = a + 1;
27: document.getElementById('let2').innerHTML = 'b = ' + b.toLocaleString() + '...' + typeof b;
28:
29: //Numberの最大値+8
30: let c = a + 8;
31: document.getElementById('let3').innerHTML = 'c = ' + c.toLocaleString() + '...' + typeof c;
32:
33: //BigIntがあれば
34: if (typeof BigInt != 'undefined') {
35: let d = BigInt(a) + BigInt(8);
36: document.getElementById('let4').innerHTML = 'd = ' + d.toLocaleString() + '...' + typeof d;
37: }
38: };
39: </script>
BigInt型を使えば、このような誤差を起こすことなく計算できる。
JavaScriptは、安全な整数の範囲を外れても自動的にはエラーを出さない。正確な整数が必要なら Number.isSafeInteger で確認するか BigInt型 を使う。小数では、用途に応じて許容誤差、丸め規則、有効桁を決め、金額など10進数での正確さが必要な処理では整数化や10進演算ライブラリを検討する。
古いブラウザとの違い
minmax3.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: console.log(Number.MAX_SAFE_INTEGER);
23: let a;
24: if (typeof Number.MAX_SAFE_INTEGER != 'undefined') {
25: a = Number.MAX_SAFE_INTEGER;
26: } else {
27: a = Math.pow(2, 53) - 1;
28: }
29: document.getElementById('let1').innerHTML = 'a = ' + a + '...' + typeof a;
30:
31: //Numberの最大値+1
32: let b = a + 1;
33: document.getElementById('let2').innerHTML = 'b = ' + b + '...' + typeof b;
34:
35: //Numberの最大値+8
36: let c = a + 8;
37: document.getElementById('let3').innerHTML = 'c = ' + c + '...' + typeof c;
38:
39: //BigIntがあれば
40: if (typeof BigInt != 'undefined') {
41: let d = BigInt(a) + BigInt(8);
42: document.getElementById('let4').innerHTML = 'd = ' + d + '...' + typeof d;
43: }
44: };
45: </script>
2021年(令和3年)当時の実行結果を参考として掲げる。IE11ではBigIntの行が表示されないが、Number型の3行は同じ結果である。
●IE11(Windows 10 64bit)
a = 9007199254740991...number
b = 9007199254740992...number
c = 9007199254741000...number
●Edge 91.0.864.64(Windows 10 64bit)
a = 9007199254740991...number
b = 9007199254740992...number
c = 9007199254741000...number
d = 9007199254740999...bigint
●Chrome 91.0.4472.114(Windows 10 64bit)
a = 9007199254740991...number
b = 9007199254740992...number
c = 9007199254741000...number
d = 9007199254740999...bigint
●Chrome 91.0.4472.120(Android)
a = 9007199254740991...number
b = 9007199254740992...number
c = 9007199254741000...number
d = 9007199254740999...bigint
●Firefox 89.0.2(Windows 10 64bit)
a = 9007199254740991...number
b = 9007199254740992...number
c = 9007199254741000...number
d = 9007199254740999...bigint
●Safari(iOS 14.6)
a = 9007199254740991...number
b = 9007199254740992...number
c = 9007199254741000...number
d = 9007199254740999...bigint
小数の計算誤差
calcError1.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: let a = 6;
23: document.getElementById('let1').innerHTML = 'a = ' + a.toString() + ' ... ' + typeof a;
24:
25: let b = 5.4;
26: document.getElementById('let2').innerHTML = 'b = ' + b.toString() + ' ... ' + typeof b;
27:
28: let c = a - b;
29: document.getElementById('let3').innerHTML = 'a - b = ' + c.toString() + ' ... ' + typeof c;
30: };
31: </script>
a = 6 ... numberなぜ、小数第1位で計算誤差が出てしまうのか。これは、IEEE 754 浮動小数点数の計算方法に起因する。
b = 5.4 ... number
a - b = 0.5999999999999996 ... number
1.0未満の小数部は、分母が2のべき乗となる分数、すなわち $ \displaystyle \frac{1}{2} , \frac{1}{4} , \frac{1}{8} $...の組み合わせで表現しなければならない。
ところが $ 0.4 = 2^{-2} + 2^{-3} + 2^{-6} + 2^{-7} \cdots $ のように循環小数になってしまうのだ。
このため、簡単な減算であるにも関わらず、誤差が発生してしまった。
calcError2.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: const mag = 10; //倍率
23:
24: let a = 6;
25: document.getElementById('let1').innerHTML = 'a = ' + a.toString() + ' ... ' + typeof a;
26:
27: let b = 5.4;
28: document.getElementById('let2').innerHTML = 'b = ' + b.toString() + ' ... ' + typeof b;
29:
30: let c = (a * mag - b * mag) / mag;
31: document.getElementById('let3').innerHTML = 'a - b = ' + c.toString() + ' ... ' + typeof c;
32: };
33: </script>
calcError3.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: const mag = 100; //倍率
23:
24: let a = 20.42;
25: document.getElementById('let1').innerHTML = 'a = ' + a.toString() + ' ... ' + typeof a;
26:
27: let b = 10.42;
28: document.getElementById('let2').innerHTML = 'b = ' + b.toString() + ' ... ' + typeof b;
29:
30: let c = (a * mag - b * mag) / mag;
31: document.getElementById('let3').innerHTML = 'a - b = ' + c.toString() + ' ... ' + typeof c;
32: };
33: </script>
これは、整数化する100を乗ずる計算で循環小数になってしまうためだ。
金額など小数桁数が決まっている値は、入力を文字列として読み、最小単位の整数へ変換して計算する方法がある。測定値では有効数字と丸め規則を決め、比較するときは許容誤差を考慮する。一般的な10進演算が必要なら、検証された10進演算ライブラリを使う方が安全である。
同じIEEE 754の倍精度を使うPHP、Python、C++などでも、内部では同種の表現誤差が起こり得る。画面表示の桁数や丸め方によって、誤差が見えない場合がある。詳しくは「有限小数、循環小数、分数、無理数」をご覧いただきたい。
オーバーフロー
minmax4.html
19: <script>
20: //ページのロード時に実行
21: window.onload = function() {
22: const a = Number.MAX_VALUE;
23: document.getElementById('let1').innerHTML = 'a = ' + a + '...' + typeof a;
24:
25: //Numberの最大値×1.1
26: let b = a * 2;
27: document.getElementById('let2').innerHTML = 'b = ' + b + '...' + typeof b;
28:
29: //BigIntで2の1024乗を正確に計算
30: if (typeof BigInt != 'undefined') {
31: const c = 2n ** 1024n;
32: document.getElementById('let3').innerHTML = 'c = ' + c + '...' + typeof c;
33: }
34: };
35: </script>
計算オーバーフローが起き、変数bは文字列ではなくNumber型の特殊値 Infinity になる。
BigInt型 では $2^{1024}$ を正確な整数として計算できるが、Number型とBigInt型は用途が異なり、演算前に型をそろえる必要がある。
a = 1.7976931348623157e+308...number大きな数を扱う場合は、Infinity 対策をしておかないとプログラムが予期しない動きをする可能性がある。
b = Infinity...number
c = 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216...bigint
コラム:分数計算
Pythonには標準ライブラリの fractions があり、Fraction型で有理数を計算できる。10進小数を正確に扱いたい場合は、言語やライブラリが提供する10進型も選択肢になる。
コラム:BigInt型
BigInt型 では多くの算術演算子、ビット演算子、比較演算子を使える。ただし、Number型と混ぜた算術演算はできず、Mathオブジェクトの多くの関数もBigIntを受け付けない。乱数、べき剰余、素数判定はユーザー関数として用意する。
bigPrime.html
91: /**
92: * BigIntの乱数を求める.
93: * @param BigInt min 最小値
94: * @param BigInt max 最大値
95: * @return BigInt min以上max以下の乱数
96: * @throws RangeError minがmaxより大きい
97: */
98: function BigIntRandom(min, max) {
99: min = BigInt(min);
100: max = BigInt(max);
101: if (min > max) {
102: throw new RangeError('minはmax以下にしてください');
103: }
104:
105: const range = max - min + 1n;
106: const bitLength = range.toString(2).length;
107: const byteLength = Math.ceil(bitLength / 8);
108: const excessBits = byteLength * 8 - bitLength;
109: const bytes = new Uint8Array(byteLength);
110: let value;
111: do {
112: crypto.getRandomValues(bytes);
113: if (excessBits > 0) {
114: bytes[0] &= 0xff >>> excessBits;
115: }
116: value = 0n;
117: for (const byte of bytes) {
118: value = (value << 8n) | BigInt(byte);
119: }
120: } while (value >= range);
121:
122: return min + value;
123: }
bigPrime.html
159: /**
160: * ミラー・ラビン素数判定法により引数(BigInt)が素数かどうかを判定する.
161: * @param BigInt n 判定する整数
162: * @return boolean true:おそらく素数/false:合成数
163: */
164: function MillerRabinPrimalityTest(n, rounds = 20) {
165: if (n < 2n) {
166: return false;
167: }
168: if ((n == 2n) || (n == 3n)) {
169: return true;
170: }
171: if ((n & 1n) == 0n) {
172: return false;
173: }
174:
175: let d = n - 1n;
176: let s = 0;
177: while ((d & 1n) == 0n) {
178: d >>= 1n;
179: s++;
180: }
181:
182: for (let i = 0; i < rounds; i++) {
183: const a = BigIntRandom(2n, n - 2n);
184: let x = BigIntModpow(a, d, n);
185: if ((x == 1n) || (x == n - 1n)) {
186: continue;
187: }
188:
189: let witness = true;
190: for (let r = 1; r < s; r++) {
191: x = (x * x) % n;
192: if (x == n - 1n) {
193: witness = false;
194: break;
195: }
196: }
197: if (witness) {
198: return false;
199: }
200: }
201: return true;
202: }
素数判定には、「PHPとPythonで巨大素数を扱う」で紹介したミラー・ラビン素数判定を用いている。
ここで使う対数関数 BigIntLog、べき剰余関数 BigIntModpow は、前述の通り、ユーザー関数として用意した。
bigPrime.html
125: /**
126: * BigIntの対数を求める.
127: * @param BigInt n 整数
128: * @param BigInt base 基数
129: * @return BigInt 対数
130: */
131: function BigIntLog(n, base) {
132: let l = 0n;
133: let x = n;
134: while ((x = x / base) > 0n) {
135: l++;
136: }
137: return l;
138: }
bigPrime.html
140: /**
141: * BigIntのべき剰余を求める.
142: * @param BigInt base 基数
143: * @param BigInt exp 指数
144: * @param BigInt mod 除数
145: * @return BigInt べき剰余
146: */
147: function BigIntModpow(base, exp, mod) {
148: let res = 1n;
149: while (exp > 0n) {
150: if ((exp & 1n) == 1n) {
151: res = (res * base) % mod;
152: }
153: base = (base * base) % mod;
154: exp >>= 1n;
155: }
156: return res;
157: }
bigPrime.html
32: const MAX_DIGITS = 200; //判定できる整数の最大桁数
ECMAScript 仕様はBigIntの固定最大値を定めていないが、実際の上限はメモリや処理系に左右される。ミラー・ラビン素数判定は確率的判定なので、結果は「おそらく素数」であり、試行回数を増やすほど合成数を素数と誤判定する確率を下げられる。桁数が増えるほど生成・判定時間も長くなるため、このサンプルでは最大200桁に制限している。
コラム:表示と内部の値は同じとは限らない
短く表示されたからといって、内部の表現誤差が消えたわけではない。出力時は用途に合った有効数字と丸め方を明示し、計算時の精度とは分けて考える必要がある。
let a = 0.599999999999999;通常は0.599999999999999と表示される。このリテラルは、その値へ変換したときに同じbinary64値へ戻せる短い10進表記として出力される。
console.log(a);
次は、Pythonだ。
a = 0.599999999999999print関数 でも通常は0.599999999999999と表示される。ただし、Pythonのfloatにも2進浮動小数点数の表現誤差はある。
print(a);
それでは、PHPはどうか――
$a = 0.599999999999999;出力結果はPHPのバージョンや precision 設定に左右される。0.6と表示されても、内部計算が10進数で正確になったことを意味しない。
print($a);
最後に、C++はどうだろう。
double a = 0.599999999999999;既定の精度では0.6と表示されることがある。必要な桁数は std::setprecision などで指定する。C++のdoubleにも同じ種類の表現誤差があり、表示だけで精度を判断してはいけない。
std::cout << a << std::endl;
参考サイト
- ECMAScript 2025 Language Specification:Ecma International
- Web Cryptography API:W3C
- Floating-Point Arithmetic: Issues and Limitations:Python Software Foundation
- fractions — Rational numbers:Python Software Foundation
- More precise float value handling:PHP
- Internet Explorer 11:Microsoft

また、コラム欄では、任意精度の整数を扱う BigInt型 を使って、巨大な素数候補を生成・判定するプログラムを紹介する。BigIntは整数専用であり、小数は扱えない。