- 投稿日:2019-07-19T21:40:45+09:00
円の面積を公式を使わずして点を数えて求める
公式を使わなくたって、図形描いて点の数を数えたら面積になるんじゃない?っていうお話です。今回は円の面積を求めてみようと思います。
公式による円の面積の求め方
円の面積は、 円周率 * 半径**2 の公式で求めることができます。 円周率 を 3.14 、半径を 100 とするなら、 3.14 * 100**2 = 31400 で 31400 と求めることができます。
点の数を数えて求める?どうするの?
黒色キャンパス内に白色の円を描いて塗りつぶした後で、キャンパス内の全ピクセルから白色であるピクセルの数を数えます。
半径 1000 なら、 2000x2000 のキャンパスを用意して 4000000 、つまり 4 百万のピクセルをしらみつぶしにチェックして白色であるピクセルの数を数えます。
正確かどうかは、どんだけ正確な円を描くことができて、それに対して十分な解像度があるかといった環境にめちゃくちゃ依存してしまうという、なかなかスリリングな話となります。
じゃあやってみよう、環境はこれ
実行環境は以下のとおりです。 JavaScript の実行環境としてウェブブラウザじゃなくて Node.js を利用したのはタイムアウトとかを気にしなくて良いし、ファイル出力とかも楽かなっと思ったからです、
- Lubuntu 16.04(64bit)
- Memory 4G
- Node.js v10.16.0
- node-canvas 2.5.0
- libcairo2-dev 1.14.6-1
Node.js で canvas を使えるようにするために node-canvas をインストールします。 node-canvas は cairo を使用しているので、 cairo 関係のライブラリを事前にインストールしておきます。
node-canvasのインストールsudo apt-get install libcairo2-dev libjpeg-dev libgif-dev npm install -g canvasプログラムはこれ
以下がプログラムです。キャンパスに半径 1000 の白色の円を描いてキャンパス内の白色のピクセル数を数えています。
円を描いてピクセル数を数える'use strict' /* sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev npm install -g canvas */ let radius = 1000; if (process.argv.length>2) radius = parseInt(process.argv[2]); const { createCanvas, Canvas } = require('canvas'); let canvas = createCanvas(radius*2, radius*2); let ctx = canvas.getContext('2d',{ pixelFormat: 'A8' }); //8bitグレースケール //図形を描画 ctx.antialias = 'none'; ctx.fillStyle = 'rgb(255,255,255)'; ctx.beginPath(); ctx.arc(radius, radius, radius, 0, 2 * Math.PI); ctx.fill(); //集計 let countArea = 0; for(let x=0;x<radius*2;x+=1000) { for(let y=0;y<radius*2;y+=1000) { let image = ctx.getImageData(x,y,1000,1000).data; for(let i=0;i<image.length;i++) if (image[i]==255) countArea++; } } //比較のため公式を使って面積を計算 let formulaArea = Math.floor(Math.PI*radius**2); //結果を表示 console.log(`radius = ${radius}`); console.log(`countArea = ${countArea}`); console.log(`formulaArea = ${formulaArea}`); console.log(`absolute_err = ${Math.abs(countArea-formulaArea)}`); console.log(`relative_err = ${Math.abs((countArea-formulaArea)/formulaArea)}`); //試しに算出した結果から逆算して円周率を計算してみる let pi = countArea/radius**2; console.log(`pi = ${pi}`);さぁ実行してみよう!
さぁて実行です! 2000x2000 のキャンバス内をピクセルを数えることになるので、速度的にどうかなって思っていたのですが、一瞬で終了しました。さすがです。
で、気になるその結果は…、以下のとおりです。
実行結果$ time node circle.js radius = 1000 countArea = 3141577 formulaArea = 3141592 absolute_err = 15 relative_err = 0.0000047746492860944385 pi = 3.141577 real 0m0.164s user 0m0.140s sys 0m0.024sおー!なんということでしょう!公式で求めたのが 3141592 で、ピクセルの数を数えた結果が 3141577 !想像以上の良い結果です!
さらに試しに逆算した円周率も四捨五入したらなんと 3.14 に! node-canvas というか cairo がそこまで正確な円を描いているとは想像もしていなかったので、かなりの驚きです。
半径を大きくすると正確になる?
もしかして半径をもっと大きくすればもう少し正確になるかと思い、半径を 10000 にして計算してみました。…ちょこっと精度があがりました。
半径10000で計算$ time node circle.js 10000 radius = 10000 countArea = 314158637 formulaArea = 314159265 absolute_err = 628 relative_err = 0.0000019989860875183802 pi = 3.14158637 real 0m1.739s user 0m1.424s sys 0m0.336sもうちょっと調子に乗ってみる
思った以上に良い結果が出たので、調子にのって、もうちょっと複雑な図形でも試してみようと思います。
ターゲットは?
「面積の求め方(第3回)~葉っぱ型図形の面積 | 学びの場.com」の問題 3 は以下のような図形の黒色の面積を求めるというものです。その面積をピクセル数を数えて求めてみます。ホームページには計算の仕方や答えも書いてあるので比較しやすそうです。
プログラムはこれ
でもってプログラムは以下のとおりです。ホームページでは 10cmx10cm でしたが、プログラムでは、 1000x1000 のサイズで描画して集計して、それを最後に 10000 で割ることで求めています。
問題3'use strict' /* sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev npm install -g canvas */ //https://www.manabinoba.com/math/6520.html let radius = 500; if (process.argv.length>2) radius = parseInt(process.argv[2]); const { createCanvas, Canvas } = require('canvas'); let canvas = createCanvas(radius*2, radius*2); //let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d',{ pixelFormat: 'A8' }); //8bitグレースケール //図形の描画 ctx.antialias = 'none'; ctx.globalCompositeOperation = "xor"; ctx.fillStyle = "rgb(255,255,255)"; ctx.beginPath(); ctx.arc(0, radius, radius, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(radius, radius*2, radius, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(0, radius*2, radius*2, 0, Math.PI*2); ctx.fill(); //集計 let countArea = 0; for(let x=0;x<radius*2;x+=1000) { for(let y=0;y<radius*2;y+=1000) { let image = ctx.getImageData(x,y,1000,1000).data; for(let i=0;i<image.length;i++) if (image[i]==255) countArea++; } } countArea/=(radius*2/10)**2; //比較のため公式を使って面積を計算 let formulaArea = 10*10 * ((10*10*Math.PI/4 - 10*10/2) * 2 / 100) / 2; //結果を表示 console.log(`radius = ${radius}`); console.log(`countArea = ${countArea}`); console.log(`formulaArea = ${formulaArea}`); console.log(`absolute_err = ${Math.abs(countArea-formulaArea)}`); console.log(`relative_err = ${Math.abs((countArea-formulaArea)/formulaArea)}`); //確認用 const fs = require('fs'); const out = fs.createWriteStream("fig3.png"); const stream = canvas.createPNGStream(); stream.pipe(out); out.on('finish', () => console.log('The PNG file was created.'));さぁ実行してみよう!
では実行!…あいかわらずあっけないくらい一瞬で終わってしまいました。で、で、気になる結果はというと…、 じゃじゃん 28.5389 となりました!小数点以下第2位で四捨五入すれば 28.5 となり、ホームページでの答えである 28.5cm² と一緒になりました!おー!
結果$ time node fig3.js radius = 500 countArea = 28.5389 formulaArea = 28.539816339744835 absolute_err = 0.0009163397448332944 relative_err = 0.00003210741561630831 The PNG file was created. real 0m0.173s user 0m0.152s sys 0m0.020s結局どう?
私個人的には使える!って思うのですが、実行する環境によって結果が変わっちゃいますし、「点の数を数えました」よりも「公式を使いました」の方が信頼されるかもしれないので努力は報われないかもしれないです。意味がないって言われちゃうかもしれないです。
でも、楽しかったので、ま、私的にはいいかなって、自己満足に浸ることができたんだし。ということでめでたしめでたし、です。
- 投稿日:2019-07-19T04:01:36+09:00
nodejsでgit mvのループ処理
_付いてない.scssをgit mvで_付けてリネームする必要があって、コマンドを調べてたんですが、macOSだとfindに
代わりにnodejsのコンソールで似たようなコマンドを実装することに。nodenode// カレントディレクトにある_付いてない全てのscssを_付けてリネーム require('fs').readdirSync('.').filter(file => file.match(/^(?!_).*?\.scss$/)).forEach(scss => require('child_process').execSync(`git mv ${scss} _${scss}`, { stdio: 'inherit' })) // git mv hoge.scss _hoge.scss // git mv fuga.scss _fuga.scss // git mv piyo.scss _piyo.scssjsしか読めないのつらい
