型拡張(Type Widening)とは、数値演算におけるオーバーフローを防ぐために変数や中間計算のデータ型をよりビット幅の広い型へ昇格させる修正手法です。

コーディング支援ツールや AI エージェントに整数オーバーフローのバグ修正を任せると、「// i128: … can exceed u32 and i64」といった自信満々の理由コメントが付与される場面によく遭遇します。レビュアーはその解説を読んで「安全側に倒してくれたなら問題ないだろう」と安易にプルリクエストを承認してしまいがちです。

筆者が個人で開発している Rust 製シミュレーションゲーム(以下、プロジェクトA)において、まさにこの現象が発生しました。AI worker が商業収容計算を i128 に広げ、「和と積が u32 と i64 を超え得る」ともっともらしいコメントを添えた差分を提出してきたのです。

しかし、定数定義と入力上限から最悪値を手計算したところ、計算値は約 8.59 × 10^13 であり、i64::MAX(約 9.22 × 10^18)に対して約 10^5 倍もの開きがありました。コメントの「exceed i64」は完全な虚偽だったのです。以下、AI が出力する理由コメントを鵜呑みにせず、手元で上限を検算して適切な型に留めるレビュー作法と、過剰な 128bit 化がもたらす技術的代償を実務ログに基づいて解説します。

なぜ「もっともらしい理由コメント」を検算する必要があるのか:実務での選択と虚偽の露呈

直前の成功体験に引きずられた型拡張の連鎖

プロジェクトAでは、今回の課題に先立って2件の整数オーバーフロー修正が行われていました。人口剰余計算や冒険者流入計算において、実際に中間計算が u64::MAX を超え得る数理的根拠が存在したため、それらの修正では u128 への型拡張が行われ、理由コメントが付与されていました。

その直後に起票された課題(Issue)の完了条件には、「直し方は先行修正に揃える(中間の計算を広い型で組む)」と明記されていました。この先行する成功パターンに同調する形で、worker(Claude Sonnet 5.5)により 128bit 化が選択されたと推測されます。

文脈同調やプロンプトの前提に引きずられて誤った判断を下す現象は、AI コードレビューにおけるハルシネーションの類型(Prompt-Induced Errors)としても知られています1。

worker による i128 化と「i64 も超える」というコメント

worker が提出した修正差分では、商業収容計算の中間変数が i128 に広げられ、コード内に次のようなコメントが添えられていました。

引用元: プロジェクトA(非公開)の migration.rs

ファイル: migration.rs(抜粋)

     let commerce = if total == 0 {
         1000
     } else {
-        (i64::from((f.inn + f.tavern) * COMMERCE_RESIDENTS_PER_SCALE) * 1000 / total).min(1000)
+        // i128: inn/tavern (u32) are unbounded, so the sum and product can exceed u32 and i64.
+        let residents =
+            (i128::from(f.inn) + i128::from(f.tavern)) * i128::from(COMMERCE_RESIDENTS_PER_SCALE);
+        (residents * 1000 / i128::from(total)).min(1000) as i64
     };

元の計算式 (f.inn + f.tavern) * COMMERCE_RESIDENTS_PER_SCALE * 1000 / total に対し、inn と tavern を i128 へキャストし、乗算結果全体を i128 で計算した上で最終的に as i64 で縮小キャストしています。同時に追加されたテストは、極端な施設値(u32::MAX)を設定して panic しないことだけを確認しており、計算値自体の妥当性やアサーションは含んでいませんでした。

レビュー指示による定数検算と「i64 超過」の否定

司令塔セッションによるレビューにおいて、diff に書かれたコメント「sum and product can exceed u32 and i64」の真偽を定数の実値で裏取りするよう明示的に指示しました。

AI レビューで拾える欠陥の種類は「レビューを入れたか」より「そのレビュアーに何を読ませたか」で決まります。過去に検証した AIレビュアーへの暗黙の前提漏洩 とも通底する視点です。

