はじめに
こんにちは,Eliot です.
ある日インターネットを徘徊してると「ナベアツのコードを自力で書けない人は,そのプログラミング言語を使える人たちの集合には属さない」という言説?を目にしました.すなわち「ナベアツのコードを自力で書ける」ことは「その(プログラミング)言語を扱える」ことの必要条件だ,という訳です.
そこで「そういえば自分ってナベアツを自力で実装できるほどの TeX 力はあるのかな」とふと疑問に思ったので,今回はそれに挑戦してみることにしました.
なお世界のナベアツを TeX で実装する話には既製品がすでにインターネット上でいくつか公開されています.しかも ZR さんの記事 ではここで書くことのほぼ完全上位互換的な話が取り上げられています.
また TeX 言語では飽き足らず BibTeX でナベアツを実装した話 や LISP on TeX でナベアツを実装する話 などのもっと難しい手段でナベアツのコードを書いている人もいます.
「じゃあこんな記事意味ないじゃん!」と思ったそこの貴方……正解です.この記事に実用上の需要はほとんど無いと思います.ですからあくまで「TeX / LaTeX ビギナーの学習記録」的なノリで面白半分に見てくださりますと幸いです.
どこまで LaTeX を許容するか ?
はじめは生の TeX 言語のみで(つまり plain TeX で使えるコードのみで)ナベアツを実装しようとしていたのですが,よくよく考えたら僕がそこまでの TeX 力を持ってる訳がありませんでした.
そこである程度は LaTeX のコードを許容することにしたのですが,流石に既存のパッケージ等は使いたくないですし,ε-TeX 拡張(\numexpr など)も僕は謎のプライドで使いたくなかったため,今回は「『黄色い本1』にマクロの説明が載っているもの」のみに限り LaTeX マクロの使用を許容することにしました.
実 装
僕は次のようにしました(以下に示すコードでは sty ファイル中ないしは \makeatletter 状況下であることを仮定しています):
\newif\if@nabeatsu
\newcount\nabeatsu@remainder
\newcount\number@of@nabeatsu
\def\Nabeatsu#1{%
\number@of@nabeatsu\z@ \@tempcnta\@ne
\count@=#1\relax \advance\count@\@ne
\@whilenum\@tempcnta<\count@\do{%
\@nabeatsufalse
\nabeatsu@remainder\@tempcnta \@tempcntb\@tempcnta
\divide\@tempcntb\thr@@ \multiply\@tempcntb\thr@@
\advance\nabeatsu@remainder-\@tempcntb
\ifnum\nabeatsu@remainder=\z@
\@nabeatsutrue
\advance\number@of@nabeatsu\@ne
\fi
\if@nabeatsu\else
\expandafter\@tfor\expandafter\@tempa
\expandafter :\expandafter =\number\@tempcnta\do{%
\ifnum\@tempa=\thr@@
\@nabeatsutrue
\advance\number@of@nabeatsu\@ne
\@break@tfor
\fi
}%
\fi
\if@nabeatsu
\textit{\number\@tempcnta}, %
\else
\number\@tempcnta, %
\fi
\global\advance\@tempcnta\@ne
}%
\\ \number\number@of@nabeatsu 回アホになりました!%
}
ここで \Nabeatsu は,引数 #1 に整数を入れることで,1 以上 #1 以下の整数のうち
- ナベアツ数であるものは斜体で
- ナベアツ数でないものは通常の書体で
表示され,さらにナベアツ数であったものの個数を出力するものです.
例えば \Nabeatsu{500} と入力すると出力結果は以下のようになります:
コードの軽いお気持ち説明
コードの説明(お気持ち程度ですが)を行います.なお僕自身も細心の注意を払ってはいますが,僕が何かしら間違った事を書いておりましたら遠慮なくご指摘ください.
余りの計算方法
皆さんご存知の通りナベアツでは実装を行う過程で「整数を 3 で割った余り」を計算する必要が出てきます.
しかし,普通のプログラミング言語であれば絶対に存在するであろう剰余を計算する機構が残念ながら TeX には用意されていません😢
そのため自力で頑張って余りを計算することになります.
そこで TeX に予め備わっている整数の四則演算を行うプリミティヴについて説明します.
-
\advance:\advance<レジスタ><数字>のように用いることで現在の<レジスタ>の値を<数字>の数だけ増やします(<数字>の部分を負にした場合は減算となります). -
\multiply:\multiply<レジスタ><数字>のように用いることで現在の<レジスタ>の値を<数字>倍します. -
\divide:\divide<レジスタ><数字>のように用いることで「現在の<レジスタ>の値を<数字>で除した数以下の最大の整数」を<レジスタ>に返します.
これらをうまく用いることで剰余を計算していきます.
割られる数を $M$,割る数を $N$ とすると,$M$ を $N$ で割った商は「$M/N$ を超えない最大の整数」,すなわち $\lfloor M/N\rfloor$ となります.ゆえに $M$ を $N$ で割った余りを $P$ とすると
M=N\cdot \left\lfloor \frac{M}{N}\right\rfloor + P\quad \mbox{すなわち}\quad P=M-N\cdot \left\lfloor \frac{M}{N}\right\rfloor
を得ます.
このうち $\lfloor M/N\rfloor$ は TeX の \divide を使えば計算できるため,これらを組み合わせて
\nabeatsu@remainder\@tempcnta \@tempcntb\@tempcnta
\divide\@tempcntb\thr@@ \multiply\@tempcntb\thr@@
\advance\nabeatsu@remainder-\@tempcntb
のようにすれば,\nabeatsu@remainder が \@tempcnta を 3 で割った余りとなります.
(なお \thr@@ は整数 3 を表す定数です.同様に \z@ は整数 0 を,\@ne は整数 1 を表す定数となります)
雑なコード解説 (前半)
上から順番に説明します.
- 上 3 行はマクロで用いる新しい変数の宣言です.特に説明はいらないかと思います.
一応今後のために変数の大まかな役割を説明しておくと,-
\nabeatsu@remainderが整数を 3 で割った余りを表すカウンタであり, -
\number@of@nabeatsuがナベアツ数の総数を表すカウンタとなります.
-
-
\number@of@nabeatsu\z@,\@tempcnta\@neでカウンタの初期値をそれぞれ 0, 1 に設定しています.これも特に言うことはないですね. -
\@whilenumは LaTeX のマクロで,他のプログラミング言語における while 構文に相当するものです.\@whilenum<condition>\do{ ... }で<condition>が真である間\do以下の処理を繰り返し行います.
つまり今回の場合は,\@tempcntaが\count@( = (#1) + 1 )よりも小さい間\do以下の処理を繰り返すということになります.
以降は\@whilenumの\do以下で実行するコードの説明となります. - フラグ
@nabeatsuの初期値を偽に設定しておきます. - 先ほど説明した通り
\@tempcntaを 3 で割った余りが\nabeatsu@remainderになります.そこで\ifnum\nabeatsu@remainder=\z@により-
\nabeatsu@remainderの値が 0 と等しければ\elseまでの処理を実行し,\elseから\fiまでの処理は無視します.ただし\elseよりも前に\fiが見つかった場合は\fiまでの処理を実行します.今回の場合は\elseが書かれていないため\fiまでに書かれている処理を実行します.
つまりこの場合はフラグ@nabeatsuを真に設定し\number@of@nabeatsuの値をインクリメイトして if 文から脱出します. - ここで
\nabeatsu@remainderの値が 0 と等しくない場合は\elseまでの処理を無視した後,\elseから\fiまでの処理を実行するのですが,今回はそもそも\elseが書かれていないためこの場合には何も行いません.
-
- 以上の操作により,
\@tempcntaを 3 の倍数であるときはフラグ@nabeatsuが真になっており,そうでない場合はフラグ@nabeatsuが偽になっています.そこでこのフラグ@nabeatsuの真偽によってさらに処理を分岐させます.
\if@nabeatsu <true code> \else <false code> \fiという一文により,- フラグ
@nabeatsuが真であれば\else以前にある<true code>を実行して\elseから\fiまでの記述を無視し, -
@nabeatsuが偽であれば\elseまでの記述を無視して\elseから\fiまでの<false code>を実行します.
- フラグ
- 今回の場合は
\if@nabeatsuの直後に\elseが来ているためフラグ@nabeatsuが真である場合には何も行いません.
偽である場合の処理はここで話すと長くなるので別章で説明します.
フラグ @nabeastu が偽である場合の処理
\else の直下に何やら悍ましいコードが書かれていると思われるのですが,これについて軽く触れておきます.
まず \@tfor というのは LaTeX のマクロで,通常は \@tfor\somecs:=<list>\do{ ... }2 と書くことで「\somecs が <list> の各項目へ展開されるように定義した後,\somecs の各項目に対して \do 以下の処理を実行する」というような操作を行います.
しかし今回は単にこのフォーマット通りに書くだけでは済みません.コードをご覧いただけば分かる通り,この \@tfor 構文の中に \expandafter という謎の呪文が大量に挿入されています.
\number は \number<整数> と書くことで <整数> の値を 10 進表記した文字列を与えるコマンドです.そのため上ソースコードでの \number\@tempcnta は \@tempcnta の値を 10 進表記した文字列となります.しかし,単に \@tfor\somecs:=\number\@tempcnta... と書いても \number\@tempcnta が展開されてないので上手くいきません.そのため \@tfor の実行前に \number を展開し,\number\@tempcnta を文字列にしてから \@tfor を実行する必要があります.そこで必要なのが \expandafter です.
\expandafter というのは TeX のプリミティヴ3で,\expandafter<token> と書くことで \expandafter の直後にあるトークンの展開を 1 回遅らせることができます.つまり\expandafter<token1><token2> と書かれていたら <token2> が先に 1 回展開され,その直後に <token1> が展開されます.
するとこの場合では \number を最初に展開したいため,\number 以前の各トークン( \@tfor, \@tempa, :, = )に \expandafter を前置する必要があります.したがって下のように書けば \number が一番最初に展開されて文字列になるため,これで \@tfor の処理を正しく行うことができます.
\expandafter\@tfor\expandafter\@tempa
\expandafter :\expandafter =\number\@tempcnta\do{...
(これは完全なる余談なのですが,人って \expandafter が必要な場面に遭遇するとワクワクするんですよね.僕も最初にこのコードを書いた際 \expandafter が必要なことがわかった時はだいぶ興奮しました.)
以上により,\do 以下では「整数 \@tempcnta を構成する各数字」に対して操作が行われることになります.
ここで \ifnum\@tema=\thr@@ により \@tempa の各数字(すなわち整数 \@tempcnta を構成する各数字)に対して 3 と等しいか否かが検査され,等しければ
\@nabeatsutrue
\advance\number@of@nabeatsu\@ne
\@break@tfor
が実行されます(等しくならない場合は何も行いません).これらの各コードは上から順に次の操作を行います:
-
\@nabeatsutrue: フラグ@nabeatsuを真にします. -
\advance\number@of@nabeatsu\@ne:\number@of@nabeatsuの値をインクリメイトします. -
\@break@tfor:\@tforのループを打ち切って抜け出します.
すなわちこの場合は,\@tempcnta を構成する数字のうちに 1 つでも 3 が含まれていれば上記の操作が行われ,逆に数字 3 が 1 つも含まれていなかった場合は何も起こらないということになります.
雑なコード解説 (後半)
山場は抜け出したので後はそこまで難しくありません.
上記の操作によって,
-
\@tempcntaが 3 の倍数である -
\@tempcntaを構成する数字のうちに 1 つでも 3 がある
のうちいずれか 1 つでも満たされる場合はフラグ @nabeatsu が真になり,逆にどちらも満たされない場合は偽になります.これを踏まえた上で以下のコードにより
- フラグ
@nabeatsuが真である場合は\@tempcntaの値を斜体で - 偽である場合は通常の書体で
出力し,最後に \@tempcnta の値をインクリメイトします.
\if@nabeatsu
\textit{\number\@tempcnta}, %
\else
\number\@tempcnta, %
\fi
\global\advance\@tempcnta\@ne
そしてまた最初に戻り,\@whilenum によって \@tempcnta の値が #1 以下であれば再び \do 以下の操作が行われます.これにより 1, 2, ..., #1 の各整数に対して上記の操作が行われることになり,正しい出力を得ることができます.
そして \@whilenum によるループが終了した後,コード最終行の記述によってナベアツ数の総数が出力され,これにて \Nabeatsu の定義は終了です.
余 談
大きな数字で何回か試してみたのですが,いかにも正しい出力を得られている感じがしました.個数もネットで調べたものと(自分が試したものは)すべて一致していたため流石に正解であると信じたいです.
しかしこのコードを書き終えた後 TeX におけるナベアツ実装をインターネットで片っ端から調べてみたのですが,意外にも僕と同じような方針を取っている人は見つかりませんでした(僕が見落としてるだけなのかもしれませんが).
僕が中途半端に LaTeX に甘えているせいなのかもしれませんし,僕の方針がとても筋の悪いものだからなのかもしれませんし,実は僕の書いたコードそのものに欠陥があって間違っているからなのかもしれません.
これで本当に欠陥があったら僕は間違っているコードを堂々と解説していたことになるのですから,もしそうだったら心の底から恥ずかしくて仕方がありません4.こわい….
おまけ : expl3 を使うと ?
せっかくなので expl3 でもナベアツコードを実装してみました(なおこちらも自分でコードを書き終えてからインターネットで検索してみたところ,ZR さんの記事 で完全上位互換な話が取り上げられていました).
僕は次のように実装しました(以下のコードでは \ExplSyntaxOn 状況下であることを仮定しています):
\int_new:N \l__number_of_nabeatsu_int
\NewDocumentCommand\ExNabeatsu{ m }{
\int_zero:N \l_tmpa_int
\int_zero:N \l__number_of_nabeatsu_int
\int_do_until:nNnn { \l_tmpa_int } = { #1 } {
\bool_set_false:N \l_tmpa_bool
\int_incr:N \l_tmpa_int
\int_compare:nNnT { \int_mod:nn { \l_tmpa_int } { 3 } } = { 0 } {
\bool_set_true:N \l_tmpa_bool
\int_incr:N \l__number_of_nabeatsu_int
}
\bool_if:NF \l_tmpa_bool {
\str_set:Ne \l_tmpa_str { \int_use:N \l_tmpa_int }
\str_if_in:NnT \l_tmpa_str { 3 } {
\bool_set_true:N \l_tmpa_bool
\int_incr:N \l__number_of_nabeatsu_int
}
}
\bool_if:NTF \l_tmpa_bool {
\textit{ \int_use:N \l_tmpa_int },~
} { \int_use:N \l_tmpa_int ,~ }
}
\\ \int_use:N \l__number_of_nabeatsu_int 回アホになりました!
}
やってる事とマクロの挙動自体は TeX on LaTeX で実装した \Nabeatsu とほぼ変わらないのですが,expl3 によって一部の操作がかなり簡略化できています.とくに \expandafter をたくさん挿入し展開の順序を変えてまで \@tfor でループを回していた部分が LaTeX kernel 謹製の関数 \str_if_in:NnT 一発で済むようになっているのが嬉しいですね.
こうして見るとどこぞの TeX 言語とは違い expl3 は可読性が高くて素晴らしいですよね.あのパット見では何をやっているのか全くわからない TeX on LaTeX のコードとは違って,これなら関数名などから何をしているのかなんとなく推測できるのではないでしょうか.
-
『LaTeX 2ε マクロ&クラス プログラミング基礎解説』のことを指しています. ↩
-
ここでの
\somecsの部分は\@tempaとすることが多いようです(実際,今回紹介したコードでもそのようにしています). ↩ -
この
\expandafterはその語呂の良さと見た目の仰々しさゆえなのか,数ある TeX プリミティヴの中でも随一の知名度と人気を博しています.
中には\expandafterを お酒のネタにしたり,絵を書く際のモチーフにしている人もいるみたいです.(ZR さんの記事『徹底解説!\expandafter活用術(キホン編)』より引用) ↩ -
かといってこういうものは完璧に正しいことを自分で確認するのも困難ですし,僕がどこかで勘違いを起こしている可能性は十分あります.どなたか TeX に詳しい方は,僕の書いたコードが正しいかどうか検証してくださると本当に嬉しいです. ↩
