TeX Alchemist Online

TeX のこと,フォントのこと,Mac のこと

カウンタ値を全角数字で出力する完全展開可能な命令を定義する

LaTeXのカウンタ値の出力の仕方として,

\newcounter{test}
\setcounter{test}{123}

\arabic{test} % => 123

というようなのはお馴染みです。 そこで,カウンタ値を半角数字ではなく全角数字で出力する,というお題を考えましょう。

\kansuji による変換

こういうときは,(u)pTeX のプリミティブである \kansuji を使うと簡単です。\kansuji は,続く数字の文字列を漢数字の文字列へと展開する命令です。

\kansuji123 % => 一二三

そこで,\kansujichar によって漢数字として対応づけられる和文文字を,漢数字ではなく全角数字へと変更します。すると,\kansuji によってカウンタ値が全角数字として出力されるようになります。

\newcounter{test}
\setcounter{test}{123}

\begingroup
  \kansujichar0=`0
  \kansujichar1=`1
  \kansujichar2=`2
  \kansujichar3=`3
  \kansujichar4=`4
  \kansujichar5=`5
  \kansujichar6=`6
  \kansujichar7=`7
  \kansujichar8=`8
  \kansujichar9=`9
  \kansuji\arabic{test}% => 123(全角)
\endgroup

カウンタ値出力用にコマンド化する

上記のコードをそのままコマンド化して,\zarabic という新たなカウンタ出力命令を作ってみましょう。latex.ltx\arabic の定義にならって,次のように \zarabic を定義します。

\def\zarabic#1{\expandafter\@zarabic\csname c@#1\endcsname}
\def\@zarabic#1{%
\begingroup
  \kansujichar0=`0
  \kansujichar1=`1
  \kansujichar2=`2
  \kansujichar3=`3
  \kansujichar4=`4
  \kansujichar5=`5
  \kansujichar6=`6
  \kansujichar7=`7
  \kansujichar8=`8
  \kansujichar9=`9
  \kansuji\number#1%
\endgroup}

これを使ってみると,問題なく機能しているように見えます。

\newcounter{test}
\setcounter{test}{123}

\zarabic{test} % => 123(全角)

問題点

ところが,このように定義した \zarabic には,元の \arabic と異なり,完全展開によって文字トークンの列にまで展開できないという問題点があります。そのため,\csname...\endcsname のような完全展開性を要求される文脈下で使えません。

\newcounter{test}
\setcounter{test}{123}

\csname command\arabic{test}\endcsname % \command123 へと展開される
\csname command\zarabic{test}\endcsname % \command123 へと展開されて欲しいがエラーとなる

解決策

完全展開可能な \zarabic を定義するため,\def のパターンマッチを用いて再帰的に文字列置換を行うという手法で \zarabic を実装し直してみましょう。

\def\zarabic#1{\expandafter\zarabic@ZERO\the\csname c@#1\endcsname 0\relax}
\def\zarabic@ZERO#10#2\relax{%
  \if"#2"%
    \zarabic@ONE#11\relax
  \else
    \zarabic@ONE#11\relax\zarabic@ZERO#2\relax
  \fi}
\def\zarabic@ONE#11#2\relax{%
  \if"#2"%
    \zarabic@TWO#12\relax
  \else
    \zarabic@TWO#12\relax\zarabic@ONE#2\relax
  \fi}
\def\zarabic@TWO#12#2\relax{%
  \if"#2"%
    \zarabic@THREE#13\relax
  \else
    \zarabic@THREE#13\relax\zarabic@TWO#2\relax
  \fi}
\def\zarabic@THREE#13#2\relax{%
  \if"#2"%
    \zarabic@FOUR#14\relax
  \else
    \zarabic@FOUR#14\relax\zarabic@THREE#2\relax
  \fi}
\def\zarabic@FOUR#14#2\relax{%
  \if"#2"%
    \zarabic@FIVE#15\relax
  \else
    \zarabic@FIVE#15\relax\zarabic@FOUR#2\relax
  \fi}
\def\zarabic@FIVE#15#2\relax{%
  \if"#2"%
    \zarabic@SIX#16\relax
  \else
    \zarabic@SIX#16\relax\zarabic@FIVE#2\relax
  \fi}
\def\zarabic@SIX#16#2\relax{%
  \if"#2"%
    \zarabic@SEVEN#17\relax
  \else
    \zarabic@SEVEN#17\relax\zarabic@SIX#2\relax
  \fi}
\def\zarabic@SEVEN#17#2\relax{%
  \if"#2"%
    \zarabic@EIGHT#18\relax
  \else
    \zarabic@EIGHT#18\relax\zarabic@SEVEN#2\relax
  \fi}
\def\zarabic@EIGHT#18#2\relax{%
  \if"#2"%
    \zarabic@NINE#19\relax
  \else
    \zarabic@NINE#19\relax\zarabic@EIGHT#2\relax
  \fi}
\def\zarabic@NINE#19#2\relax{%
  \if"#2"%
    #1%
  \else
    #1\zarabic@NINE#2\relax
  \fi}

このように \zarabic を完全展開可能な形に定義しておけば,

\csname command\zarabic{test}\endcsname

のような用法が通ることになります。

この\def のパターンマッチを用いて再帰的に文字列置換を行うという手法は,今回の「半角数字→全角数字」に限らず,TeXのトークン列に対して

hoge → あいうえお
fuga → かきくけこ
piyo → さしすせそ
……

といった,完全展開可能なままいくつもの規則に基づく全置換を一括で行いたい場面で活用できることでしょう。

ランダムテストしてみる

今回定義した \zarabic が,「同じ数字を複数回含む場合」など,あらゆる場合について正しく機能しているかどうかをテストするために,ランダムな入力を与えて \kansuji による出力結果と比較しまくるテストを実施してみましょう。

TeXにおける乱数生成の手法はかつて TeX & LaTeX Advent Calendar 2018 の記事にまとめました

doratex.hatenablog.jp

今回は,\kansuji による出力結果と,今回定義した \zarabic による出力結果が一致するかどうかを,0~999の値について全数チェックすると同時に,pgfパッケージが提供する \pgfmathrandom を使って乱数を1000個生成し,それらの入力値についても確かめてみましょう。

カウンタ値を全角数字で出力する完全展開可能な命令 \zarabic を定義する · GitHub

上記レポジトリの test-zarabic.tex をコンパイルすると,次のような出力が得られ,全テストケースに成功している様子が分かります。