レビューサブエージェントが定数 COMMERCE_RESIDENTS_PER_SCALE = 10 を参照して最悪値を厳密に検算した結果は以下の通りでした。

  • 入力上限: f.inn = u32::MAX(4,294,967,2952)、f.tavern = u32::MAX(4,294,967,295)
  • 和の最大値: 4,294,967,295 + 4,294,967,295 = 8,589,934,590(u32::MAX を超過。コメントの「exceed u32」は真実)
  • 定数倍の最大値: 8,589,934,590 × 10 = 85,899,345,900
  • 分子の最悪値(千分率係数倍): 85,899,345,900 × 1000 = 85,899,345,900,000(約 8.59 × 10^13)
  • i64::MAX との比較: i64::MAX = 9,223,372,036,854,775,807(約 9.22 × 10^183)
  • 余裕度: i64::MAX / 最悪値 ≈ 107,374 倍(約 1.07 × 10^5 倍の開き)

定数が約100万を超えない限り、i64 は絶対にオーバーフローしません。

「exceed i64」は完全な虚偽であり、AI がプロジェクト全体の定数定義コンテキストを追跡できずに誤認(False Positive)した結果と推測されます4。

メリットの裏にあるトレードオフと「念のため 128bit」の代償

反論:「大は小を兼ねるのだから、念のため 128bit にしておけば安全ではないか?」

ここで当然考えられる反論は、「たとえ i64 で足りていたとしても、i128 にしておけば将来定数が変更された際にも壊れないのだから、過剰に広い型にしておく方が防衛的プログラミングとして安全ではないか?」という意見です。

しかし、「念のため」で行う安易な型拡張には、無視できない3つの技術的代償が存在します。

1. ハードウェア命令の喪失とランタイム除算関数呼び出し

多くの 64bit アーキテクチャ(x86_64 や AArch64)には、ネイティブな 128bit 整数除算命令が存在しません。

コンパイラ(rustc -O)が生成するアセンブリコードを比較すると、その違いは顕著です。

  • i64 除算: AArch64 では sdiv、x86_64 では cqo + idiv という単一または最小限のインラインハードウェア命令列で完結し、即座に実行されます。
  • i128 除算: ネイティブ命令がないため、LLVM compiler-rt のランタイム関数 __divti3 5 へのサブルーチン呼び出し(bl ___divti3)を生成します。

ランタイム関数呼び出しが発生すると、ABI 上のレジスタ退避・復元(スタックフレーム確保)やサブルーチン分岐が生じ、インライン完結する 64bit 除算に比べてオーバーヘッド要因となります。

2. 周囲のコードベースとの整合性破壊と不要なキャスト

同一関数内のわずか6行上にある jobs の計算では、同様の施設値計算が i64 で行われていました。

ここだけを i128 にすると、関数の戻り値のために as i64 という縮小キャストが必要になり、分母の total も i128::from(total) へ変換する必要が生じるなど、不要な型キャストのノイズが周囲のコードベースに散乱します。

3. 嘘の前提コメントがコードベースに固定化されるリスク

最も有害なのは、「i64 も超える」という誤った理由コメントがコードベースに残存することです。

将来コードを読む人間や次世代の AI エージェントがそのコメントを真実だと信じ込み、「ここは i64 に戻してはいけない危険な領域だ」と誤ったメンタルモデルを継承してしまう原因になります。

是正と反証テストによる検証

司令塔から worker への差し戻し指示により、型を i64 に戻し、不要なキャストを削り、コメントを「the sum and product of inn/tavern (u32) can exceed u32.」に変更して事実に合わせました。

テストが通っていることを仕様が守られている証拠として過信してはならず、既存テスト自体が弱い挙動を前提に書き換わっていないか疑う姿勢が不可欠です。

司令塔は承認前に一時 worktree を作成し、修正前のコードに対して追加テストを実行しました。その結果、migration.rs:102:20 で attempt to add with overflow による panic が実際に発生することを確認できました。バグが実在し、かつ i64 への修正によってそのバグが確実に解消されていることを実証したのです。

