今日は3月14日、πの日ということで円周率の計算をするプログラムを作ろう、という趣旨の記事です。
#使ったもの
HTMLとタイトルにありますが、HTMLの記述は数行でほとんどがJavaScriptです。HTMLのcanvas要素をJavaScriptでゴリゴリいじって描画します。
canvasについては、このサイトを参考にしました。
#計算方法
数値計算ではなく、確率(乱数)を用いるモンテカルロ法で円周率を求めます。
青色の点は円の内側、赤色の点は円の外側にある点です。
均一に点がばらまかれていれば、青と青+赤の点の数の比は、円の面積と四角形の面積比と等しいはずです。
青色の点の数をa、赤色の点の数をbとすると、
$\pi r^2:4r^2 = a:a+b$
$(a+b)\pi r^2 = 4ar^2$
$\pi = \frac{4a}{a+b}$
となり、上記式を用いて円周率を求めることができる。
#実装
##HTML
canvas要素にidをつけて置いておくだけでいいです。
###HTMLのソースコード
<!DOCTYPE html>
<html>
<head>
<title>円周率</title>
<script type="text/javascript" src="./pi.js"></script>
</head>
<body onload="init();">
<h1>モンテカルロ法による円周率の計算</h1>
<canvas id="pi"></canvas>
</body>
</html>
##JavaScript
コードが長いので詳しい説明は省きます。今日中に終わりそうにないので。
ただ、やってることは、
1.背景の描画
2.乱数で点の生成
3.$\pi$の計算
4.2,3の繰り返し
と簡単なので、頭のいい人はもう少し短いコードで書けると思います。
ベクトルクラスを実装する際はこちらの記事を参考にさせて頂きました。
###JavaScriptのソースコード
//canvas要素の大きさ
const CANVAS_WIDTH = 400;
const CANVAS_HEIGHT = 400;
//変数の準備
var ctx;
const FPS = 1000;
const TIME = 1000/FPS;
var total = 0;
var In = 0;
//ベクトルクラスの実装
class vec2{
//
constructor(x=0, y=0){
this.x = x;
this.y = y;
}
//足し算メゾット
add(v){
this.x += v.x;
this.y += v.y;
return this;
}
//ベクトルを複製するメゾット
clone(){
return new vec2(this.x, this.y);
}
//静的足し算メゾット
static add(v1, v2){
return v1.clone().add(v2);
}
}
//原点の位置を定義
var origin = new vec2((CANVAS_WIDTH/2),(CANVAS_HEIGHT/2));
//bodyが読み込まれたときに実行される関数
//
function init(){
//canvas要素を取得
let pi = document.getElementById("pi");
if(pi.getContext){
ctx = pi.getContext("2d");
//canvasのサイズを指定
pi.width = CANVAS_WIDTH;
pi.height = CANVAS_HEIGHT;
//背景の描画
draw_init();
//点を打ちまくる
setInterval(draw,TIME);
}
}
//繰り返し実行される関数
function draw(){
//文字を消す処理
ctx.clearRect(0,360,CANVAS_WIDTH,400);
//乱数の代入と変数の準備
let tempX = getRandomInt(200) - 100;
let tempY = getRandomInt(200) - 100;
let pi;
//円の中か外の判定
ctx.fillStyle = "rgb(255,0,0)"
if((tempX**2)+(tempY**2) <= 100**2){
In++;
ctx.fillStyle = "rgb(0,0,255)"
}
total++;
//piの計算
pi = 4*In/total;
//点の描画処理
let v1 = new vec2(tempX,tempY);
v1.y *= -1;
let screen = vec2.add(v1,origin);
ctx.fillRect(screen.x,screen.y,1,1);
//piとtotalの表示
ctx.fillStyle = "rgb(0,0,0)"
ctx.fillText(pi,0,380);
ctx.fillText(total,0,400);
}
//背景を描画する関数
function draw_init(){
//x軸
ctx.beginPath();
ctx.moveTo(0,CANVAS_HEIGHT/2);
ctx.lineTo(CANVAS_WIDTH,CANVAS_HEIGHT/2);
ctx.stroke();
ctx.font = "20px serif";
ctx.fillText("x",CANVAS_WIDTH-20,(CANVAS_HEIGHT/2)+20);
//y軸
ctx.beginPath();
ctx.moveTo(CANVAS_WIDTH/2,CANVAS_HEIGHT);
ctx.lineTo(CANVAS_WIDTH/2,0);
ctx.stroke();
ctx.fillText("y",(CANVAS_WIDTH/2)+10,15);
//原点
ctx.fillText("O",(CANVAS_WIDTH/2)-20,(CANVAS_HEIGHT/2)+20);
//四角形
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(100,300);
ctx.lineTo(300,300);
ctx.lineTo(300,100);
ctx.lineTo(100,100);
ctx.stroke();
//円
let t;
for(t=0;t<=2*Math.PI;t+=0.01){
let v2 = new vec2(100,t);
let v1 = new vec2(v2.x*Math.cos(v2.y),v2.x*Math.sin(v2.y));
v1.y *= -1;
let screen = vec2.add(v1, origin);
ctx.fillRect(screen.x, screen.y, 1, 1);
}
}
//乱数を返す関数
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max+1));
}
##実行結果
いい感じに近似出来ているのではないだろうか?
#あとがき的なもの
初めて記事を書きました。なので少し言い訳と予防線を貼ったりします。
マークダウンの書き方などわからないことが多く、見苦しい記事になっていたら申し訳ないです。
間違いなどがありましたら、コメントで指摘していただけたら修正いたします。
もうすぐ3月14日が終わってしまう。はたして、3月14日に見られた人はいるのだろうか。
追記(2021/03/16)
github.ioを使って公開しました。よかったら見てみてください。
pi.html
追記(2021/05/30)
//乱数を返す関数
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
を
//乱数を返す関数
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max+1));
}
と修正しました。