ベクトルの内積
内積とは
「懐中電灯の光を壁に当てるとき、光が壁に垂直なら影は点になり、斜めになるほど影が長くなる」——この「どのくらい同じ方向を向いているか」を数値で表すのが内積(dot product)です。
2 つのベクトルを「掛け合わせる」方法のひとつが内積で、結果はスカラー(向きを持たない数値)になります——「2 つの矢印から 1 つの数を作る演算」と覚えましょう。
定義:角度で定義
「2 つのベクトルのなす角が小さいほど(同じ方向を向いているほど)、内積は大きくなる」——これが内積の本質です:
ここで は 2 つのベクトルのなす角()。(同じ向き)、(直角)、(逆向き)。
定義:成分で定義
「横成分どうし、縦成分どうしをかけて足す」——計算はシンプルです。
、 のとき:
この 2 つの定義は等価です——角度の定義と成分の定義は同じ値を与えます。
内積の幾何的意味
「 を の方向に”影”として投影したとき、その影の長さと の大きさをかけた値」——これが内積の幾何的なイメージです:
つまり「 の大きさ × の への投影の長さ」。
- :内積 > 0(同じ方向寄り)——「同じ方向を向いている」
- :内積 = 0(直交)——「完全に直角」
- :内積 < 0(逆方向寄り)——「反対方向を向いている」
直交条件
「2 つのベクトルが垂直かどうかを調べるのに、分度器は不要」——内積がゼロかどうかだけ確認すれば OK です:
、 のとき:
これは非常によく使います。証明問題で「 を示せ」という形が頻出です——「成分を代入して計算するだけ」で垂直性が証明できます。
インタラクティブ図解:内積と角度
マウスを動かすとベクトル が回転します。内積の値と角度がリアルタイムに更新されます。直交したとき(内積 = 0)に特別なハイライトが出ます。
var OX = 300, OY = 180, SC = 60;
function arrow(x1, y1, x2, y2, color, width) {
var dx = x2-x1, dy = y2-y1;
var len = Math.sqrt(dx*dx+dy*dy);
if (len < 2) return;
var ux=dx/len, uy=dy/len;
var hw=9, hl=13;
ctx.beginPath(); ctx.moveTo(x1,y1); ctx.lineTo(x2-ux*hl,y2-uy*hl);
ctx.strokeStyle=color; ctx.lineWidth=width; ctx.stroke();
ctx.beginPath(); ctx.moveTo(x2,y2);
ctx.lineTo(x2-ux*hl-uy*hw, y2-uy*hl+ux*hw);
ctx.lineTo(x2-ux*hl+uy*hw, y2-uy*hl-ux*hw);
ctx.closePath(); ctx.fillStyle=color; ctx.fill();
}
function loop() {
ctx.clearRect(0, 0, W, H);
// グリッド
ctx.strokeStyle = '#161b22'; ctx.lineWidth = 1;
for (var gx=-4;gx<=6;gx++) {ctx.beginPath();ctx.moveTo(OX+gx*SC,0);ctx.lineTo(OX+gx*SC,H);ctx.stroke();}
for (var gy=-3;gy<=4;gy++) {ctx.beginPath();ctx.moveTo(0,OY-gy*SC);ctx.lineTo(W,OY-gy*SC);ctx.stroke();}
ctx.strokeStyle='#30363d';ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(0,OY);ctx.lineTo(W,OY);ctx.stroke();
ctx.beginPath();ctx.moveTo(OX,0);ctx.lineTo(OX,H);ctx.stroke();
ctx.fillStyle='#8b949e';ctx.font='12px sans-serif';
ctx.fillText('x',W-14,OY-8);ctx.fillText('y',OX+6,14);
// a = (2, 1)(固定)
var ax=2, ay=1;
var aLen = Math.sqrt(ax*ax+ay*ay);
// b = マウス方向の単位ベクトル × 2.2
var dx = mx - OX, dy = -(my - OY);
var dLen = Math.sqrt(dx*dx+dy*dy);
var bLen = 2.2;
var bx = dLen > 0.1 ? (dx/dLen)*bLen : 0;
var by = dLen > 0.1 ? (dy/dLen)*bLen : bLen;
var dot = ax*bx + ay*by;
var cosTheta = dot / (aLen * bLen);
cosTheta = Math.max(-1, Math.min(1, cosTheta));
var theta = Math.acos(cosTheta);
var thetaDeg = (theta * 180 / Math.PI);
// a の画面先端
var aEx = OX + ax*SC, aEy = OY - ay*SC;
var bEx = OX + bx*SC, bEy = OY - by*SC;
// 直交に近いかどうか
var isPerp = Math.abs(dot) < 0.15;
// 角度弧
var aAngle = Math.atan2(-ay, ax);
var bAngle = Math.atan2(-by, bx);
ctx.beginPath();
ctx.arc(OX, OY, 30, Math.min(aAngle,bAngle), Math.max(aAngle,bAngle));
ctx.strokeStyle = '#ffa65780'; ctx.lineWidth = 2; ctx.stroke();
ctx.fillStyle = '#ffa657'; ctx.font = '12px serif';
var midAngle = (aAngle + bAngle) / 2;
ctx.fillText('θ', OX + 38*Math.cos(midAngle), OY + 38*Math.sin(midAngle));
// b の a への射影
var projLen = dot / aLen;
var projX = OX + (ax/aLen) * projLen * SC;
var projY = OY - (ay/aLen) * projLen * SC;
ctx.beginPath(); ctx.moveTo(bEx, bEy); ctx.lineTo(projX, projY);
ctx.strokeStyle = '#f0883e50'; ctx.lineWidth = 1.5; ctx.setLineDash([3,3]); ctx.stroke(); ctx.setLineDash([]);
ctx.beginPath(); ctx.arc(projX, projY, 4, 0, Math.PI*2);
ctx.fillStyle = '#f0883e80'; ctx.fill();
// ベクトル a
arrow(OX, OY, aEx, aEy, isPerp ? '#ffa657' : '#58a6ff', 3);
ctx.fillStyle = isPerp ? '#ffa657' : '#58a6ff';
ctx.font = 'bold 14px serif';
ctx.fillText('a⃗', aEx+8, aEy-8);
// ベクトル b
arrow(OX, OY, bEx, bEy, isPerp ? '#ffa657' : '#f0883e', 3);
ctx.fillStyle = isPerp ? '#ffa657' : '#f0883e';
ctx.fillText('b⃗', bEx+8, bEy-8);
// 直交マーク
if (isPerp) {
var pu = ax/aLen * 14, pv = ay/aLen * 14;
var bu = bx/bLen * 14, bv = by/bLen * 14;
ctx.beginPath();
ctx.moveTo(OX+pu, OY-pv);
ctx.lineTo(OX+pu+bu, OY-pv-bv);
ctx.lineTo(OX+bu, OY-bv);
ctx.strokeStyle = '#ffa657'; ctx.lineWidth = 2; ctx.stroke();
}
// 情報パネル
var pColor = isPerp ? '#ffa657' : (dot > 0 ? '#56d364' : '#f85149');
ctx.fillStyle = '#0d1117e0';
ctx.fillRect(8, 8, 260, 130);
ctx.strokeStyle = isPerp ? '#ffa657' : '#30363d'; ctx.lineWidth = isPerp ? 2 : 1;
ctx.strokeRect(8, 8, 260, 130);
ctx.font = '13px monospace';
ctx.fillStyle = '#58a6ff';
ctx.fillText('a⃗ = (' + ax + ', ' + ay + ') |a⃗|=' + aLen.toFixed(2), 16, 28);
ctx.fillStyle = '#f0883e';
ctx.fillText('b⃗ = (' + bx.toFixed(2) + ', ' + by.toFixed(2) + ') |b⃗|=' + bLen.toFixed(2), 16, 48);
ctx.fillStyle = '#ffa657';
ctx.fillText('θ = ' + thetaDeg.toFixed(1) + '°', 16, 68);
ctx.fillStyle = pColor;
ctx.font = 'bold 14px monospace';
ctx.fillText('a⃗·b⃗ = ' + dot.toFixed(3), 16, 90);
ctx.font = '12px monospace';
ctx.fillStyle = '#8b949e';
ctx.fillText('= |a||b|cosθ = ' + (aLen*bLen*cosTheta).toFixed(3), 16, 108);
if (isPerp) {
ctx.fillStyle = '#ffa657'; ctx.font = 'bold 13px sans-serif';
ctx.fillText('⊥ 直交!(dot = 0)', 16, 128);
} else {
ctx.fillStyle = pColor; ctx.font = '12px sans-serif';
ctx.fillText(dot > 0 ? '同方向寄り(鋭角)' : '逆方向寄り(鈍角)', 16, 128);
}
requestAnimationFrame(loop);
}
loop();
内積の性質
内積は次の性質を満たします(スカラー積の公理)——「普通の掛け算と似た性質が成り立つ」と覚えましょう:
最後の は「自分自身との内積はベクトルの大きさの二乗」—— なので となり、自然にこの形になります。
展開公式
「 のベクトル版」——同じパターンで展開できます:
これを使うと余弦定理がすっきり導けます——「余弦定理はベクトルの展開公式の別表現」です:
余弦定理 と対応します。
内積の応用
角度を求める
「2 本の矢印の間の角度を、成分だけで計算できる」——実用的な公式です:
2 ベクトルの成分がわかれば、角度を求めることができます——「内積を計算して両辺の大きさで割るだけ」。
射影(projection)
「斜面を歩くとき、水平方向にどれだけ進んだか」——これが射影のイメージです。 の への射影の長さ:
射影ベクトル( 方向への射影を矢印として表したもの):
例題
、 のとき、
- を求めよ
- と のなす角を求めよ
- となるような を求めよ( とする)
解:
-
成分ごとにかけて足す——
-
、——「3-4-5の直角三角形」から がすぐ分かります:
- 直交条件「内積 = 0」を使う——
まとめ
- 内積の定義:(2通りの定義が等価)——「角度版と成分版は同じ値」
- 内積はスカラー(数値)を返す——「2 つの矢印から 1 つの数を取り出す演算」
- 直交条件:(最重要!)——「垂直かどうかは内積がゼロかで確認する」
- 内積から角度・射影・余弦定理などが導ける——「内積は幾何と代数をつなぐ橋」
次回は空間(3 次元)へ飛び出してベクトルを扱います。