引用元: プロジェクトA(非公開)の migration.rs

ファイル: migration.rs(抜粋)

     let commerce = if total == 0 {
         1000
     } else {
-        // i128: inn/tavern (u32) are unbounded, so the sum and product can exceed u32 and i64.
+        // i64: the sum and product of inn/tavern (u32) can exceed u32.
         let residents =
-            (i128::from(f.inn) + i128::from(f.tavern)) * i128::from(COMMERCE_RESIDENTS_PER_SCALE);
-        (residents * 1000 / i128::from(total)).min(1000) as i64
+            (i64::from(f.inn) + i64::from(f.tavern)) * i64::from(COMMERCE_RESIDENTS_PER_SCALE);
+        (residents * 1000 / total).min(1000)
     };

修正前コードに対して追加テストを実行した際の反証テストログは以下の通りです。

引用元: プロジェクトA(非公開)の migration_test.rs

thread 'test_extreme_commerce_facilities_do_not_overflow_attractiveness' (11054739) panicked at crates/trillion-core/src/migration.rs:102:20:
attempt to add with overflow
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 10 filtered out; finished in 0.00s

前提が変われば結論はどう変わるか

定数の変更可能性とドメイン境界

今回 i64 で十分と断定できたのは、定数 COMMERCE_RESIDENTS_PER_SCALE = 10 が明確に定義されていたためです。

もし将来のゲームバランス調整でこの定数が 10^6(100万)を超える規模に引き上げられる可能性があるなら、あるいは外部設定ファイルから動的に巨大な値が注入されるアーキテクチャであれば、128bit 化や saturating_* 演算の導入が必要になります。

型の選定は「念のため」という感覚ではなく、「現在のドメイン境界(定数と入力値域)の積」に基づいて決定論的に行われるべきです。

言語仕様と安全な境界値設計

Rust における整数オーバーフローの言語仕様は以下の通りです。

  • Debug ビルドでは -C debug-assertions によりパニックが発生する6。
  • Release ビルドでは 2の補数ラップアラウンド(wrapping)が行われる7。
  • 言語仕様上は unsafe(未定義動作)とは見なされない8が、CWE-1909 にあるようにビジネスロジックの重大な破綻やセキュリティリスクを引き起こす。

SEI CERT C の INT32-C10 にある事前条件チェック(Precondition Testing)や、Alexis King の提唱する Parse, don’t validate11 の思想に基づき、型システムとドメイン境界を厳密に一致させることが重要です。

整数オーバーフロー対策アプローチの比較

整数オーバーフローへの対処は、単に「最も広い型を使う」ことではなく、計算コスト、型システムの表現力、およびコードの可読性のバランスを考慮して選択する必要があります。

アプローチ実行時コストコードの簡潔さ安全性の根拠向いているユースケース
i64 への適切な型拡張 (本アプローチ)最小 (ネイティブ 64bit 命令で完結)高 (周囲のコードと統一、キャスト最小)定数と入力上限の乗算検算による数学的証明ドメイン定数と入力の型範囲が確定している内部計算
念のための i128 拡張中〜高 (除算時にランタイム __divti3 呼び出し)低 (縮小キャストや周囲との不整合が発生)「とりあえず入るだろう」という感覚的判断中間計算が真に 64bit を超える超巨大演算のみ
saturating_ / checked_ 演算**小〜中 (分岐または条件付き移動命令)中 (メソッドチェーンが必要)上限飽和や None 処理による網羅的ハンドリング外部からの無制限な入力や、上限で頭打ちにしてよい処理

入力境界と定数が明確であるならば、手元での最悪値検算に基づきネイティブに処理できる最小の安全な型(本例では i64)を選択することが、パフォーマンスと保守性の両面において最善の選択肢となります。

よくある質問(FAQ)

Q. なぜ AI エージェントは「i64 も超える」といった誤った理由コメントを生成してしまうのですか?

