読者です 読者をやめる 読者になる 読者になる

沖の雑記帳

基本的には趣味に絡んで雑多な内容を色々と

Windowsバッチファイルでマイナンバーのチェックデジットを計算

バッチファイル 雑記 マイナンバー プログラミング

Visual Batを入れてみたことだし、使い勝手の検証のためになんかいいものはないかなと思ったらこんな記事を見つけました。 ちょっと流行(?)に乗り遅れた感じがしますね。
でもそもそもバッチファイル自体が流行以前の問題なので問題ないですね。

マイナンバーのチェックデジットを計算する - Qiita

計算方法やらなんやらは上記サイトを参照してください。

というわけで、早速書いてみました。なんか割りと試行錯誤しすぎてかなり汚いコードになってます。
もっと綺麗に書けるかもしれないんで、わかる方がいたらコメントいただけないでしょうか?

マイナンバーのチェックデジット計算

@echo off
REM validate_mynumber.bat
REM マイナンバーのチェックサムを検証する
REM 参考:http://qiita.com/qube81/items/fa6ef94d3c8615b0ce64
setlocal enabledelayedexpansion
call :calc_strlen %1
if %strlen% neq 12 echo requires 12-digit input & goto end

set mynumber=%1
set check_digit=%mynumber:~-1%
set /a sum=0
set temp=%mynumber:~0,-1%

for /l %%I in (1,1,11) do (
  if %%I leq 6 (
    set /a q=%%I+1
  ) else (
    set /a q=%%I-5
  )
  set p=!temp:~-1!
  set /a sum+=!p!*!q!
  set temp=!temp:~0,-1!
)
set /a remainder=!sum!%%11

if !remainder! leq 1 (
  set check=0
) else (
  set /a check=11-!remainder!
)
if !check_digit! equ !check! (
  echo true
) else (
  echo false
)
goto end

:calc_strlen
set str=%1
set strlen=0
:loop_calc
if defined str (
  set str=%str:~1%
  set /a strlen+=1
  goto :loop_calc
)
exit /b

:end
endlocal

検証用バッチファイル

@echo off
setlocal enabledelayedexpansion

set number=12345678901
for /l %%I in (0,1,9) do (
  echo input:%number%%%I
  call validate_mynumber %number%%%I
)

endlocal

検証結果

$ test.bat
input:123456789010
false
input:123456789011
false
input:123456789012
false
input:123456789013
false
input:123456789014
false
input:123456789015
false
input:123456789016
false
input:123456789017
false
input:123456789018
true
input:123456789019
false

ちゃんと、8の時にtrueになっているので合っているようですね。
一応、自分のマイナンバーも入力してみましたがしっかりtrueが返ってきたのでよかった(返ってこないと大問題)

追記

各言語の実装をまとめられる方からコメントをいただきました。
参考までに現在どのような言語でマイナンバーのチェックデジット計算が実装されているか、自分はここにはない別の言語で実装してやるなんかの参考にもどうぞ

C++でマイナンバーのチェックデジットを計算する - Qiita

解説

処理の解説がないのは少々不親切なので追記

文字数の計算

Batファイルにはそもそも文字列長を計算してくれるような機能はありません
なので少々強引に文字列長の計算をする処理を:calc_strlenラベル以下に記述しています。

:calc_strlen
set str=%1
set strlen=0
:loop_calc
if defined str (
  set str=%str:~1%
  set /a strlen+=1
  goto :loop_calc
)
exit /b

今回は処理を簡略化するために第1引数以外を無視しています。
:calc_strlenが文字列長を計算し、strlenという変数に文字列長を代入します。
%1は第1引数
if defined strはバッチファイルの仕様上、空でなければ定義されているという扱いになるため変数strが空になるまでループさせ、変数strlenをインクリメントする処理になっています。
%str:~1%:これは、strの1文字目から最後までを取得という意味。0文字目開始なので、ループ中で空になるまで1文字ずつシフトしています。

if %strlen% neq 12はそのままstrlenが12じゃなかったらという意味ですね。
今回扱いたい文字列長は12桁の数値なので12文字以外は処理を行わないようにしています。

バッチファイルで使用できる比較演算子については、コマンドプロンプトif /?などと入力していただければ確認できるので省略します。

初期化処理
set mynumber=%1
set check_digit=%mynumber:~-1%
set /a sum=0
set temp=%mynumber:~0,-1%

特に解説すべきところはなさそうですが、2行目についてset check_digit=%mynumber:~-1%の部分は、最後から1文字目から最後まで文字列を取得
また、set temp=%mynumber:~0,-1%の部分は、0文字目から最後から1文字目までの文字列を取得しています。

バッチファイルの文字列は0文字目を起点とするため、変数名:の後に0以上の数値を指定することで指定した位置からの文字を取得することができます。
同様に、変数名:(開始位置),(終了位置)とすることで開始位置から終了位置までの文字列を取得することができます。
また0以上の数値を指定すると開始位置からの文字数、負の値を指定すると終了位置からの文字数を指定することができます。

これらのことを利用して検証用のチェックデジットと計算に使用するための数字を分割しています。

計算・検証

for /l %%I in (1,1,11) do (
  if %%I leq 6 (
    set /a q=%%I+1
  ) else (
    set /a q=%%I-5
  )
  set p=!temp:~-1!
  set /a sum+=!p!*!q!
  set temp=!temp:~0,-1!
)

まず前半部分です。FOR文の使用方法についてもfor /?とすることで確認可能なので省略
今回、!(変数名)!という表記が出てきていますがこちらは変数の遅延展開*1を使用しています。
この辺りから遅延展開というものを使わないとどうやらうまく計算されなくてドハマリしてました。
ただ計算内容自体は参考にしたRuby版と同様で、i≦6の場合はi+1を、i>7の場合はi-5をqとして、検証用に計算しているだけですね。

set /a remainder=!sum!%%11

if !remainder! leq 1 (
  set check=0
) else (
  set /a check=11-!remainder!
)
if !check_digit! equ !check! (
  echo true
) else (
  echo false
)

最後は特筆すべき点はありませんね。
少しわかりにくいところとしてはバッチファイルを書く場合の注意点が1点含まれているところでしょうか?
剰余の計算が他の言語同様に%として使用できるのですが、バッチファイルでは%を表す場合は%%と重ねて書かなければなりません。
なので%%と二重に書いています。

さて解説は以上のとおりとなります。

いかがでしたでしょうか?バッチファイルは本来計算が得意な言語(?)ではありませんが工夫次第で色々なことができそうですね。

追記(2016-03-07)

Awk版を作成しました。

awkでマイナンバーのチェックデジットを検証・集計 - 沖の雑記帳

WinBatch版及びAwk版をそれぞれGitHubにあげてみました。

okiworks (Hironao Oki) · GitHub

追記(2016-04-11)

このページも含めリンクしてくださっている、yumedotoさんのページリンクを追加
様々な言語での実装状況まとめも作成されています。

qiita.com

*1:使用直前で変数が更新される。遅延展開しない場合は最初にセットされた値