指定した桁数を表示できるHTML・JSコードを公開します。また桁数が多いためバックグラウンド仕様のworker.jsで作成しました。
仕様は桁数の入力・計算開始・表示クリア・印刷となっており、1万桁ならほぼ一瞬で表示、10万桁なら約1分超で表示できるOffscreenCanvas 版です。※CPUスペック・メモリ必須
また、高速スピゴット法・C版(C → WASM → JavaScript)もオプションとしてコードを載せておきますので参考まで。

| PI.HTML |
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>円周率 =</title>
</head>
<body>
<div style="margin-bottom:10px;">
桁数:<input id="digitsInput" type="number" value="100">
<button onclick="start()">描画開始</button>
<button onclick="clearCanvas()">クリア</button>
<button onclick="printCanvas()">印刷</button>
</div>
<canvas id="piCanvas" width="800"></canvas>
<script>
// ===============================
// 変数宣言
// ===============================
let digits = 100;
let fontSize = 16;
let x = 10;
let y = 30;
let blockCount = 0;
const canvas = document.getElementById('piCanvas');
const ctx = canvas.getContext('2d');
const worker = new Worker("worker.js");
/*const worker = new Worker("worker.js?v=2");*/
worker.onmessage = function(e) {
const data = e.data;
if (data.error) {
alert(data.error);
return;
}
if (data.done) {
console.log("計算完了");
return;
}
if (data.batch) {
// バッチで受け取って一気に描画
for (const block of data.batch) {
drawBlock(block);
}
return;
}
if (data.block) {
drawBlock(data.block);
}
};
// ===============================
// 行幅計測
// ===============================
function measureLineWidth() {
ctx.font = `${fontSize}px monospace`;
let line = "3.14159 ";
for (let i = 1; i <= 20; i++) {
if (i % 2 === 1 && i > 1) line += " ";
line += "00000";
}
return ctx.measureText(line).width;
}
// ===============================
// 描画開始
// ===============================
function start() {
digits = parseInt(document.getElementById('digitsInput').value, 10);
const width = measureLineWidth();
canvas.width = width + 20;
const lines = Math.ceil(digits / 100);
canvas.height = (lines + 1) * (fontSize + 10);
ctx.fillStyle = "#f0f8ff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "#333";
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000";
ctx.font = `${fontSize}px monospace`;
x = 10;
y = fontSize + 10;
blockCount = 0;
drawText("3.");
worker.postMessage({ cmd: "start", value: digits });
}
// ===============================
// ブロック描画
// ===============================
function drawBlock(block) {
blockCount++;
if (blockCount % 2 === 1 && blockCount > 1) {
drawText(" ");
}
drawText(block);
if (blockCount % 20 === 0) {
x = 10;
y += fontSize + 10;
}
}
// ===============================
// テキスト描画
// ===============================
function drawText(text) {
ctx.fillText(text, x, y);
x += ctx.measureText(text).width;
}
// ===============================
// クリア
// ===============================
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// ===============================
// 印刷(全ブラウザ対応版)
// ===============================
function printCanvas() {
const dataURL = canvas.toDataURL("image/png");
const w = window.open("", "_blank");
w.document.write(`
<html>
<head><title>Canvas Print</title></head>
<body style="margin:0; text-align:center; font-size:18px;">
<div style="margin:10px 0; font-weight:bold;">
円周率(表示桁数:${digits} 桁)
</div>
<img id="printImg" src="${dataURL}" style="max-width:100%;"/>
<div style="margin-top:20px; font-size:16px; color:#555;">
※ 入力桁数:${digits} 桁
</div>
</body>
</html>
`);
w.document.close();
const waitDomReady = setInterval(() => {
if (w.document.readyState === "complete") {
clearInterval(waitDomReady);
const img = w.document.getElementById("printImg");
img.onload = () => {
setTimeout(() => {
w.focus();
w.print();
}, 100);
};
if (img.complete) {
setTimeout(() => {
w.focus();
w.print();
}, 100);
}
}
}, 50);
}
</script>
</body>
</html>
| worker.js |
// π 計算 Worker(Spigot法 + OffscreenCanvas描画)
onmessage = async function(e) {
const digits = e.data.digits;
// -----------------------------
// OffscreenCanvas 準備
// -----------------------------
const fontSize = 16;
const blocks = Math.ceil(digits / 5);
const lines = Math.ceil(blocks / 20);
const width = 2000;
const height = (lines + 1) * (fontSize + 10);
const off = new OffscreenCanvas(width, height);
const ctx = off.getContext("2d");
ctx.fillStyle = "#f0f8ff";
ctx.fillRect(0, 0, width, height);
ctx.strokeStyle = "#333";
ctx.strokeRect(0, 0, width, height);
ctx.fillStyle = "#000";
ctx.font = `${fontSize}px monospace`;
let x = 10;
let y = fontSize + 10;
let blockCount = 0;
// -----------------------------
// π 計算準備
// -----------------------------
let base = 100000;
let a = new Array(5000).fill(0);
let carry = 0;
let index = 0;
let drawnDigits = 0;
let lastBlock = "";
// 最初の "3."
ctx.fillText("3.", x, y);
x += ctx.measureText("3.").width;
// -----------------------------
// π 計算ループ
// -----------------------------
while (drawnDigits < digits) {
let b = 0;
// --- 前半 ---
for (let j = index + 17; j > index; j--) {
b = (20 * base + b) * j;
a[j] = b % (2 * j + 1);
b = Math.floor(b / (2 * j + 1));
}
// --- 後半 ---
for (let j = index; j > 0; j -= 17) {
a[j] += b * j;
b = 0;
const jEnd = j - 17;
for (let k = j; k > jEnd; k--) {
const l = 2 * k + 1;
const c = Math.floor(a[k] / l) * base + Math.floor(b / l) * k;
b = (b % l) * k + (a[k] % l) * base;
a[k] = b % l;
b = c + Math.floor(b / l);
}
}
carry = carry * base + b;
if (index > 0) {
const block = ('00000000000' + carry).slice(-11, -6);
if (block === lastBlock) {
postMessage({ error: "循環小数" });
return;
}
lastBlock = block;
drawnDigits += 5;
// -----------------------------
// Worker 内で描画
// -----------------------------
blockCount++;
if (blockCount % 2 === 1 && blockCount > 1) {
ctx.fillText(" ", x, y);
x += ctx.measureText(" ").width;
}
ctx.fillText(block, x, y);
x += ctx.measureText(block).width;
if (blockCount % 20 === 0) {
x = 10;
y += fontSize + 10;
}
carry %= (10 * base);
}
index += 17;
}
// -----------------------------
// 完成した OffscreenCanvas をメインへ送る
// -----------------------------
const blob = await off.convertToBlob();
postMessage({ blob, width, height });
};
高速スピゴット法・C版
gcc pi_spigot.c -O3 -o pi_spigot (ターミナル)
./pi_spigot 100 # 100桁
./pi_spigot 1000 # 1000桁
./pi_spigot 10000 # 10000桁
| pi_spigot.c |
#include <stdio.h>
#include <stdlib.h>
#define BASE 100000 // 5桁ブロック用
#define ARR_SIZE 5000 // 配列サイズ(JS版と同程度)
int main(int argc, char *argv[]) {
int digits = 100; // デフォルト桁数
if (argc >= 2) {
digits = atoi(argv[1]);
if (digits <= 0) {
fprintf(stderr, "桁数は正の整数で指定してください。\n");
return 1;
}
}
int *a = (int *)calloc(ARR_SIZE, sizeof(int));
if (!a) {
fprintf(stderr, "メモリ確保に失敗しました。\n");
return 1;
}
long long carry = 0;
int index = 0;
int drawnDigits = 0;
// 先頭の "3."
printf("3.");
while (drawnDigits < digits) {
long long b = 0;
// --- 前半 ---
for (int j = index + 17; j > index; j--) {
b = (20LL * BASE + b) * j;
a[j] = (int)(b % (2 * j + 1));
b = b / (2 * j + 1);
}
// --- 後半 ---
for (int j = index; j > 0; j -= 17) {
a[j] += (int)(b * j);
b = 0;
int jEnd = j - 17;
for (int k = j; k > jEnd; k--) {
int l = 2 * k + 1;
long long c = (long long)(a[k] / l) * BASE
+ (long long)(b / l) * k;
b = (long long)(b % l) * k
+ (long long)(a[k] % l) * BASE;
a[k] = (int)(b % l);
b = c + b / l;
}
}
carry = carry * BASE + b;
if (index > 0) {
// 5桁ブロック抽出
int blockVal = (int)((carry / BASE) % BASE);
// 5桁ゼロ埋めで出力
printf("%05d", blockVal);
drawnDigits += 5;
carry = carry % (10LL * BASE);
}
index += 17;
}
printf("\n");
free(a);
return 0;
}
この C コードを WASM にコンパイルしてブラウザから呼び、出力を 5桁ブロック配列として JS に渡す。それを上記の Canvas 描画に流す
C → WASM(Emscripten)ターミナルでEmscripten をインストール
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
C のスピゴット法を WASM 用に書く(C 関数)
#include <stdint.h>
#include <stdlib.h>
#define BASE 100000
#define ARR_SIZE 5000
// WASM から呼び出す関数
// digits: 欲しい桁数
// out: 5桁ブロックを格納する配列(JS側で確保)
void compute_pi(int digits, uint32_t *out) {
int *a = (int *)calloc(ARR_SIZE, sizeof(int));
long long carry = 0;
int index = 0;
int drawn = 0;
while (drawn < digits) {
long long b = 0;
for (int j = index + 17; j > index; j--) {
b = (20LL * BASE + b) * j;
a[j] = b % (2 * j + 1);
b /= (2 * j + 1);
}
for (int j = index; j > 0; j -= 17) {
a[j] += b * j;
b = 0;
int jEnd = j - 17;
for (int k = j; k > jEnd; k--) {
int l = 2 * k + 1;
long long c = (a[k] / l) * BASE + (b / l) * k;
b = (b % l) * k + (a[k] % l) * BASE;
a[k] = b % l;
b = c + b / l;
}
}
carry = carry * BASE + b;
if (index > 0) {
uint32_t block = (carry / BASE) % BASE;
out[drawn / 5] = block;
drawn += 5;
carry %= (10LL * BASE);
}
index += 17;
}
free(a);
}
C → WASM にコンパイル
emcc pi.c -O3 \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_compute_pi"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap","getValue"]' \
-o pi.js
pi.js(JavaScript ラッパー)・pi.wasm(高速ネイティブコード)が生成
JavaScript から WASM を呼び出し
import init from "./pi.js";
let Module;
async function loadWasm() {
Module = await init();
// C関数 compute_pi を JS から呼び出せるようにする
const computePi = Module.cwrap("compute_pi", null, ["number", "number"]);
return computePi;
}
WASM に配列を渡して π を計算
async function runPi(digits) {
const computePi = await loadWasm();
const blocks = Math.ceil(digits / 5);
// WASM メモリに配列を確保
const ptr = Module._malloc(blocks * 4);
// π 計算(C側で ptr に書き込まれる)
computePi(digits, ptr);
// JS 側に読み出す
const result = [];
for (let i = 0; i < blocks; i++) {
result.push(Module.getValue(ptr + i * 4, "i32"));
}
Module._free(ptr);
return result;
}
OffscreenCanvas に描画(既存コード使用)
const blocks = await runPi(digits);
const off = new OffscreenCanvas(width, height);
const ctx = off.getContext("2d");
ctx.fillText("3.", x, y);
let blockCount = 0;
for (const block of blocks) {
blockCount++;
if (blockCount % 2 === 1 && blockCount > 1) {
ctx.fillText(" ", x, y);
x += ctx.measureText(" ").width;
}
const text = block.toString().padStart(5, "0");
ctx.fillText(text, x, y);
x += ctx.measureText(text).width;
if (blockCount % 20 === 0) {
x = 10;
y += fontSize + 10;
}
}
const blob = await off.convertToBlob();
postMessage({ blob });
このコードで10万桁でも数十秒ぐらいで処理可能です。
ローカル環境でWebサーバを動かしたい場合は、ターミナル等でpython -m http.server 8000を入力 ブラウザでhttp://localhost:8000を実行すると動作確認ができます。
※全てのコードは参考コードなので、コピペしたコードが仮に不具合が生じても管理者は責任を負いません。
