見出し画像

高校数学をプログラミングで解く(数学A編)「2-7 円と直線」


はじめに

今回は、数学Aで学ぶ「円と直線」について、円と直線(特に接線)を描くプログラムをいくつか作成します。

円周上の点を通る接線を描く

ここでは、円Oの周上の点Aを通る接線$${l}$$を描くプログラムを作成します。

図1 円Oの周上の点Aを通る接線l

アルゴリズム設計(接線を描くための準備)

図 2のように座標軸を設定します。そして、円Oの中心座標を$${(x_O, y_O)}$$、半径を$${r}$$とし、点Aの座標を$${(x_A, y_A)}$$とします。なお、点Aは円Oの周上の点なので、$${x_A}$$と$${y_A}$$は、以下の関係式を満たします。

$$
y_A=y_O \pm \sqrt{r^2-(x_A-x_O)^2}
$$

次に、接線$${l}$$の方程式を考えます。円の中心Oと点Aを結ぶ直線の傾きは

$$
\frac{y_A-y_O}{x_A-x_O}
$$

となります。接線$${l}$$はこの直線と垂直に交わるので、接線$${l}$$の傾きは

$$
-\frac{x_A-x_O}{y_A-y_O}
$$

となります。接線$${l}$$は点Aを通ることを考慮すると、接線$${l}$$の方程式の切片を求めることができます。結果として、直線$${l}$$の方程式は

$$
y=-\frac{x_A-x_O}{y_A-y_O}x + \frac{x_A^2+y_A^2-x_Ox_A-y_Oy_A}{y_A-y_O}
$$

となります。

図2 座標軸の設定

円周上の点を通る接線を描くプログラム

円周上の点を通る接線$${l}$$の方程式がわかりましたので、この接線を描くプログラムを作成していきます。ここでは、

$$
x_O=200, \ y_O=100, \ r=150, \\ x_A=100, y_A=y_O+\sqrt{r^2-(x_A-x_O)^2}
$$

として描いています。

// 円Oの周上の点Aを通る接線l
void setup(){
  size(500, 500); // キャンバスの大きさを指定する
  translate(width/5.0, height*4.0/5.0); // 座標の中心を移動する
  scale(1,-1); // y軸正の向きを下向きから上向きに反転する
  background(255,255,255); // 背景を白色にする
  noFill(); // 図形の塗りつぶしなし
  noLoop(); // 繰り返し処理をしない
  
  // 円Oの中心座標と半径
  float O_x, O_y, O_r; 
  O_x = 200.0;
  O_y = 100.0;
  O_r = 150.0;
  
  // 点Aの座標
  float A_x, A_y;  
  A_x = 100.0;
  A_y = O_y + sqrt( O_r*O_r - (A_x-O_x) * (A_x-O_x) );  
  
  // 直線lの傾きと切片
  float m, n;
  m = -(A_x - O_x) / (A_y - O_y);
  n = (A_x*A_x + A_y*A_y - O_x * A_x - O_y * A_y) / (A_y - O_y);

  //  円Oを描く
  circle(O_x, O_y, 2.0*O_r);
  // 円の中心を描く
  strokeWeight(5.0);
  point(O_x, O_y);
  // 点Aを描く
  stroke(255,0,0);
  point(A_x, A_y);

  // 接線lを描く
  strokeWeight(1.0);
  line( -width/5.0, m * (-width/5.0) + n, width*4.0/5.0, m * (width*4.0/5.0) + n );

}

ソースコード1 円周上の点を通る接線を描くプログラム

ソースコード1を、Processingの開発環境ウィンドウを開いて(スケッチ名を「circleandline1」としています)、テキストエディタ部分に書いて実行すると、図3のように円と指定した円周上の点で接する直線が実行ウィンドウのキャンバスに描かれます。

図3 円と指定した円周上の点で接する直線

注意点

直線$${l}$$の方程式を

$$
y=-\frac{x_A-x_O}{y_A-y_O}x + \frac{x_A^2+y_A^2-x_Ox_A-y_Oy_A}{y_A-y_O}
$$

