はじめに
日本語の数字表記をプログラムで扱うと、英語の感覚では意外なところでつまずきます。英語は 1,000 ごとに thousand / million / billion と単位が変わりますが、日本語は 10,000(万)ごとに単位が変わります。この「4桁区切り」を理解しておくと、漢数字への変換はかなりシンプルに書けます。
完成形の動きを先に確認したい場合は、数字を入力すると漢字表記とローマ字の読みを表示してくれる DigitalKW の Japanese Number Converter が参考になります。この記事では、同じような変換を JavaScript でゼロから組み立てながら、どこに例外があるのかを整理していきます。
日本語の数字の構造
日本語の数は、次の 2 種類の単位の組み合わせでできています。
| 種類 | 単位 | 値 |
|---|---|---|
| 小さい単位 | 十・百・千 | 10, 100, 1000 |
| 大きい単位 | 万・億・兆・京 | 10^4, 10^8, 10^12, 10^16 |
たとえば 123,456,789 は、4桁ずつ区切ると 1 | 2345 | 6789 になります。
-
1→ 一 + 億 -
2345→ 二千三百四十五 + 万 -
6789→ 六千七百八十九
つなげると「一億二千三百四十五万六千七百八十九」です。英語のように 3 桁区切り(123,456,789)で考えると、億や万の位置がずれてしまうので注意が必要です。
4桁ブロックを変換する
まず 0〜9999 の範囲だけを変換する関数を作ります。ポイントは「1 のときは数字を書かない」というルールです。10 は「一十」ではなく「十」、100 は「一百」ではなく「百」と書きます。
const DIGITS = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
const SMALL = ["", "十", "百", "千"];
function fourDigits(n) {
let s = "";
for (let i = 3; i >= 0; i--) {
const d = Math.floor(n / 10 ** i) % 10;
if (d === 0) continue; // 0 の位は何も書かない
s += (d === 1 && i > 0 ? "" : DIGITS[d]) + SMALL[i];
}
return s;
}
0 の位を飛ばすだけで、105 は「百五」、2026 は「二千二十六」になります。アラビア数字のように「〇」で桁を埋める必要はありません。
大きい単位をつなげる
次に、数字全体を 10000 で割りながら 4 桁ずつ取り出し、ブロックごとに万・億・兆を付けます。兆を超える値は Number の安全な整数の範囲(約 9007 兆)に近づくので、最初から BigInt で扱っておくと安心です。
const LARGE = ["", "万", "億", "兆", "京"];
function toKansuji(value) {
let n = BigInt(value);
if (n === 0n) return "零";
let out = "";
let unit = 0;
while (n > 0n) {
const chunk = Number(n % 10000n);
if (chunk !== 0) out = fourDigits(chunk) + LARGE[unit] + out;
n /= 10000n;
unit++;
}
return out;
}
ブロックが 0 のときに単位ごとスキップしているのが大事なところです。これがないと 300,000,000 が「三億万」のようなおかしな表記になります。
実行結果は次のとおりです。
0 零
10 十
105 百五
2026 二千二十六
10000 一万
10500 一万五百
123456789 一億二千三百四十五万六千七百八十九
300000000 三億
1000000000000 一兆
10000 は「万」ではなく「一万」になります。小さい単位(十・百・千)では 1 を省略しますが、大きい単位では「一万」「一億」と書くのが普通です。上のコードでは、チャンクが 1 のとき fourDigits(1) が「一」を返すので、自然にこのルールを満たしています。
「一千万」か「千万」か
迷いやすいのが 10,000,000 です。コードの出力は「千万」ですが、金額や公的な文書では「一千万」と書く場面も多くあります。どちらも誤りではないので、用途に合わせて fourDigits の先頭の千にだけ「一」を付けるオプションを用意しておくと便利です。小切手や契約書のように改ざんを防ぎたい場合は、さらに「壱・弐・参・拾」などの大字を使うこともあります。
読み方(ローマ字)の例外
漢字表記は機械的に作れますが、読み方には音の変化があります。代表的なものはこのあたりです。
| 数字 | 読み | 変化 |
|---|---|---|
| 300 | sanbyaku | h → b |
| 600 | roppyaku | 促音 + p |
| 800 | happyaku | 促音 + p |
| 3000 | sanzen | s → z |
| 8000 | hassen | 促音 |
単純に 数字の読み + "hyaku" で連結すると「sanhyaku」「rokuhyaku」になってしまうので、例外だけを辞書で持つのが現実的です。
const READ_DIGIT = ["", "ichi", "ni", "san", "yon", "go", "roku", "nana", "hachi", "kyuu"];
function readFour(n) {
const parts = [];
const th = Math.floor(n / 1000), h = Math.floor(n / 100) % 10,
t = Math.floor(n / 10) % 10, o = n % 10;
if (th) parts.push({ 1: "sen", 3: "sanzen", 8: "hassen" }[th] || READ_DIGIT[th] + "sen");
if (h) parts.push({ 1: "hyaku", 3: "sanbyaku", 6: "roppyaku", 8: "happyaku" }[h] || READ_DIGIT[h] + "hyaku");
if (t) parts.push(t === 1 ? "juu" : READ_DIGIT[t] + "juu");
if (o) parts.push(READ_DIGIT[o]);
return parts.join(" ");
}
console.log(readFour(1984)); // sen kyuuhyaku hachijuu yon
4 と 7 と 9 には「shi / yon」「shichi / nana」「ku / kyuu」の 2 通りの読みがあります。数を数えるときや単位の前では「yon」「nana」「kyuu」が使われることが多いため、ここではそちらに統一しました。日付(四月 = shigatsu)や時刻(九時 = kuji)では別の読みになるので、用途が決まっている場合は辞書を分けるのがおすすめです。
まとめ
- 日本語の数字は 3 桁ではなく 4 桁ごとに単位が変わる
- 十・百・千の前の「一」は省略し、万・億の前の「一」は省略しない
- ブロックが 0 のときは大きい単位ごと書かない
- 兆を超える値は
BigIntで扱う - 読み方は 300・600・800・3000・8000 などの音便を辞書で処理する
この 5 点を押さえておけば、請求書の金額表示や日本語学習アプリのような場面でも、漢数字の変換で困ることはほとんどありません。逆方向(漢数字 → アラビア数字)の変換も、同じ 4 桁ブロックの考え方で、万・億の位置で区切ってから千・百・十を足し合わせれば実装できます。