法人番号の検査用数字をJSで計算する — mod 9の0と9の死角

法人番号は 13 桁だが、中身は「12 桁の基礎番号」と、その前に付く「1 桁の検査用数字(チェックディジット)」である。取引先マスタに法人番号の列を足すとき、入力ミスを手元で弾きたいなら、この 1 桁を計算できれば足りる。通信も API キーも要らない。

この記事では、ハシトシステムの法人番号チェックディジット計算ツール の実装に沿って算式を追い、Node.js で国税庁の例と国税庁自身の法人番号を検算する。あわせて、この算式が原理的に見逃す入力ミス(0 と 9)を総当たりで確かめる。

算式は財務省令で決まっている

国税庁の法人番号公表サイトにある資料「法人番号の構成」は、検査用数字の算式を次のように示している(財務省令 2 条)。

1
2
3
4
9 - ( Σ[n=1..12] Pn × Qn  を 9 で除した余り )

Pn : 基礎番号の最下位の桁を 1 桁目としたときの n 桁目の数字
Qn : n が奇数のとき 1、n が偶数のとき 2

つまり、右端から数えて奇数桁はそのまま、偶数桁は 2 倍して全部足し、9 で割った余りを 9 から引く。

同じサイトの資料「チェックデジットの計算」には計算例がある。会社法人等番号 700110005901 の場合、最下位から偶数桁の和が 13、奇数桁の和が 11 で、13 × 2 + 11 = 37、37 ÷ 9 の余りが 1、9 - 1 = 8 となり、法人番号は 8700110005901 になる。

株式会社など設立登記法人の基礎番号は、商業登記法に基づく 12 桁の会社法人等番号そのものである。国の機関や地方公共団体は、基礎番号の上位が 0000 で始まる(同じく「法人番号の構成」の表による)。

ツールの実装

計算ツールの checkDigit は、算式をそのまま 1 ループにしている。

1
2
3
4
5
6
7
8
9
10
11
// 国税庁が公開している検査用数字の算式:
// 9 - ( (奇数桁の和 x 1 + 偶数桁の和 x 2) mod 9 )
// 桁の数え方は「基礎番号(12桁)の1桁目を最下位」とする。
function checkDigit(body12) {
var odd = 0, even = 0;
for (var i = 0; i < 12; i++) {
var d = Number(body12[11 - i]); // 最下位から数える
if ((i + 1) % 2 === 1) odd += d; else even += d;
}
return String(9 - ((odd + even * 2) % 9));
}

ポイントは 2 つある。

  • 桁は右から数える。body12[11 - i] で最下位から読み、i + 1 を算式の n に当てている。左から数えると、12 桁は偶数桁数なので奇数と偶数がすべて入れ替わり、別の値になる。
  • 入力は文字列のまま扱う。ツールは 12 桁なら検査用数字を付けた 13 桁を表示し、13 桁なら先頭 1 桁と計算値を突き合わせる。ハイフンと空白は replace(/[\s-]/g, '') で除いてから ^[0-9]{12}$|^[0-9]{13}$ で検査する。

検査用数字は 0 にならない

x mod 9 は 0〜8 なので、9 - (x mod 9) は 1〜9 になる。「法人番号の構成」の表でも、検査用数字の欄は「1〜9」となっている。言い換えると、先頭が 0 の 13 桁は法人番号ではない。入力チェックに ^[1-9][0-9]{12}$ を使えば、計算の前に形だけで弾ける。

0 と 9 を取り違えても通ってしまう

9 を法とする計算なので、ある桁が 0 から 9(またはその逆)に変わると、その桁の寄与は重み 1 なら 9、重み 2 なら 18 だけ変わる。どちらも 9 の倍数なので余りが変わらず、検査用数字は同じになる。

隣り合う 2 桁の入れ替えも同様で、重みが (1, 2) の位置で a と b を入れ替えると、積和は (b - a) × 1 + (a - b) × 2 = a - b だけ変わる。これが 9 の倍数になるのは a と b が 0 と 9 のときだけである。つまり、

  • 1 桁の書き換えは、0 と 9 の取り違えだけ見逃す
  • 隣り合う 2 桁の入れ替えは、09 と 90 の入れ替えだけ見逃す

ということになる。JAN コード(EAN-13)の重み 1 と 3・モジュラス 10 とは性質が違う(JAN の検算は「JANの末尾1桁はどう決まるか」で扱った)。

実際に試す

