整数オーバーフローと浮動小数点数の限界
このトピックを修了すると
整数オーバーフローが発生する理由を理解し、浮動小数点数の精度の限界を説明でき、数値比較時に注意すべき点を理解できるようになります。
コンピューターは数字を間違える
print(0.1 + 0.2) # 0.30000000000000004print(0.1 + 0.2 == 0.3) # False!これはPythonのバグではありません。C、Java、JavaScript、Rust — すべての言語で同じです。コンピューターが小数を保存する方法が原因です。
整数オーバーフロー
ビット数と範囲
コンピューターでは、整数は固定されたビット数で表現されます。
8ビット符号なし整数: 0 ~ 255
8ビット符号付き整数: -128 ~ 127
32ビット符号付き整数: -2,147,483,648 ~ 2,147,483,647
64ビット符号付き整数: -9.2 × 10^18 ~ 9.2 × 10^18範囲を超えると、オーバーフローが発生します。
オーバーフローの実態
8ビット符号なし整数で、255 + 1 は?
11111111 (255)
+ 00000001 (1)
----------
100000000 (256 — ただし、9番目のビットが切り捨てられる!)
= 00000000 (0)255の次に0に戻ります! これがオーバーフローです。自動車の走行距離計が999,999から000,000に戻るのと同じです。
実際の事故例
// C言語 — 32ビット整数オーバーフロー
int balance = 2147483647; // 最大値
balance = balance + 1; // -2147483648! (正の数 → 負の数に反転)- アリアン5号機ロケット爆発 (1996): 64ビット値を16ビットに変換 → オーバーフロー → ロケットが自爆
- 江南スタイル YouTube 再生回数 (2014): 32ビット整数の限界 (21億) を超える → Googleが64ビットに変更
Pythonはオーバーフローがない
# Pythonは整数のサイズに制限がないbig = 2 ** 100print(big) # 1267650600228229401496703205376
bigger = 2 ** 1000print(len(str(bigger))) # 302桁!Pythonは、必要に応じてメモリを自動的に拡張します。しかし、C、Java、JavaScriptなどのほとんどの言語では整数のサイズが固定されているため、オーバーフローに注意する必要があります。
NumPyもオーバーフローが発生します。NumPy配列はCスタイルの固定サイズ整数を使用するためです。
import numpy as np
a = np.int32(2147483647)print(a + 1) # -2147483648 (オーバーフロー!)
b = np.int64(2147483647)print(b + 1) # 2147483648 (64ビットでは安全)浮動小数点数 — IEEE 754
0.1を2進数に変換すると
10進数で1/3 = 0.333333...が無限小数であるように、2進数でも0.1 = 0.0001100110011...が無限小数です。
0.1 (10進数) = 0.00011001100110011001100110011... (2進数、無限繰り返し)コンピューターは、有限のビット(64ビット)でこの無限小数を保存するため、どこかで切り捨てる必要があります。この切り捨てが誤差の原因です。
IEEE 754 — 64ビット浮動小数点数
[1ビット符号][11ビット指数][52ビット仮数]
0 01111111011 1001100110011001100110011001100110011001100110011010
符号: 0 (正の数)
指数: 実際の指数を決定
仮数: 有効数字 (52ビット ≈ 15〜17桁の精度)# 15〜17桁まで正確print(f"{0.1:.20f}") # 0.10000000000000000555print(f"{0.2:.20f}") # 0.20000000000000001110print(f"{0.3:.20f}") # 0.299999999999999988900.1も正確ではなく、0.2も正確ではないため、足した結果も正確ではありません。
実践的な問題と解決策
1. 比較 — 絶対に == で比較しない
# 悪いif 0.1 + 0.2 == 0.3: print("Equal") # 実行されない!
# 良い — 許容誤差(イプシロン)を使用epsilon = 1e-9if abs((0.1 + 0.2) - 0.3) < epsilon: print("Equal") # 実行される
# より良い — math.isclose() を使用import mathif math.isclose(0.1 + 0.2, 0.3): print("Equal") # 実行される2. 累積誤差 — 繰り返し計算で大きくなる
total = 0.0for _ in range(1000): total += 0.1print(total) # 99.99999999999857 (100ではない!)print(f"Error: {abs(total - 100):.15f}") # 0.0000000000014323. 金融計算 — Decimal を使用
from decimal import Decimal
# float — 誤差が発生price = 0.1 + 0.2print(price) # 0.30000000000000004
# Decimal — 正確price = Decimal('0.1') + Decimal('0.2')print(price) # 0.3 (正確!)
# 注意:文字列で初期化する必要があるDecimal(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625')Decimal('0.1') # Decimal('0.1') — 正確金額、税金、金利 — 正確性が重要な計算では、floatの代わりにDecimalを使用します。
JavaScriptの数値 — 整数も浮動小数点数
JavaScriptには整数の型はありません。すべての数値が64ビット浮動小数点数(IEEE 754)です。
// JavaScript — 大きな整数の精度損失
console.log(9007199254740992 === 9007199254740993); // true!
// 2つの数が同じと判断 — 52ビットの仮数の範囲を超えたため
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)このため、JavaScriptでBigIntが導入されました。
const big = 9007199254740993n; // BigIntリテラル (n接尾辞)
console.log(big === 9007199254740993n); // true — 正確Twitter(現在のX)のツイートIDが文字列で渡される理由もこれです。JSONの数値として解析すると精度が失われるため、APIがIDを文字列 "id_str" でも提供します。
特殊な値
# 無限大print(float('inf')) # infprint(float('inf') + 1) # infprint(float('inf') * -1) # -inf
# NaN (Not a Number)print(float('nan')) # nanprint(float('nan') == float('nan')) # False! (NaNは自分自身とも等しくない)
import mathprint(math.isnan(float('nan'))) # True (正しい確認方法)print(math.isinf(float('inf'))) # TrueNaNは、唯一自分自身とも==がFalseになる値です。NaNの確認には必ずmath.isnan()を使用してください。
重要なまとめ
| 概念 | まとめ |
|---|---|
| 整数オーバーフロー | 固定ビット数を超えると値が反転する。Pythonは例外(自動拡張) |
| IEEE 754 | 64ビット浮動小数点数の標準。52ビットの仮数 ≈ 15〜17桁の精度 |
| 0.1 + 0.2 ≠ 0.3 | 0.1が2進数で無限小数 → 保存時に切り捨て → 誤差が発生 |
| 比較 | == の代わりに math.isclose() を使用 |
| 金融計算 | float の代わりに Decimal を使用 |
| NaN | 自分自身とも等しくない値。math.isnan() で確認 |
「コンピューターは正確に計算する」という誤解があります。コンピューターは、有限のビットで無限の数を表現するため、根本的に精度に限界があります。この限界を理解すると、金融システムでDecimalを使用する理由、ゲームで座標が「震える」理由、科学計算で誤差分析が必要な理由が明確になります。
実践的なルール:整数はPythonなら安全、他の言語は範囲を確認。小数は== 絶対禁止、金融はDecimal。NaNはmath.isnan()で確認。