背景
自分のサイトに小さいツールをたくさん置いていて、そのうちのひとつに法人番号のチェックディジット計算があります。
法人番号というのは、国税庁が法人に割り当てている13桁の番号です。
先頭1桁が検査用数字(チェックディジット)で、残りの12桁が基礎番号。
入力フォームで受け取ったときに、写し間違いをその場で弾くために使います。
チェックディジットは要するに「他の桁から計算で出せる1桁」で、入力された値と計算値が合わなければ、どこか間違っているという判定ができます。
このツールを直しているときに、私は最初、他のコード体系の実装をそのまま持ってこようとしました。
そして、それだと合わないということに気づきました。
その話です。
何が違うのか
まず、よくある方の例を出します。
ISBN-13 や JAN コードのチェックディジットは、こういう形です。
// ISBN-13: 左から 1,3,1,3... の重みを掛けて合計し、10 の補数を取る
function cd13(first12) {
var sum = 0;
for (var i = 0; i < 12; i++) sum += Number(first12[i]) * (i % 2 === 0 ? 1 : 3);
return String((10 - (sum % 10)) % 10);
}
first12 は ISBN-13 の先頭12桁を文字列で渡したものです。
戻り値が末尾に付く1桁になります。
法(割る数)は 10、重みは 1 と 3 の交互、数える向きは左から。
ISBN-10 だとまた違って、法が 11 になります。
function cd10(first9) {
var sum = 0;
for (var i = 0; i < 9; i++) sum += (10 - i) * Number(first9[i]);
var r = (11 - (sum % 11)) % 11;
return r === 10 ? 'X' : String(r);
}
余りが 10 になったときだけ X になるのは、11 通りの値を1文字で表せないからです。
ここは有名な話なので知っている人も多いと思います。
で、法人番号です。
国税庁が公開している算式はこうなっています。
9 - ( (奇数桁の和 × 1 + 偶数桁の和 × 2) mod 9 )
法が 9 です。
10 でも 11 でもない。
桁の数え方でハマる
さらに厄介なのが、この「奇数桁・偶数桁」の数え方です。
仕様では、基礎番号12桁の1桁目を最下位として数えます。
つまり右端が1桁目。
ISBN の実装は左から数えているので、同じ感覚で書くと重みが逆に付きます。
そして困ったことに、逆に付けても計算自体は通ってしまいます。
エラーにならず、ただ間違った答えが出る。
実装はこうなりました。
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));
}
body12 は基礎番号12桁の文字列です。
body12[11 - i] のところが肝で、インデックスを反転させることで「右端から i 番目」を取っています。
i は 0 始まりなので、桁の番号にするために (i + 1) % 2 で奇数偶数を判定しています。
ここを i % 2 と書くと、奇数と偶数がまるごと入れ替わります。
これも例外を出さずに、静かに違う答えを返します。
実際に確かめる
ツールに例として入れてある番号で見てみます。
基礎番号 700150000901 を入れると、検査用数字は 1 になり、13桁は 1700150000901 になります。
計算過程を出すとこうです。
odd: 1 + 9 + 0 + 0 + 1 + 0 = 11
even: 0 + 0 + 0 + 5 + 0 + 7 = 12
(11 + 12 x 2) mod 9 = 35 mod 9 = 8
9 - 8 = 1
右端の 1 から数え始めているのが分かると思います。
odd 側の先頭にある 1 は、基礎番号の最後の文字です。
ここで仮に左から数える実装にすると、odd と even の中身が入れ替わって (12 + 11 × 2) mod 9 = 34 mod 9 = 7 となり、検査用数字は 2 になります。
別の答えが出ているのに、コードはエラーを出しません。
だから、この手の実装はテストを書かないと合っているかどうか分からない。
公開されている実在の法人番号をいくつか入れて、全部通ることを確認するのが早いです。
ツールはここに置いてあります。
https://hashitosystem.com/tools/corpnum/
計算の各段(odd の内訳、even の内訳、剰余、補数)をそのまま画面に出すようにしました。
自分の実装の答え合わせに使えるようにしたかったからです。
チェックディジットの実装は、どれも似た形をしているので流用したくなります。
でも法が違い、重みが違い、数える向きが違う。
似ているものほど、仕様のほうを一度読み直してから書いたほうが安全です。
本記事はAI補助で執筆した、個人開発の紹介記事です。