Node.js 24.11.1(Windows 11)で動作を確かめた。依存パッケージは要らない。次の内容を corpnum.mjs として保存する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// corpnum.mjs : 法人番号の検査用数字(財務省令の算式)を計算・検証する
function checkDigit(base12) {
if (!/^[0-9]{12}$/.test(base12)) throw new Error('base must be 12 digits (string)');
let sum = 0;
for (let n = 1; n <= 12; n++) {
const p = Number(base12[12 - n]); // 最下位を1桁目とした n 桁目
const q = n % 2 === 1 ? 1 : 2; // n が奇数なら1、偶数なら2
sum += p * q;
}
return 9 - (sum % 9);
}
function isValid(corp13) {
const s = String(corp13).replace(/[\s-]/g, '');
if (!/^[0-9]{13}$/.test(s)) return false;
return checkDigit(s.slice(1)) === Number(s[0]);
}

// 1) 国税庁資料の例と、国税庁自身の法人番号
console.log('700110005901 ->', checkDigit('700110005901'));
console.log('7000012050002 valid?', isValid('7000012050002'));

// 2) 値域: ランダムな基礎番号 100万件で検査用数字の分布を見る
const hist = new Array(10).fill(0);
for (let i = 0; i < 1e6; i++) {
let b = '';
for (let j = 0; j < 12; j++) b += Math.floor(Math.random() * 10);
hist[checkDigit(b)]++;
}
console.log('digit histogram 0..9:', hist.join(' '));

// 3) 単一桁の書き換えで見逃す組み合わせ(重み1と2の両方で調べる)
const miss1 = new Set();
for (let a = 0; a <= 9; a++) for (let d = 0; d <= 9; d++) {
if (a === d) continue;
for (const q of [1, 2]) if (((d - a) * q) % 9 === 0) miss1.add(`${a}->${d}`);
}
console.log('undetected single-digit substitutions:', [...miss1].join(', '));

// 4) 隣り合う2桁の入れ替えで見逃す組み合わせ
const missT = new Set();
for (let a = 0; a <= 9; a++) for (let b = 0; b <= 9; b++) {
if (a === b) continue;
if ((a - b) % 9 === 0) missT.add(`${a}${b}<->${b}${a}`);
}
console.log('undetected adjacent transpositions:', [...missT].join(', '));

// 5) 国税庁の例 8700110005901 に、実際に 0/9 のミスを入れてみる
console.log('8700110005091 valid?', isValid('8700110005091')); // 末尾近くの 90 -> 09
console.log('8700119005901 valid?', isValid('8700119005901')); // 0 -> 9 に書き換え

// 6) 数値型に入れると先頭の0が消える
console.log('Number("000012050002") ->', Number('000012050002'));
1
node corpnum.mjs

出力(分布の数値は乱数なので実行ごとに少し変わる):

1
2
3
4
5
6
7
8
700110005901 -> 8
7000012050002 valid? true
digit histogram 0..9: 0 111691 111169 110957 111121 111441 111120 111048 110289 111164
undetected single-digit substitutions: 0->9, 9->0
undetected adjacent transpositions: 09<->90, 90<->09
8700110005091 valid? true
8700119005901 valid? true
Number("000012050002") -> 12050002

1 行目は国税庁資料の計算例(8)と一致する。2 行目の 7000012050002 は国税庁自身の法人番号で、国税庁サイトのヘッダ に掲載されている。基礎番号が 000012 で始まるのは、上の表でいう国の機関(行政機関)の区分にあたる。

3 行目のとおり、検査用数字に 0 は 1 度も出ない。5 行目以降の 2 つは、どちらも本来の 8700110005901 とは別の番号なのに検査を通っている。検査用数字が合っていても、番号が実在するとは限らないことがわかる。

実装するときの注意

  • 数値型にしない。最後の行のとおり、国の機関などの基礎番号は 0000 で始まるので、Number や parseInt、表計算ソフトの数値セルに入れた時点で先頭の 0 が消え、桁がずれて計算が狂う。CSV で受け取るときは文字列の列として読む。
  • 形式チェックと検査用数字のチェックは別物。検査用数字が合っても、0 と 9 のミスは残るし、番号が実在する保証にもならない。実在の確認が必要なら、国税庁の法人番号公表サイト(Web-API を含む)で照会する。
  • 右から数える。12 桁を左から数えると重みがすべて逆になる。国税庁の例 700110005901 で 8 が出ることをテストに入れておくと、取り違えをすぐ検出できる。

まとめ

  • 法人番号の先頭 1 桁は、12 桁の基礎番号から「9 - (右から奇数桁 × 1 + 偶数桁 × 2 の和 mod 9)」で決まり、値は必ず 1〜9 になる。
  • 9 を法とするため、0 と 9 の書き換え、09 と 90 の入れ替えだけは検査用数字では見つからない。
  • 基礎番号は文字列のまま扱う。先頭の 0000 が消えると計算が合わなくなる。

ツール: 法人番号チェックディジット計算(ハシトシステム)。12 桁を入れると検査用数字を付けた 13 桁を、13 桁を入れると検査結果と計算過程を表示する。JAN や ISBN などの検査用数字は チェックディジット計算ツール で確かめられる。