と求めましたが、$${y_A=y_O}$$のときは、この方程式は利用できません。実際に、$${x_A=x_O-r=50}$$、$${y_A=y_O=100}$$とおいて、ソースコード1のプログラムを実行してみると、接線$${l}$$の方程式の傾きと切片は無限大に発散してしまい、接線$${l}$$を描かなくなります。ここでは、このような例外も考慮したプログラムになるようにソースコード1を修正してみます。
$${y_A=y_O}$$のときの接線$${l}$$の方程式は

$$
x=x_A
$$

となります。そこで、修正する箇所は「接線lの傾きと切片」と「接線lを描く」部分の2箇所になります。まず、「接線lの傾きと切片」の部分

  // 直線lの傾きと切片
  float m, n;
  m = -(A_x - O_x) / (A_y - O_y);
  n = (A_x*A_x + A_y*A_y - O_x * A_x - O_y * A_y) / (A_y - O_y);

  // 直線lの傾きと切片
  float m, n;
  m = 0.0;
  n = 0.0;
  if( A_y != O_y ){
    m = -(A_x - O_x) / (A_y - O_y);
    n = (A_x*A_x + A_y*A_y - O_x * A_x - O_y * A_y) / (A_y - O_y);
  }

と修正します。また、「接線lを描く」部分

  // 接線lを描く
  strokeWeight(1.0);
  line( -width/5.0, m * (-width/5.0) + n, width*4.0/5.0, m * (width*4.0/5.0) + n );

  // 接線lを描く
  strokeWeight(1.0);
  if( A_y != O_y ){
    line( -width/5.0, m * (-width/5.0) + n, width*4.0/5.0, m * (width*4.0/5.0) + n );
  } else {
    line( A_x, -height/5.0, A_x, height*4.0/5.0);
  }

と修正します。つまり、$${y_A=y_O}$$のときは接線$${l}$$の方程式の傾きや切片を計算せず、点Aを通る$${y}$$軸と並行な直線を直接描くようにしています。
実際、$${x_A=x_O-r=50}$$、$${y_A=y_O=100}$$としてプログラムを修正します。

// 円Oの周上の点Aを通る接線l
void setup(){
  size(500, 500); // キャンバスの大きさを指定する
  translate(width/5.0, height*4.0/5.0); // 座標の中心を移動する
  scale(1,-1); // y軸正の向きを下向きから上向きに反転する
  background(255,255,255); // 背景を白色にする
  noFill(); // 図形の塗りつぶしなし
  noLoop(); // 繰り返し処理をしない
  
  // 円Oの中心座標と半径
  float O_x, O_y, O_r; 
  O_x = 200.0;
  O_y = 100.0;
  O_r = 150.0;
  
  // 点Aの座標
  float A_x, A_y;  
  A_x = O_x - O_r;
  A_y = O_y + sqrt( O_r*O_r - (A_x-O_x) * (A_x-O_x) );  

  // 直線lの傾きと切片
  float m, n;
  m = 0.0;
  n = 0.0;
  if( A_y != O_y ){
    m = -(A_x - O_x) / (A_y - O_y);
    n = (A_x*A_x + A_y*A_y - O_x * A_x - O_y * A_y) / (A_y - O_y);
  }
  
  //  円Oを描く
  circle(O_x, O_y, 2.0*O_r);
  // 円の中心を描く
  strokeWeight(5.0);
  point(O_x, O_y);
  // 点Aを描く
  stroke(255,0,0);
  point(A_x, A_y);

  // 接線lを描く
  strokeWeight(1.0);
  if( A_y != O_y ){
    line( -width/5.0, m * (-width/5.0) + n, width*4.0/5.0, m * (width*4.0/5.0) + n );
  } else {
    line( A_x, -height/5.0, A_x, height*4.0/5.0);
  }

}

ソースコード2 ソースコード1を修正したバージョン

ソースコード2を、Processingの開発環境ウィンドウを開いて(スケッチ名を「circleandline1v2」としています)、テキストエディタ部分に書いて実行すると、円と円に接する$${y}$$軸と並行な直線が実行ウィンドウのキャンバスに描かれます。

図4 円と円に接するy軸と並行な直線

直線に接する円

次に、直線$${l}$$上の点Aで接する2つの半径$${r}$$の円$${\mathrm{O}_1, \mathrm{O}_2}$$を描くプログラムを作成します。

