背景
自分のサイトに、数字と漢数字を相互変換するツールを置いています。
年賀状やのし袋の金額を縦書きにするときに、毎回「一万円」と書くのか「万円」なのか迷うので作りました。
作ってみて、1 を省ける位と省けない位があることを、ちゃんと意識していなかったことに気づきました。
その備忘録です。
漢数字の位取り表記は、十・百・千で1桁ずつ、万・億・兆で4桁ずつ位を上げる書き方だ。
12345678 なら「千二百三十四万五千六百七十八」になる。
結論
十・百・千の前の「一」は省き、万・億・兆の前の「一」は省きません。
- 1000 → 千
- 10000 → 一万
- 10000000 → 千万
- 100000000 → 一億
「千円」とは言っても「万円」とは言わないのと同じです。
千万の「千」は、万の前に付く数字のほうなので、一は付けません。
数字から漢数字へ
ツールでは下記のように、4桁ずつ区切って変換しています。
numStr は画面で入力した数字の文字列(カンマや空白は取り除いたもの)です。
16桁(兆の位)までを扱います。
function numToKanji(numStr) {
let s = String(numStr).replace(/[,\s]/g, "").replace(/^0+(?=\d)/, "");
if (!/^\d+$/.test(s) || s.length > 16) return null;
if (s === "0") return "零";
const digits = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
const units = ["", "十", "百", "千"];
const groups = ["", "万", "億", "兆"];
let out = "";
let gi = 0;
while (s.length) {
const chunk = s.slice(-4); // 下から4桁
s = s.slice(0, -4);
let part = "";
for (let i = 0; i < chunk.length; i++) {
const d = +chunk[chunk.length - 1 - i];
if (!d) continue; // 0 の位は書かない
const dg = d === 1 && i > 0 ? "" : digits[d]; // 十百千の前の一を省く
part = dg + units[i] + part;
}
if (part) out = part + groups[gi] + out; // 区切りが全部0なら万なども書かない
gi++;
}
return out;
}
ポイントは、一を省く判定が区切りの中(i > 0、つまり十百千の位)だけで行われることです。
区切りの単位の「万」の前には、区切りの中身が「一」でもそのまま付きます。
いくつか入れてみると、下記になりました(Node.js v24.11.1)。
1 -> 一
10 -> 十
1000 -> 千
1001 -> 千一
10000 -> 一万
11000 -> 一万千
110000 -> 十一万
10000000 -> 千万
2026 -> 二千二十六
10000 が「一万」、10000000 が「千万」になっているのがわかると思います。
11000 は「一万千」で、「一万一千」とは書いていません。
小切手や契約書のように、あとから書き足されると困る場面では「一千」と書くこともありますが、このツールは日常の表記に合わせました(改ざんを防ぎたいなら、壱・弐・拾などの大字のほうを使う前提です)。
漢数字から数字へ
逆向きは、省かれた一も、付いている一も、どちらも読めないといけません。
人が書く漢数字には「一千万」も「千万」もあるし、年号なら「二〇二六」のような数字並びもあるからです。
下記は、ツールの読み取り部分を簡略化したものです。
str は入力欄の文字列で、桁が大きくなっても丸められないように BigInt で足し上げています。
const KDIG = { 〇: 0, 零: 0, 一: 1, 二: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9 };
const KSMALL = { 十: 10n, 百: 100n, 千: 1000n };
const KBIG = { 万: 10000n, 億: 100000000n, 兆: 1000000000000n };
function kanjiToNum(str) {
let total = 0n, section = 0n, num = 0n, hasNum = false;
for (const ch of str.replace(/[\s,]/g, "")) {
if (ch >= "0" && ch <= "9") { num = num * 10n + BigInt(ch); hasNum = true; }
else if (ch in KDIG) { num = num * 10n + BigInt(KDIG[ch]); hasNum = true; }
else if (ch in KSMALL) { section += (hasNum ? num : 1n) * KSMALL[ch]; num = 0n; hasNum = false; }
else if (ch in KBIG) {
const head = section + num || 1n; // 「万」だけなら一万
total += head * KBIG[ch];
section = 0n; num = 0n; hasNum = false;
} else return null;
}
return (total + section + num).toString();
}
section は万未満の区切りの合計、num は直前に読んだ数字だ。
十百千が来たときに、直前に数字が無ければ 1 として掛けるので、「千」も「一千」も 1000 になる。
数字が続いたときは num * 10 + 数字 で桁を上げるので、「二〇二六」は 2026 になる。
同じように入れてみると、下記になりました。
千万 -> 10000000
一千万 -> 10000000
万 -> 10000
二〇二六 -> 2026
二千二十六 -> 2026
3万5千 -> 35000
一〇〇万 -> 1000000
省かれていても付いていても、同じ数になっています。
「3万5千」のように算用数字が混ざっていても読めるので、ニュースの見出しのような表記もそのまま貼れます。
作ったツールはここに置いています。
https://hashitosystem.com/tools/kansuji/
漢数字にするときは、十百千の前の一は落とし、万億兆の前の一は必ず書く。
戻すときは、一が省かれた形も数字並びの形も同じ数として読む、と決めておけば迷いません。
本記事はAI補助で執筆した、個人開発の紹介記事です。