計算可能性と決定不能問題(停止性問題)
なぜ完璧なバグ検出器やリンタが原理的に作れないのか。停止性問題の決定不能性を理解すると、静的解析ツールの限界と近似という設計判断が腑に落ちる。
- 1.チューリングマシンは計算の数学モデルで、現実の言語が解ける問題の範囲(計算可能性)を厳密に定義する。
- 2.停止性問題(任意のプログラムと入力に対し、それが停止するか否かを常に正しく判定する)は決定不能で、そんなプログラムは原理的に存在しない。
- 3.帰結として「あらゆるコードの性質を完全・正確に静的解析する」ことは不可能。実用ツールは健全性か完全性のどちらかを捨て、近似で妥協している。
なぜこの話が実務に効くのか
「コードを実行せずに、無限ループするか、null参照を起こすか、デッドコードかを完璧に判定するツールが欲しい」——この願いは、原理的に叶いません。リンタや型検査器がときに「保守的すぎる警告」を出し、ときに「見逃し」をするのは、ツールの実装が未熟だからではなく、完全かつ正確な静的解析が数学的に不可能だからです。その根拠が停止性問題の決定不能性です。
チューリングマシンと計算可能性
チューリングマシンは、無限長のテープ・ヘッド・有限個の状態と遷移規則だけからなる、極限まで単純化した計算モデルです。重要なのは性能ではなく境界を定めることにあります。
チャーチ=チューリングのテーゼは「直感的に『計算できる』ものは、すべてチューリングマシンで計算できる」と主張します。λ計算・再帰関数・現実のプログラミング言語(チューリング完全なもの)は、どれも計算能力が等価であることが証明されています。つまり Python で書けない計算は C でも書けず、その逆も成り立ちません。
ある問題が「決定可能」とは、入力を与えると必ず有限時間で停止し、Yes/Noを正しく返すアルゴリズムが存在することです。停止しない可能性が残るものは決定可能とは呼びません。停止性問題は、この意味で決定不能です。
停止性問題:その主張と証明の骨格
停止性問題とは「任意のプログラム P と入力 x を与えたとき、P(x) が停止するか無限に走り続けるかを、常に正しく判定する」という問題です。これを解くプログラム halts(P, x) は存在しません。
証明は対角線論法による背理法です。仮に正しい halts が存在したとして、次の意地悪なプログラム trap を作ります。
def trap(P):
if halts(P, P): # P に「P自身」を入力したら停止する?
while True: # 「停止する」なら、あえて止まらない
pass
else:
return # 「止まらない」なら、すぐ止まる
ここで trap(trap) を考えます。halts(trap, trap) が「停止する」と判定すれば、定義上 trap(trap) は無限ループに入り停止しません。逆に「停止しない」と判定すれば、trap(trap) はすぐ return して停止します。どちらの判定も実際の挙動と矛盾します。よって最初の仮定(正しい halts の存在)が誤りです。
特定のプログラムが停止するかは、人間が読めば分かることも多いです。決定不能が言っているのは**「あらゆるプログラム×あらゆる入力」を、ひとつの汎用アルゴリズムが正しく裁ける**ことの否定。反例がひとつでもあれば「常に正しい判定器」は存在しません。
ライスの定理:話は停止だけではない
停止性は氷山の一角です。ライスの定理は「プログラムが計算する関数の、自明でない意味的性質はすべて決定不能」と述べます。「この関数は常に正の値を返すか」「この入力で例外を投げるか」「2つの関数は等価か」——これらは原理的に完全判定できません。
| 問い(プログラムの性質) | 決定可能か | 理由・補足 |
|---|---|---|
| 構文が文法に合っているか | 決定可能 | 構文は意味でなく形。パーサで有限時間で判定できる |
| この行は到達不能(デッドコード)か | 決定不能 | 到達には実行時の意味が絡む。停止性に帰着する |
| この変数は使用前に必ず初期化されるか | 決定不能(一般には) | 完全判定は不可。実用ツールは保守的に近似する |
| 2つのプログラムが同じ関数を計算するか | 決定不能 | ライスの定理が直接適用される意味的性質 |
| この正規表現が文字列にマッチするか | 決定可能 | 正規言語はチューリング完全でなく有限オートマトンで解ける |
最終行が鍵です。正規表現エンジンが静的に振る舞いを解析できるのは、対象がチューリング完全より弱い計算モデル(有限オートマトン)だからです。表現力を絞れば決定可能性が戻る——これが実務での回避策の本質です。
実務的含意:健全性と完全性のトレードオフ
完全な解析が無理なら、ツールはわざと間違える方向を選ぶしかありません。静的解析の評価軸は次の2つです。
| 性質 | 意味 | 犠牲にすると起きること |
|---|---|---|
| 健全(sound) | 問題があれば必ず警告する=見逃しゼロ | 安全側に倒すため、問題ない箇所にも警告(偽陽性)が増える |
| 完全(complete) | 警告したものは本当に問題=偽陽性ゼロ | 確実な箇所だけ報告するため、見逃し(偽陰性)が出る |
決定不能性ゆえに、汎用の解析で両方を同時に100%達成することはできません。だから各ツールは立場を決めています。
- 型検査器は健全性を重視し、「実行時には起きないが証明できない」コードも弾く(コンパイルエラー)。だから安全だが、ときに余計に厳しい。詳細は型システムへ。
- 多くのリンタやバグ検出器は完全性寄りで、確度の高い問題だけ報告して偽陽性を抑える。代わりに見逃しが残る。
- 「到達不能コード」警告が出ないことがあるのも、判定が一般に決定不能で、安全に判定できる範囲に限っているからです。
「停止性問題が決定不能なら、なぜ無限ループ検出ツールが存在するのか」——答え:それらは健全でも完全でもない近似だから。明らかなパターン(while(true) で抜け道なし等)は検出できるが、すべての無限ループを漏れなく・誤りなく検出するわけではない。決定不能性が否定するのは「完全かつ正確な汎用判定器」の存在だけです。
チューリング完全という諸刃の剣
設定ファイルやテンプレート言語が「チューリング完全になってしまった」という話を聞きます。表現力が上がる一方で、その設定が停止するか・何を出力するかを静的に保証できなくなることを意味します。逆に、意図的に表現力を制限した言語(全関数が停止する全域言語、SQL の中核、正規言語など)は、停止性や一部の性質を静的に保証できます。
「何でも書ける」表現力は、「何も保証できない」とほぼ同義です。解析や最適化の恩恵が欲しい領域(ビルド設定、クエリ、ポリシー記述)では、あえてチューリング完全を避けるのが定石。表現力と検証可能性はトレードオフだと意識しましょう。
まとめ
- チューリングマシンは計算可能性の境界を定める数学モデル。チャーチ=チューリングのテーゼにより、現実の汎用言語の計算能力はすべて等価です。
- 停止性問題は決定不能で、対角線論法により「常に正しい停止判定器」は存在しないと証明されます。ライスの定理はこれを「自明でない意味的性質すべて」へ拡張します。
- だから完全かつ正確な静的解析は原理的に不可能。実用ツールは健全性か完全性を捨て、近似と保守的判定で折り合いをつけています。ツールの偽陽性・偽陰性は欠陥ではなく、理論的限界の現れです。
計算量の上限(Big-O)が「どれだけ速く解けるか」の話なら、計算可能性は「そもそも解けるのか」というさらに手前の問いです。両者を区別できると、「遅い」と「不可能」を取り違えずに設計判断ができます。
プログラミング Article
計算可能性と決定不能問題(停止性問題)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
計算可能性
比較で見る軸
難易度: advanced / カテゴリ: プログラミング / タグ数: 5
導入後に効く点
停止性問題(任意のプログラムと入力に対し、それが停止するか否かを常に正しく判定する)は決定不能で、そんなプログラムは原理的に存在しない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- プログラミング
- タグ数
- 5
判断チェックリスト
- 自社の用途が「計算可能性 / 停止性問題」に近いか確認する。
- 強みである「チューリングマシンは計算の数学モデルで、現実の言語が解ける問題の範囲(計算可能性)を厳密に定義する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。