AI生成コンテンツ / AI-generated content
目次
サンプル・プログラム
大きな整数
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$ となる。
「正規化数」は有効数字の先頭を1にそろえた数であり、「非正規化数」は0に近い領域で精度を減らして表す数である。
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位までの値を使っただけなのに、なぜ誤差が出るのか。これは、5.4を2進数の有限桁で正確に表せず、保存する時点で近似値に丸めるためである。
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進数では0.011001100110…と同じ並びが続く循環小数である。分数で書くと $ 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>
金額など小数桁数が決まっている値は、入力を文字列として読み、最小単位の整数へ変換して計算する方法がある。測定値では有効数字と丸め規則を決め、比較するときは許容誤差を考慮する。一般的な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の最大有限値×2
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有限の計算結果かどうかは Number.isFinite で確認できる。ただし、最大値を少しでも超える数学上の結果が必ずInfinityになるわけではない。たとえばNumber.MAX_VALUE + 1は、丸められてNumber.MAX_VALUEのままになる。
b = Infinity...number
c = 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216...bigint
小さすぎる値とNaNの確認
Number.isFinite(Infinity); // falseNumber.isFiniteは有限かどうかを調べるもので、誤差の有無は判定しない。NaNはNaN自身とも等しくならないため、NaN === NaNではなくNumber.isNaNを使う。
Number.isFinite(NaN); // false
Number.isFinite(0.6); // true
Number.isNaN(0 / 0); // true
コラム:分数計算
AI生成コンテンツ / AI-generated content
コラム:BigInt型
BigInt型 では多くの算術演算子、ビット演算子、比較演算子を使える。ただし、Number型と混ぜた算術演算はできず、Mathオブジェクトの多くの関数もBigIntを受け付けない。乱数、べき剰余(累乗した数を割った余り)、素数判定はユーザー関数として用意する。
大きな整数は最初から12345678901234567890n、またはBigInt("12345678901234567890")のように書く。Number型で丸められた値を後からBigIntへ変換しても、失われた桁は戻らない。逆にBigIntをNumberへ変換すると精度を失う場合がある。
また、5n / 2nは2nであり、小数部分は0に近い方向へ切り捨てる。0nでの除算はInfinityではなく例外となる。
サンプルの素数とは、2以上で、1と自分自身以外の正の整数では割り切れない整数のことである。2、3、5、7などが該当し、1は素数ではない。このサンプルの実行には、BigIntとcrypto.getRandomValuesに対応したブラウザが必要である。
bigPrime.html
96: /**
97: * BigIntの乱数を求める.
98: * @param BigInt min 最小値
99: * @param BigInt max 最大値
100: * @return BigInt min以上max以下の乱数
101: * @throws RangeError minがmaxより大きい
102: */
103: function BigIntRandom(min, max) {
104: min = BigInt(min);
105: max = BigInt(max);
106: if (min > max) {
107: throw new RangeError('minはmax以下にしてください');
108: }
109:
110: const range = max - min + 1n;
111: if (range === 1n) return min;
112: const bitLength = (range - 1n).toString(2).length;
113: const byteLength = Math.ceil(bitLength / 8);
114: const excessBits = byteLength * 8 - bitLength;
115: const bytes = new Uint8Array(byteLength);
116: let value;
117: do {
118: crypto.getRandomValues(bytes);
119: if (excessBits > 0) {
120: bytes[0] &= 0xff >>> excessBits;
121: }
122: value = 0n;
123: for (const byte of bytes) {
124: value = (value << 8n) | BigInt(byte);
125: }
126: } while (value >= range);
127:
128: return min + value;
129: }
bigPrime.html
170: /**
171: * ミラー・ラビン素数判定法により引数(BigInt)が素数かどうかを判定する.
172: * @param BigInt n 判定する整数
173: * @param Number rounds 試行回数(正の安全な整数)
174: * @return boolean true:おそらく素数/false:素数ではない
175: */
176: function MillerRabinPrimalityTest(n, rounds = 20) {
177: if (typeof n !== 'bigint') throw new TypeError('BigIntを指定してください');
178: if (!Number.isSafeInteger(rounds) || rounds < 1) throw new RangeError('試行回数は正の安全な整数にしてください');
179: if (n < 2n) {
180: return false;
181: }
182: if ((n == 2n) || (n == 3n)) {
183: return true;
184: }
185: if ((n & 1n) == 0n) {
186: return false;
187: }
188:
189: let d = n - 1n;
190: let s = 0;
191: while ((d & 1n) == 0n) {
192: d >>= 1n;
193: s++;
194: }
195:
196: for (let i = 0; i < rounds; i++) {
197: const a = BigIntRandom(2n, n - 2n);
198: let x = BigIntModpow(a, d, n);
199: if ((x == 1n) || (x == n - 1n)) {
200: continue;
201: }
202:
203: let witness = true;
204: for (let r = 1; r < s; r++) {
205: x = (x * x) % n;
206: if (x == n - 1n) {
207: witness = false;
208: break;
209: }
210: }
211: if (witness) {
212: return false;
213: }
214: }
215: return true;
216: }
素数判定には、「PHPとPythonで巨大素数を扱う」で紹介したミラー・ラビン素数判定を用いている。
判定には、べき剰余関数 BigIntModpow を使う。補助関数 BigIntLog も掲載するが、現在の判定処理からは呼び出していない。BigIntLogは対数の小数部分を切り捨てた整数を返す関数で、たとえばBigIntLog(999n, 10n)は2nとなる。
bigPrime.html
131: /**
132: * BigIntの対数を求める.
133: * @param BigInt n 整数
134: * @param BigInt base 基数
135: * @return BigInt 対数の整数部分(切り捨て)
136: */
137: function BigIntLog(n, base) {
138: if (typeof n !== 'bigint' || typeof base !== 'bigint') throw new TypeError('BigIntを指定してください');
139: if (n < 1n || base < 2n) throw new RangeError('nは1以上、baseは2以上にしてください');
140: let l = 0n;
141: let x = n;
142: while ((x = x / base) > 0n) {
143: l++;
144: }
145: return l;
146: }
bigPrime.html
148: /**
149: * BigIntのべき剰余を求める.
150: * @param BigInt base 基数
151: * @param BigInt exp 指数
152: * @param BigInt mod 除数
153: * @return BigInt べき剰余
154: */
155: function BigIntModpow(base, exp, mod) {
156: if (typeof base !== 'bigint' || typeof exp !== 'bigint' || typeof mod !== 'bigint') throw new TypeError('BigIntを指定してください');
157: if (exp < 0n || mod <= 0n) throw new RangeError('指数は0以上、除数は1以上にしてください');
158: base = ((base % mod) + mod) % mod;
159: let res = 1n % mod;
160: while (exp > 0n) {
161: if ((exp & 1n) == 1n) {
162: res = (res * base) % mod;
163: }
164: base = (base * base) % mod;
165: exp >>= 1n;
166: }
167: return res;
168: }
bigPrime.html
32: const MAX_DIGITS = 200; //判定できる整数の最大桁数
ECMAScript 仕様はBigIntの固定最大値を定めていないが、実際の上限はメモリや処理系に左右される。ミラー・ラビン素数判定は確率的判定なので、結果は「おそらく素数」であり、試行回数を増やすほど合成数を素数と誤判定する確率を下げられる。桁数が増えるほど生成・判定時間も長くなるため、このサンプルでは最大200桁に制限している。処理中は画面操作が一時的に止まることがある。
コラム:表示と内部の値は同じとは限らない
AI生成コンテンツ / AI-generated content
短く表示されたからといって、内部の表現誤差が消えたわけではない。出力時は用途に合った有効数字と丸め方を明示し、計算時の精度とは分けて考える必要がある。
let a = 0.599999999999999;通常は0.599999999999999と表示される。Number型の標準的な文字列化では、読み戻したときに同じ内部の値となる、できるだけ短い10進表記を使う。console.logの表示方法そのものは実行環境にも依存する。
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:Number型とBigInt型:Ecma International
- ECMAScript 2025:Numberの定数と判定関数:Ecma International
- Web Cryptography API:W3C
- Floating-Point Arithmetic: Issues and Limitations:Python Software Foundation
- fractions — Rational numbers:Python Software Foundation
- precision:浮動小数点数の表示精度:PHP
- C++規格草案:入出力ストリームの初期設定:C++ Working Draft
- The Intel 8008:Intel
- Handbook of Applied Cryptography:Public-Key Parameters:Menezes・van Oorschot・Vanstone
- Internet Explorer 11:Microsoft
