AIに渡すプロンプトの違いによるAIコード生成の決定的な差
AIに「円周率を計算して」と丸投げした場合と数学的知見を与えるとコードはどう変わるのか
導入:AIは「優秀な代筆屋」か「諸刃の剣」か
本記事では、AIに丸投げした場合と、数学的背景を与えた場合で、生成されるコードがどれほど変わるかを比較します。
「AIにプログラミングを任せれば、どんなコードでも一瞬で書いてくれる」
そんな時代になり、とりあえず「〇〇を計算するコードを書いて」と抽象的なプロンプトを投げては、出てきたコードをそのまま貼り付けて動かす――そんなワークフローが日常になっていないだろうか。
しかし、AIが出力するコードの質は、突き詰めれば「人間側が渡すプロンプトの解像度(=持っている語彙と文脈)」によって決まる。GIGO(Garbage In, Garbage Out)の原則は、AI時代になって一層シビアになっている。
今回は、円周率を求めるJavaScriptのコードを例に、「丸投げしたプロンプト」から出てくるコードと、「数学的文脈と知見の設計思想」を指定したプロンプトから出てくるコードが、いかに別物になるかを見てみよう。
パターン1:AIに「円周率を計算するコードを書いて」と丸投げした場合
「円周率を計算するコードを書いて」とだけ指定すると、単純な例としてライプニッツ級数などの逐次的な無限級数を使ったコードが生成されることがある。
// ------------------------------------------------------------
// 任意精度 π(マクローリン展開)
// 3.108268566698946130001699549352e+0
// ------------------------------------------------------------
static calcPI2(fp = 30, mac = 100) {
fp = Number(fp);
mac = Number(mac);
let sum = new N6LBigFloatCalculator("0");
for (let i = 0; i < mac; i++) {
let nBig = new N6LBigFloatCalculator(String(2 * i + 1));
let term = new N6LBigFloatCalculator("1").div(nBig, fp).quot;
let ffp = fp - Math.floor(fp / 10);
let eeps = N6LBigFloatCalculator.epsilon(ffp);
if(new N6LBigFloatCalculator("0").epsCmp(term, eeps)) break;
if (i % 2 === 0) {
sum = sum.add(term, fp);
} else {
sum = sum.sub(term, fp);
}
}
return sum.mul("4", fp);
}
このコードの特徴
-
収束の遅さ: 項数(
mac)をそれなりに回さなければ精度が出ず、符号を交互にひっくり返す(i % 2 === 0)という冗長な処理が必要になる。 -
よくある汎用コード: 動くには動くが、数理的な必然性や美しさが表に出てこない、いわゆる「AIが検索結果から引っ張ってきた平均的なコード」の域を出ない。
マクローリン回数が少なければ精度が悪く3.10...とすぐに真値から外れてしまう。
パターン2:人間側が「アルキメデスとピタゴラス、第一象限の多角形近似と漸化式」を指定した場合
円周率( π/2 )の幾何学的・漸化式的近似
近似計算のステップ5までの一覧
| step | 多角形の辺数 $N_n$ | 中心角 $\alpha = 2\pi/N_n$ | 半角 $\theta = \alpha/2$ | 一辺 $s_n = 2 \sin \theta$ | 近似値 $\pi_n \approx$ | 誤差率 error_n(%) |
|---|---|---|---|---|---|---|
| 0 | 4 | $\pi/2$ | $\pi/4$ | $s_0 \approx 1.414...$ | $2.8284...$ | -9.9684 |
| 1 | 8 | $\pi/4$ | $\pi/8$ | $s_1 \approx 0.7654...$ | $3.0616...$ | -2.5505 |
| 2 | 16 | $\pi/8$ | $\pi/16$ | $s_2 \approx 0.3901...$ | $3.1208...$ | -0.6413 |
| 3 | 32 | $\pi/16$ | $\pi/32$ | $s_3 \approx 0.1960...$ | $3.1365...$ | -0.1606 |
| 4 | 64 | $\pi/32$ | $\pi/64$ | $s_4 \approx 0.0981...$ | $3.1403...$ | -0.0402 |
| 5 | 128 | $\pi/64$ | $\pi/128$ | $s_5 \approx 0.0490...$ | $3.1412..$ | -0.0100 |
表の各列の補足:
- $N_n$: 多角形の辺数(step 0 の正方形からスタートし、ステップごとに倍増)
- $\alpha, \theta$: 多角形を構成する中心角とその半角
- $s_n$: 単位円上の弦の長さ(一辺の長さ) ルートのネストが深くなっていくのが特徴です
- $\pi_n, error_n$: その時点での円周率の近似値と、真値に対する誤差率
(※初期化方式の違いにより step の番号がずれることがあります)
アルゴリズムの一般化式(0-origin 統一版)
- 多角形の辺数:
$$N_n = 4 \times 2^n \quad (n = 0, 1, 2, \dots)$$
- 中心角 $\alpha$ と 半角 $\theta$:
$$\alpha_n = \frac{2\pi}{N_n} = \frac{\pi}{2^n}$$
$$\theta_n = \frac{\alpha_n}{2} = \frac{\pi}{2^{n+1}}$$
-
補助変数 $a_n$ の漸化式:
-
初期値: $a_0 = 0$ (幾何学的には $2\cos\left(\frac{\pi}{2}\right) = 0$)
-
漸化式: $a_{n+1} = \sqrt{2 + a_n}$
-
一般項: $a_n = 2 \cos\left(\frac{\pi}{2^n}\right)$
-
一辺の長さ $s_n$:
$$s_n = 2 \times \sin(\theta_n) = 2 \times \sqrt{\frac{1 - \frac{a_n}{2}}{2}} = \sqrt{2 - a_n}$$
- 円周率の近似値 $\pi_n$:
$$\pi_n \approx s_n \times \frac{N_n}{2}$$
- 誤差率 $error_n$:
$$error_n = \frac{\pi_n - \pi}{\pi}$$
数学的背景:なぜこの式で計算できるのか?
- 半径 1 の円と弦の長さ
- 半径 $1$ の円において、中心角が $\alpha$ の扇形の両端を結ぶ直線(弦)の長さは、三角関数を用いて次のように表せます。
$$\text{弦長} = s_n = 2 \times \sin\left(\frac{\alpha}{2}\right)= \sqrt{2 - a_n}
$$
- 半角公式の適用
$$\sin\left(\frac{\theta}{2}\right) = \sqrt{\frac{1 - \cos\theta}{2}}, \quad \cos\left(\frac{\theta}{2}\right) = \sqrt{\frac{1 + \cos\theta}{2}}$$
- 前段階の余弦 $a_n$ を $a_{n+1} = \sqrt{2 + a_n}$ で次々とルートの入れ子としてビルドしていくことで、中心角 $\alpha$ を半分ずつ細分化していきます。
- それに伴い、一辺の長さ $s_n$ も $s_n = 2 sin \frac{\alpha}{2} = \sqrt{2 - a_n}$ によって連動して求まり、辺の数($N_n$)を掛け合わせることで、円周の長さへと迫っていきます。
「正方形の対角線($\sqrt{2}$)からスタートし、
補助変数 $a_n$ で $\cos$ をビルドしながら多角形の辺数を倍々に増やすと、円弧に近づく。
これがアルキメデスの円周率計算を洗練させた漸化式アルゴリズムの原理です。」
各ステップの詳細展開
- step = 0
$$N = 4, \quad \alpha = \frac{\pi}{2}, \quad \theta = \frac{\pi}{4}$$
$$a_0 = 0$$
$$s_0 = 2 \times \sin\left(\frac{\pi}{4}\right) = 2 \times \sqrt{\frac{1 - \cos\left(\frac{\pi}{2}\right)}{2}} = \sqrt{2 - a_0} = \sqrt{2}$$
$$s_0 \approx 1.414..., \quad \pi \approx s_0 \times 2 = 2.8284...$$
- step = 1
$$N = 8, \quad \alpha = \frac{\pi}{4}, \quad \theta = \frac{\pi}{8}$$
$$a_1 = \sqrt{2 + a_0} = \sqrt{2} \quad \left(= 2 \cos\left(\frac{\pi}{4}\right)\right)$$
$$s_1 = 2 \times \sin\left(\frac{\pi}{8}\right) = 2 \times \sqrt{\frac{1 - \cos\left(\frac{\pi}{4}\right)}{2}} = \sqrt{2 - a_1} = \sqrt{2 - \sqrt{2}}$$
$$s_1 \approx 0.7653..., \quad \pi \approx s_1 \times 4 = 3.0616...$$
- step = 2
$$N = 16, \quad \alpha = \frac{\pi}{8}, \quad \theta = \frac{\pi}{16}$$
$$a_2 = \sqrt{2 + a_1} = \sqrt{2 + \sqrt{2}} \quad \left(= 2 \cos\left(\frac{\pi}{8}\right)\right)$$
$$s_2 = 2 \times \sin\left(\frac{\pi}{16}\right) = 2 \times \sqrt{\frac{1 - \cos\left(\frac{\pi}{8}\right)}{2}} = \sqrt{2 - a_2} = \sqrt{2 - \sqrt{2 + \sqrt{2}}}$$
$$s_2 \approx 0.3901..., \quad \pi \approx s_2 \times 8 = 3.1208...$$
以下同様に、$a_{n+1} = \sqrt{2 + a_n}$ で次の $a$ をビルドし、それを使って $s_{n+1} = \sqrt{2 - a_{n+1}}$ で一辺を計算していくことで、プログラムのループとも完全に一致する美しい構造になります。
マスター:ニュートンの言葉を引用して
マスター:アルキメデス&ピタゴラス の二人の巨人の大きさと言ったら言葉では言い尽くせないです
私なんかはその肩の上にちょこんとへばりついているだけですね
人間側が「アルキメデスとピタゴラスの図形、第一象限における多角形近似、半角公式の漸化式と桁落ち対策、そして理論上の限界値によるガードレール」などの上記のような数学的背景の強烈な文脈(語彙)を指定してプロンプトを組み立てた場合、出力されるコードは完全に別次元のものに変貌する。
// ------------------------------------------------------------
// 任意精度 π(アルキメデス&ピタゴラスの多角形近似 漸化式:上下限ガードレール版)
// 3.141592653647406523266274689024
// ------------------------------------------------------------
static calcPI(fp = 30, maxSteps = 50) {
fp = Number(fp);
maxSteps = Number(maxSteps);
let two = new N6LBigFloatCalculator("2");
let zero = new N6LBigFloatCalculator("0");
// 初期設定:正4角形(第一象限ベース)
let an = new N6LBigFloatCalculator("0");
let edges = new N6LBigFloatCalculator("1");
let pi = new N6LBigFloatCalculator("0");
// 第一象限の限界値(π/2 ≒ 1.570796... を安全に挟む防壁)
let up = new N6LBigFloatCalculator("1.570796327");
let low = new N6LBigFloatCalculator("1.570796325");
let bpi = null;
let ffp = fp - Math.floor(fp / 10);
let eeps = Number("1e-"+String(ffp));
for (let step = 1; step <= maxSteps; step++) {
bpi = pi.clone();
// 辺の倍加
edges = edges.mul("2", fp);
// an = sqrt(2 + an)
let twoPlusAn = two.add(an, fp);
an = N6LBigFloatCalculator.sqrt(twoPlusAn, fp);
// sn = sqrt(2 - an) (新しい一辺)
let twoSubAn = two.sub(an, fp);
let sn = N6LBigFloatCalculator.sqrt(twoSubAn, fp);
// pi = sn * edges (第一象限の累積長)
pi = sn.mul(edges, fp);
// 【ガードレール】計算結果が許容範囲を超えて狂い始めたら即座にストップ
if (17 < step && (pi.compareTo(low) < 0 || 0 < pi.compareTo(up))) {
pi = bpi;
break;
}
// 収束判定
if (bpi !== null) {
let diff = pi.sub(bpi, fp);
if (zero.epsCmp(diff, eeps)) {
break;
}
}
}
return pi.mul("2", fp);
}
このコードの圧倒的な強み
- 爆発的収束: 平方根のネストが深まるごとに桁数が倍加していくため、少ないステップ数で真値へ到達する。しかし理論上は急速に収束するアルゴリズムでも、有限精度演算では桁落ちによって途中から精度が壊れる。
-
自律防衛(ガードレール)の存在: 理論上の限界値(
lowとup)を中盤以降に走らせ、万が一の丸め誤差や桁落ちによる暴走を自らスライスして回収する。 - 要塞としての美学: 先人たちの幾何学的知見(アルキメデス、ピタゴラス)がそのままアルゴリズムの心臓部として組み込まれている。
先ほどの3.10に比べて真値との精度の差を比べてほしい。
計算値:3.141592653647406523266274689024
真値: 3.14159265358979323846264338327950288419716939937510
1桁の小数精度だったものが9桁の小数精度が得られた
計算結果を比較する
パターン1では、マクローリン展開して得られた値は、
計算値
3.108268566698946...
となりました。
一方、パターン2では、
計算値
3.141592653647406523266274689024
真値
3.141592653589793238462643383279...
となりました。
このときの絶対誤差は約
$5.76 × 10^{-11}$
です。
つまり、単に「円周率を計算する」という同じ要求でも、プロンプトでアルゴリズムや数値計算上の制約を指定することで、生成されるコードの性質が大きく変わりました。
ここで重要なのは、パターン2が単に「速い公式」を使っていることではありません。
アルゴリズムの数学的背景、有限精度演算で発生する桁落ち、そして理論値による異常検出まで、人間側から問題設定として与えていることです。
AIはこれらの条件をコードへ展開することができます。
しかし、その条件そのものを考えるのは別の仕事です。
| 項目 | 丸投げプロンプト | 数学的文脈ありプロンプト |
|---|---|---|
| アルゴリズム | ライプニッツ級数 | 多角形近似+半角公式漸化式 |
| 収束速度 | 遅い | 爆速 |
| 精度 | 1桁 | 9桁 |
| 計算値 | 3.108… | 3.141592653647… |
| 真値との差 | 大きい | 5.76×10⁻¹¹ |
| 数値計算の安全性 | なし | ガードレールあり |
| AIの役割 | 代筆屋 | 乗数 |
AIは「思考の代行」ではなく「乗数」である
2つのコードを見比べれば一目瞭然だ。
- 丸投げしたコードは、要求された条件に対しては動作するが、アルゴリズムの選択理由や数値計算上の限界まで明示されていない。
- 文脈を与えて構築したコードは、エラーや破綻のリスクまで計算し尽くされた「知の要塞」。
AIは中立な「乗数」にすぎない。
人間側が浅い理解のままプロンプトを渡せば、AIが生成するコードもまた不安定なものになる。
AIに与える問題設定・制約・背景知識の解像度によって、生成されるコードの方向性は大きく変わる。
「AIに何を書かせるか」だけではなく、「AIに何を考慮させるか」を人間が指定する。
AIは人間の知識を自動的に正しいコードへ変換する魔法ではない。
むしろ、人間が問題を正しく理解し、重要な制約を言語化できたとき、
その知識をコードという形に展開するための強力な「乗数」になる。
「まず自分の手で考え、基礎を学ぶこと」。
それこそが、AIをより有効に使い、自分のコード資産を守るための重要な土台になるのである。
💡 LLMから狙い通りのコードを引き出す実践アプローチ
1. 事前準備と前提の明確化
-
定義・要件・制約・例外の事前指定
- 一発で答えを求めず、条件を明確にしてAIの迷いを防ぐ。
-
周辺コードや仕様の明示
- 移植や翻訳作業では、関連するメソッドや仕様のコードを提示して基準を合わせる。
-
数学的・理論的背景の共有
- 基盤となる数式やアルゴリズムの定義を説明し、それに沿った生成を求める。
2. プロンプトの構造化・誘導手法
-
手順の明示・段階的な検証
- いきなり完成コードを求めるのではなく、まずアルゴリズムの手順、前提条件、入出力、エッジケースを整理させ、その内容を確認してからコード生成へ進む。
-
インターフェース(型・データ構造)の厳密化
- 引数や戻り値の型・プロパティを具体的に指定し、メソッドやプロパティの捏造(ハルシネーション)を防ぐ。
-
エッジケース(境界値・例外)の事前指定
- 「0や負の値の扱い」「エラーの返し方」など、端の処理をあらかじめ縛って堅牢性を高める。
-
出力フォーマットの限定
- 「解説は最小限」「コードブロックのみ」「Diff形式」など、出力形式を制限してノイズと検証の手間を減らす。
まとめ
すでに「定義・周辺コード・数学的背景」を伝えるアプローチができている場合、そこに**「手順の言語化(CoT)」や「インターフェース・境界値の事前指定」**を組み合わせることで、さらに狙い通りの高品質な出力を引き出しやすくなります。
ここでいうプロンプトエンジニアリングは、AIを「うまく騙して良い答えを出させる技法」ではなく、人間側の問題理解をAIが扱える形に整理する作業だと考えています。
作者
GitHub: https://github.com/NAS6mixfoolv
X(旧Twitter): https://x.com/NAS6_oxo
作者HP: https://nas6.net
気に入っていただけたら GitHub に ⭐ をいただけると嬉しいです!