図5 直線に接する2つの円

アルゴリズム設計(円を描くための準備)

図 6のように座標軸を設定します。そして、直線$${l}$$の方程式を

$$
y=mx+n
$$

とし、点Aの座標を$${(x_A,y_A)}$$とします。なお、点Aは直線$${l}$$上の点なので、$${x_A}$$と$${y_A}$$は、以下の関係式を満たします。

$$
y_A=mx_A+n
$$

この設定のもと、接する円の中心座標$${(x_O,y_O)}$$を求めます。そのためにまず接する円の中心と直線$${l}$$との接点(ここでは点A)を結ぶ直線$${l'}$$を考えます。この直線$${l'}$$の方程式は直線$${l}$$と垂直に交わるので、

$$
y=-\frac{1}{m}x+\frac{x_A}{m}+y_A
$$

となります。
接する円の中心座標は直線$${l'}$$上で点Aから距離$${r}$$の点となります。これを求めるために、中心を点Aとする半径$${r}$$の円を考えると、この円と直線$${l'}$$の交点が接する円の中心座標となることがわかります。中心を点Aとする半径$${r}$$の円の方程式は

$$
(x-x_A)^2+(y-y_A)^2=r^2
$$

となるので、直線$${l'}$$の方程式との連立方程式を解くと、

$$
x_O=x_A \pm \frac{|m|}{\sqrt{m^2+1}}r, \ \ y_O=y_A \mp \frac{|m|}{m \sqrt{m^2+1}} r
$$

が得られます。接する円は2つ得られるので、それぞれの中心座標を$${\mathrm{O}_1(x_1, y_1)}$$と$${\mathrm{O}_2(x_2, y_2)}$$としておきます。

$$
x_1=x_A + \frac{|m|}{\sqrt{m^2+1}}r, \ \ y_1=y_A - \frac{|m|}{m \sqrt{m^2+1}} r \\ x_2=x_A - \frac{|m|}{\sqrt{m^2+1}}r, \ \ y_1=y_A + \frac{|m|}{m \sqrt{m^2+1}} r
$$

図6 座標軸の設定

直線に接する円を描くプログラム

直線$${l}$$に接する円の中心と半径がわかりましたので、この接する円を描くプログラムを作成していきます。ここでは、

$$
m=0.5, \ n=-100, \ r=150, \\
x_A=100, \ y_A=mx_A+n
$$

として描いています。

// 直線l上の点Aで接する半径rの円
void setup(){
  size(500, 600); // キャンバスの大きさを指定する
  translate(width/3.0, height*2.0/5.0); // 座標の中心をキャンバスの中心に移動する
  scale(1,-1); // y軸正の向きを下向きから上向きに反転する
  background(255,255,255); // 背景を白色にする
  noFill(); // 図形の塗りつぶしなし
  noLoop(); // 繰り返し処理をしない

  // 直線lの傾きと切片
  float m, n;
  m = 0.5;
  n = -100.0;
  
  // 点Aの座標
  float A_x, A_y;
  A_x = 100.0;
  A_y = m * A_x + n;  
  
  // 接する円の半径
  float r;
  r = 150.0;
  
  // 1つ目の接する円の中心座標
  float O1_x, O1_y; 
  O1_x = A_x + abs(m) / sqrt(m*m+1.0) * r;
  O1_y = A_y - abs(m) / m / sqrt(m*m+1.0) * r;
  
  // 2つ目の接する円の中心座標
  float O2_x, O2_y; 
  O2_x = A_x - abs(m) / sqrt(m*m+1.0) * r;
  O2_y = A_y + abs(m) / m / sqrt(m*m+1.0) * r;
  
  // 接線lを描く
  line( -width/3.0, m * (-width/3.0) + n, width*2.0/3.0, m * (width*2.0/3.0) + n );
  // 点Aを描く
  strokeWeight(5.0);
  point(A_x, A_y);
  // 1つ目の接する円
  stroke(255,0,0);
  strokeWeight(1.0);
  circle(O1_x, O1_y, 2.0*r);
  // 1つ目の接する円の中心を描く
  strokeWeight(5.0);
  point(O1_x, O1_y);
  // 2つ目の接する円
  stroke(0,0,255);
  strokeWeight(1.0);
  circle(O2_x, O2_y, 2.0*r);
  // 2つ目の接する円の中心を描く
  strokeWeight(5.0);
  point(O2_x, O2_y);

}

ソースコード3 直線に接する円を描くプログラム

ソースコード3を、Processingの開発環境ウィンドウを開いて(スケッチ名を「circleandline2」としています)、テキストエディタ部分に書いて実行すると、図7のように指定した直線とその直線上の点で接する2つの円が実行ウィンドウのキャンバスに描かれます。

図7 直線とその直線上の点で接する2つの円

注意点

今回作成したプログラムは直線$${l}$$が$${x}$$軸または$${y}$$軸に平行である場合は利用できません。このような場合でも利用できるようにするには、プログラムを修正する必要があります。つまり、直線$${l}$$が「$${x}$$軸に平行」のとき、「$${y}$$軸に平行」のとき、そして「$${x}$$軸とも$${y}$$軸とも平行ではない」ときに場合分けをして描くようにプログラムを修正します。この修正はそんなに難しくないので、チャレンジしてみてください。

円の外部の点を通る2つの接線

今度は、円Oの外部の点Aを通り、円Oに接する2つの直線$${l_1}$$、$${l_2}$$を描くプログラムを作成します。

図8 円の外部の点を通る2つの接線

アルゴリズム設計(接線を描くための準備)

図9のように座標軸を設定します。そして、円Oの中心座標を$${(x_O, y_O)}$$、半径を$${r}$$とし、点Aの座標を$${(x_A,y_A)}$$ とします。また、接線$${l_1}$$、$${l_2}$$と円Oとの接点をそれぞれ$${\mathrm{T}_1(x_1, y_1)}$$、$${\mathrm{T}_2(x_2, y_2)}$$とおき、線分OAの長さを$${d}$$とします。なお、$${d}$$は

$$
d=\sqrt{(x_A-x_O)^2+(y_A-y_O)^2}
$$

と計算することができます。△$${\mathrm{OAT_1}}$$は線分OAを斜辺とする直角三角形となるので、線分$${\mathrm{AT_1}}$$の長さは$${\sqrt{d^2-r^2}}$$となります。
では、接点$${\mathrm{T_1}}$$、$${\mathrm{T_2}}$$の座標を求めていきます。これらの接点は、点Aを中心とする半径$${\sqrt{d^2-r^2}}$$の円と円Oとの交点となります。点Aを中心とする半径$${\sqrt{d^2-r^2}}$$の円の方程式は

$$
(x-x_A)^2+(y-y_A)^2=d^2-r^2
$$

となり、円Oの方程式は

$$
(x-x_O)^2+(y-y_O)^2=r^2
$$

となるので、これらの方程式を連立させて解くと、接点$${\mathrm{T_1}}$$、$${\mathrm{T_2}}$$の座標は、それぞれ

$$
x_1 =x_O+\frac{(x_A-x_O)r^2+r \sqrt{d^2-r^2}(y_A-y_O)}{d^2}, \\ y_1 =y_O+\frac{(y_A-y_O)r^2-r \sqrt{d^2-r^2}(x_A-x_O)}{d^2}
$$

及び

$$
x_2 =x_O+\frac{(x_A-x_O)r^2-r \sqrt{d^2-r^2}(y_A-y_O)}{d^2}, \\ y_2 =y_O+\frac{(y_A-y_O)r^2+r \sqrt{d^2-r^2}(x_A-x_O)}{d^2}
$$

となります。これらの座標がわかったので、接線$${l_1}$$、$${l_2}$$の方程式はそれぞれ

$$
y=\frac{y_1-y_A}{x_1-x_A}x+y_1-\frac{y_1-y_A}{x_1-x_A}x_1
$$

及び

$$
y=\frac{y_2-y_A}{x_2-x_A}x+y_2-\frac{y_2-y_A}{x_2-x_A}x_2
$$

と得られます。

図9 座標軸の設定

円の外部の点を通る2つの接線を描くプログラム

接線$${l_1}$$、$${l_2}$$の方程式がわかりましたので、この2つの接線を描くプログラムを作成していきます。ここでは、

$$
x_O=200, \ y_O=100, \ r=150, \\ x_A=-100, \ y_A=0
$$

として描いています。

// 円Oの外部の点Aを通る接線
void setup(){
  size(500, 500); // キャンバスの大きさを指定する
  translate(width/4.0, height*2.0/3.0); // 座標の中心をキャンバスの中心に移動する
  scale(1,-1); // y軸正の向きを下向きから上向きに反転する
  background(255,255,255); // 背景を白色にする
  noFill(); // 図形の塗りつぶしなし
  noLoop(); // 繰り返し処理をしない
  
  // 円Oの中心座標と半径
  float O_x, O_y, O_r; 
  O_x = 200.0;
  O_y = 100.0;
  O_r = 150.0;
  
  // 点Aの座標
  float A_x, A_y;
  A_x = -100.0;
  A_y = 0.0;  
  
  // 円Oの中心と点Aの距離
  float d;
  d = sqrt( (A_x - O_x) * (A_x - O_x) + (A_y - O_y) * (A_y - O_y) );
  
  // 1つ目の接点の座標
  float T1_x, T1_y;
  T1_x = O_x + ((A_x - O_x)*O_r*O_r + O_r*sqrt(d*d - O_r*O_r)*(A_y - O_y))/d/d;
  T1_y = O_y + ((A_y - O_y)*O_r*O_r - O_r*sqrt(d*d - O_r*O_r)*(A_x - O_x))/d/d;
  
  // 1つ目の直線の傾きと切片
  float m1, n1;
  m1 = (T1_y - A_y) / (T1_x - A_x);
  n1 = T1_y - m1 * T1_x;

  // 2つ目の接点の座標
  float T2_x, T2_y;
  T2_x = O_x + ((A_x - O_x)*O_r*O_r - O_r*sqrt(d*d - O_r*O_r)*(A_y - O_y))/d/d;
  T2_y = O_y + ((A_y - O_y)*O_r*O_r + O_r*sqrt(d*d - O_r*O_r)*(A_x - O_x))/d/d;
  
  // 2つ目の直線の傾きと切片
  float m2, n2;
  m2 = (T2_y - A_y) / (T2_x - A_x);
  n2 = T2_y - m2 * T2_x;

  //  円Oを描く
  circle(O_x, O_y, 2.0*O_r);
  // 円の中心を描く
  strokeWeight(5.0);
  point(O_x, O_y);
  // 点Aを描く
  point(A_x, A_y);
  // 1つ目の接点を描く
  stroke(255,0,0);
  point(T1_x, T1_y);
  // 1つ目の接線を描く
  strokeWeight(1.0);
  line( A_x, A_y, width*3.0/4.0, m1 * (width*3.0/4.0) + n1 );
  // 2つ目の接点を描く
  stroke(0,0,255);
  strokeWeight(5.0);
  point(T2_x, T2_y);  
  // 2つ目の接線を描く
  strokeWeight(1.0);
  line( A_x, A_y, width*3.0/4.0, m2 * (width*3.0/4.0) + n2 );
  
}

ソースコード4 円の外部の点を通る2つの接線を描くプログラム

ソースコード4を、Processingの開発環境ウィンドウを開いて(スケッチ名を「circleandline3」としています)、テキストエディタ部分に書いて実行すると、円と円の外部の点を通る2つの接線が実行ウィンドウのキャンバスに描かれます。

図10 円と円の外部の点を通る2つの接線

発展

今回紹介した円の外部の点を通る2つの接線を応用すると、円に外接する四角形を描くことができます。ここでは紹介しませんが、比較的簡単に描くことができますので、ぜひチャレンジしてみてください。

まとめ

今回は、数学Aで学ぶ「円と直線」について、特に円に接する直線を描くプログラムをいくつか作りました。ポイントは円や直線に対して座標軸を設定して接点の座標を算出することです。これらの座標がわかれば、あとは直線や円の方程式を作って、それをプログラムで描くことは比較的簡単です。「円と直線」の簡単な問題も敢えてプログラミングして解いてみると、いろいろと気付きが得られます。時間があるときには、試してみることをお勧めします。

参考文献

改訂版 教科書傍用 スタンダード 数学A(数研出版、ISBN9784410209277)

この記事が気に入ったらサポートをしてみませんか?