A. AI エージェントは直前の会話やコミット履歴に強く影響されるバイアス(文脈同調)を持ち、プロジェクト全体の定数定義まで遡って厳密な掛け算を行うことが苦手だからです。先行する修正で 128bit 化が行われていた場合、そのパターンに同調して「より安全側に倒したもっともらしい理由」を補完(ハルシネーション)してしまう傾向があります。

Q. 整数オーバーフロー対策として、とりあえず 128bit 整数(i128/u128)を使っておくのはアンチパターンですか?

A. 内部計算の上限を検算しないまま漫然と 128bit 整数を採用するのは避けるべきです。現行の多くの 64bit CPU にはネイティブな 128bit 除算命令が存在しないため、コンパイラがランタイム関数(__divti3)の呼び出しを生成してオーバーヘッドが生じるほか、周囲のコードとの型不整合や誤った前提コメントの固定化を招くためです。

まとめ

  • 型を広げる PR に付いた理由コメントは、定数定義と入力上限を手計算するまで信用してはならない。 AI は文脈に同調して「i64 も超える」といったもっともらしい虚偽を平然と作文します。
  • 128bit 整数への過剰な型拡張は、ネイティブ命令の喪失とランタイム関数呼び出し(__divti3)という実害を伴う。 大は小を兼ねるという安易な防衛策は、不要なキャストやサブルーチン呼び出しのオーバーヘッドを引き起こします。
  • 型選定の正しさは、修正前コードでの反証テスト(panic 再現確認)によって担保する。 テストが通ったことだけで満足せず、本来のバグが確実に防がれているかを実測で裏付けます。

AI を活用した開発プロセスの品質管理、AI Operations の導入設計、コードレビュー運用の見直しについて、ぜひ コンタクトフォーム よりお気軽にご相談ください。

Footnotes

  1. LLM Hallucinations in AI Code Review Diffray、2026年。AI コードレビューにおけるハルシネーションの類型(Factual Errors、先行文脈に引きずられる Prompt-Induced Errors)。 ↩

  2. Primitive Type u32 Rust Standard Library Documentation。u32::MAX の値(4,294,967,295)の定義と仕様。 ↩

  3. Primitive Type i64 Rust Standard Library Documentation。i64::MAX の値(9,223,372,036,854,775,807)の定義と演算メソッド。 ↩

  4. AI code review false positives: causes, rates and fixes Cubic、2025年。AI コードレビューにおける False Positive の主要因と、プロジェクト全体の文脈欠落に関する分析。 ↩

  5. LLVM compiler-rt: divti3.c LLVM Project。128bit 符号付き除算関数 __divti3 のランタイム実装。ハードウェア命令を持たない環境でのソフトウェアエミュレーション。 ↩

  6. Expressions - The Rust Reference The Rust Project Developers。整数オーバーフロー時における Debug ビルドでのパニック挙動の仕様。 ↩

  7. Data Types - The Rust Programming Language Steve Klabnik and Carol Nichols。Release モードでの 2の補数ラップアラウンド挙動および wrapping_* / checked_* / saturating_* メソッドファミリの仕様。 ↩

  8. Behavior not considered unsafe - The Rust Reference The Rust Project Developers。整数オーバーフローが言語仕様上 unsafe(未定義動作)に該当しないことの規定。 ↩

  9. CWE-190: Integer Overflow or Wraparound MITRE。整数オーバーフローの脆弱性定義。計算結果が型の許容範囲を超えて予期しない挙動を引き起こすリスク。 ↩

  10. INT32-C: Ensure that operations on signed integers do not result in overflow SEI CERT C Coding Standard。符号付き整数演算におけるオーバーフロー防止のための事前条件チェック(Precondition Testing)の原則。 ↩

  11. Parse, don’t validate Alexis King、2019年11月5日。型システムによるドメインモデリングの思想。入力境界での厳密な型確定の重要性。 ↩