2012/12/30

2013年賀状

年賀状書いた。

%!
<< /PageSize [285 420] >> setpagedevice

% excerpt from Bill Casselman's 
% "Mathematical Illustrations - a manual of geometry and PostScript"
/ctransform { load
  1 dict begin
  /f exch def
  [{[3 1 roll f {moveto}]}
   {[3 1 roll f {lineto}]}
   {[7 1 roll
     f 6 2 roll
     f 6 2 roll
     f 6 2 roll
     {curveto}]}
   {[{closepath}]}
   pathforall
  ]
  newpath
  {aload pop exec} forall
  end
  } def

/f { % u = (x^2-y^2) / 200, v = 2xy / 100
  /y exch def
  /x exch def
  x dup mul y dup mul sub 150 div
  1.2 x mul y mul 100 div
} def

/fcn { % u = ((x-100)^2-y^2) / 7 , v = 1.2xy / 7
  /y exch def
  /x exch def
  x 100 sub dup mul y dup mul sub 28 div
  1.2 x mul y mul 7 div
} def

/setrandcolor {
  /m exch .4 add def
  /r1 {rand 1 mod 1 div m add} def
  /r2 {rand 3 mod 17 div m add} def
  /r3 {rand 10 mod 17 div m add} def
  r1 r2 r3 setrgbcolor
} def

/randcgrid {
  currentlinewidth 10 div setlinewidth
    0 .5 300 {
      /i exch def
      i 0 exch moveto 300 0 rlineto /f ctransform
      i 400 div setrandcolor stroke} for
    0 .5 300 {
      /i exch def
      i 0 moveto 0 300 rlineto /f ctransform
      i 300 div setrandcolor stroke} for
} def

/xoffset 200 def

0 1 20{
  /n exch def

  xoffset 10 add -10 translate randcgrid
  0 xoffset sub 20 translate
  0 0 moveto
  newpath
  /Titania findfont 11 scalefont setfont
  0.7 0.1 0.1 setrgbcolor
  12 5.5 moveto
  (2008 2009 2010 2011 2012 2013 2014) true charpath
  /fcn ctransform clip
  /fcn ctransform clip
  1 0.5 0.55 setrgbcolor
  fill
  stroke
  newpath
  1 0 0 setrgbcolor
  120 setlinewidth
  145 140 moveto
  145 400 lineto
  stroke

  initclip
  20 10 moveto
  /Georgia-Bold findfont 10 scalefont setfont
  0 0 0 setrgbcolor
  n =string cvs show
  (/20) show
  showpage
  0 0 moveto
} for

"Mathematical Illustrations"を読みつつパスを変換(実質的には等角写像でぐにゅっと曲げた)して生成した。/fcn が西暦の文字列を構成するパスを変換するための写像を表す関数で、/f が矩形の格子を背景のテクスチャへと変換する写像を表す関数。

SDIM0193

利用したフォントは Titania 。毎年、フォント探しに一番時間がかかってるような気がする。

毎年、親戚や家族付き合いのある友人向けに20枚くらいだけ印刷してて、今年のものは「2008年に生まれた息子も5歳を迎えます今年も来年も引き続きよろしくお願いします」と読みます。去年のものはだいぶ色きちがいになってしまったので、今年はあっさりとした配色にして、何か手書きするスペースを残しました。

2012/12/24

TeX で花火(手抜きバージョン)

ちょうど一年前、「LaTeX/TikZ で花火」というネタが流行ったのは記憶に新しいところです(参考1参考2)。 TikZ は高機能なグラフィック機能を提供する LaTeX のパッケージで、繰り返し構文なども提供されており、アルゴリズム的に花火っぽい絵を描画するコードがいくつか登場しました。

自分でも TeX & LaTeX Advent Calendar 2012 のために花火をひとつくらい打ち上げてみようかなと思ってたのですが、TikZ と格闘するような時間もなく、あきらめてかけていたところ、この花火ネタより前の2009年に話題を集めた LaTeX お絵かきネタを思い出しました。コーヒーポットのシミを論文などに刷り込むための LaTeX スタイル "LaTeX Coffee Stains" です。これの花火バージョンがあれば、なんとかゴール目前までつながった TeX & LaTeX Advent Calendar 2012 のお祝いネタとしてちょうどいいのではないでしょうか。

というわけで急遽でっちあげたのが、見た目にもめでたい「花火をページに刷り込むコマンド」です。花火を打ち上げたいページで \fireworks と書くだけ!

正直に打ち明けると、このネタは上記の coffee.sty の丸ぱくりです。 しかも自分では TeX のコードをまったく書いてません。元ネタである coffee.sty を見たら、PSTricks という PostScript の描画命令を直接埋め込める機能を使ってコーヒーのシミを描いていたので、この PostScript のシミデータを自作の PostScript 花火データにまるっと差し替えました。 PostScript の花火をどう作ったかは聞かないでください。 PSTricks では PostScript の描画コマンドは直接利用できるけど PostScript のプログラムを書けるわけではないので、アルゴリズム的には生成してないです。なお、 PSTricks は dvipdfmx では使えないので、PDFを作る場合は pdftex か dvips 経由で。

というわけで、明日の TeX & LaTeX Advent Calendar 2012 最終日はTeX芸人の中のTeX芸人、ZRさんの予定です。

2012/12/18

parsec で極める文章編集

正規表現をまったく使えない編集者はひとにぎりだと思いますが、正規表現だと原稿の半角丸括弧を全角に変換する作業とか頭痛いですよね。わたしもいつも困ってました。

というわけで、いまや編集者必須ツールといってもいい parsec を新人編集者にぜひ使ってもらおうということで、 「Haskell Advent Calendar 2012」18日目という場を借りた素人チュートリアル記事です。 Haskeller が書いてるわけではないので、 「その考え方は違う」とか「もっと効率的な書き方がある」といったコメントがもらえるとうれしいです。 ちなみに、わたしの周りに新人編集者はもう何年もいません。まだ見ぬ新人へ向けて書きます。

parsec で最速テキストフィルター

最初に parsec を使おうと思ったときにぶちあたるのは、プログラマ向けの解説しかないことだと思います。 編集者というものは、 CSV や IP アドレスをパースしたり、ましてや関数電卓を作ったりしない。 Ruby で正規表現を使ったテキストフィルターを書くときみたいに parsec を使うにはどうすればいいでしょうか。

外側から考えます。いま作りたいのは、入力ファイルを指定して、何か文字列変換を施した結果を出力するテキストフィルターです。Ruby なら ARGF.gets とかすればいいとこですが、ここは我慢してとりあえずこんな枠組みを書きます。

module Main () where

import System.Environment
import qualified System.IO as IO
import Text.ParserCombinators.Parsec hiding (many, (<|>))
import Control.Applicative

main = do
  args <- getArgs
  inh <- IO.openFile (args !! 0) IO.ReadMode
  body <- IO.hGetContents inh
  IO.putStr $ doSomething body
  IO.hClose inh

doSomething が具体的な変換処理で、ここに「日本語文章に出てくる半角丸括弧を全角に」とか「TeX の数式を抜き出す」とか「コード行に出てくるキーワードにハイライトのタグをつける」とかいった処理をするパーザを parsec で書くわけです。

では doSomething を考えましょう。ここでは doSomething という名前を説明のために使い続けますが、実際にスクリプトを書くときはテキスト変換処理を表す適切な名前をつけてください。

doSomething :: String -> String
doSomething lines = case parse (concat <$> manyTill block eof) "" lines of
  Left  err -> ""
  Right str -> str

テキストフィルターなので doSomething は文字列から文字列への変換を担う関数でないと困ります。 そこで 1行目には String -> String と書いてあります。

case ... of の内側の manyTill block eof が、とっかかりとなる最初のパーサです。 これは、「今はなんだか決めてないけど block という文字列の塊を取ってくるパーザがあるとして、それをファイルの終わり eof まで繰り返し実行する(manyTill)」、という意味です。 繰り返しとってきたその結果は、文字列のリストなので、一つにつなげるために concat <$> と書いています。 このように関数のうしろに <$> と書き、続けてパーザを書くと、「うしろに書いたパーザが返すものに最初の関数を適用したものを返すパーザ」になります。

ここで注意しないといけないのは、パーザは「文字列をパースして得られる文字列」を返してくれるわけではないという点です。 parsec では、パーズして得られる結果は「パーザを実行する専用世界」の中にあり続けます。 その専用世界を Parser と呼ぶことにしていて、 だから parsec におけるパーザは、たとえば文字列を返すものであれば Parser String といいます。文字を返すものなら Parser Char です。何も返さないパーザというのもあって(スペースを読み飛ばす、とかです)、これは Parser () といいます。

この Parser 世界の中にフィルタリングしたい文字列を入れて、パーズした結果をもらいたいわけですが、この世界とのやり取りは決められた出入り口からしかできないようになっています。 someParser というパーザを書いたとして、それで文字列をパーズして結果を「専用世界」の外に引っ張り出してくる方法のひとつが、上記の parse someParser "" lines という書き方です。

こうして手に入る結果は「パースに成功してこんな文字列が手に入った、または失敗した」というちょっと変わった形をしています。そのままでは doSomething の結果としてふさわしくありません(だって doSomething は文字列を返すってことにしたので)。そこで case ... ofRight および Left という識別子を使って、成功の場合も失敗の場合も文字列を返すようにしています。作ってるのがテキストフィルターなので、失敗の場合は空文字列を返しとけばいいでしょう。

ここでようやく block を何にするか決めます。 テキストフィルターの仕様を考えるわけです(というわけで、ここまではテキストフィルターを書くときの定型だと思ってもいいです)。

いま、HTML の <p> タグの内側にある半角丸括弧だけをすべて全角丸括弧に直したいとします。 一方、 <pre> タグの中にある半角丸括弧とかは、コードの断片である可能性が高いので、変換してはいけないとします。数式なんかに出てくる半角丸括弧も全角にしてはいけません。 いま仮に、 <p> タグの中には日本語の本文だけしかないものとしましょう(でないと説明のコードが増えてしまうからです。べつに一定のルールにしたがって出てくるぶんには、その部分だけ処理を飛ばすようにパーザを書けばいいのです。ただし完璧を目指すと泥沼になるので適当な精度で切り上げましょう)。

この場合の block の仕様はこうです。こいつは文字列を返すパーザにしたいので、 Parser String だと宣言しておきます。

block :: Parser String
block = choice [ try japara
               , otherlines ]

japara :: Parser String
japara = string "<p>" *> (conc <$> manyTill anyChar (try $ string "</p>"))
  where conc = ("<p>"++) . (++"</p>") . replaceParen

otherlines :: Parser String
otherlines = manyTill anyChar $ (try $ string "\n")

ざっくりというと、日本語の本文(japara パーサ)かそれ以外(otherlines パーサ)かで選択(choice 関数)をして、日本語の本文だったら半角括弧を全角に変換します(replaceParen 関数)。 block だけでなく、そこから呼んでる japara だったり otherlines だったりは、すべて文字列を返すパーザです。こんなふうに、基本的なパーザをいろいろ組み合わせて好きなパーザを作るわけです。 string [文字列] とか anyChar なんかも、もちろんパーザで、これらのいわば最小の部品は parsec にあらかじめ用意されています。ほかの部品はここにドキュメントがあるので探してください。

japara、つまり日本語の本文は、「文字列 <p> から </p> までの内側」です。 *> は、右側だけを結果に残すようなパーザを作ってくれます。

otherlines、つまり日本語の本文以外は、「改行までの文字なら何でも」とってくるパーザです。 この定義だと 1行とったら終わってしまうように思いますが、外枠のほうで block を何度も繰り返しとり出すことにしてあるので、これで問題ありません。

<p> タグ内で半角括弧の置換を行う replaceParen を書くには、 最初のほうで定義した doSomething と同じ考え方をします。 doSomething では本文全体から必要なブロックと不要なブロックを切り出すパーザを繰り返し使ったわけですが、今度は置換する要素としない要素に切り刻むパーザを作り、それで各ブロックを処理していきます。 丸括弧は入れ子になってるかもしれないので、丸括弧か否か(parensnoParens)だけでなく、丸括弧内か(inParens)も選択肢になりえます。

replaceParen :: String -> String
replaceParen line = case parse (concat <$> many1 strOrParen) "" line of
  Left err -> ""
  Right str -> str

strOrParen :: Parser String
strOrParen = choice [ try noParens, try inParen, parens ]

inParen :: Parser String
inParen = string "(" *> (wrapDP <$> (manyTill strOrParen (string ")")))
  where wrapDP = ("("++) . (++")") . concat

noParens :: Parser String
noParens = many1 $ noneOf "()"

parens :: Parser String
parens = many1 $ oneOf "()"

以上を ReplaceParen.hs のような名前で保存して以下のように実行すれば <p> タグの中だけ半角丸括弧を全角に置換できます。このブログ記事のソースみたいなのを処理しても、コード片に出てくる半角丸括弧は置換されません。やったね。

$ runghc ReplaceParen.hs input.html > result.html

parsec でテキストフィルターを書くときのまとめ

  1. 全体をブロック要素へと切り刻むパーザを choice で作り
  2. 各ブロックをインライン要素へと切り刻むパーザを choice で作る
  3. どちらも 「case parse [パーザ] "" [パーズする対象] of 」で文字列から文字列への関数にしたてる

書き捨てとはいえ、このような再帰的なパターンになると、正規表現をサポートしてるエディタではつらいし、sed/Ruby/Python/Gauche などでスクリプトを書くにしてもかえってコード力が要求されることが多いように思います。 単純よりちょっと込み入ったテキスト処理になると、Haskell のほうが parsec のおかげで楽に編集補助ツールが書けることもあるはずです。プログラマでないみなさんも『すごい Haskell たのしく学ぼう!』『プログラミング Haskell』だけは読んでおきましょう。再帰に対する理解もあるとなおよいので、『Scheme 手習い』もぜひ読みましょう。これは宣伝です。

Haskell のコードには、ここで出てきたような <$> とか *> のような記号がちょこちょこ出てくるのでとっつきにくいかも知れませんが、『すごい Haskell たのしく学ぼう!』の Kindle 版とか、ちゃんと索引もついてるので、こんな検索しにくそうな記号もばっちり調べられます。これは宣伝であると同時に、電子書籍にも索引あったほうがいいよという、この記事の対象者である新人編集者むけのアドバイスです。

注意

書き捨てのテキストフィルターに完璧は目指さないこと。あくまでも編集作業の補助に使いましょう。 実は上の例でも、たとえば原稿中に <p> 要素の入れ子がないことや、<p> タグがすべて行頭にあることを密かに仮定していて、だから otherlines が簡単に定義できてます。

経験からいうと、 otherlines のような「探してないその他大勢」をすっ飛ばすパーザを書くほうが大変で、行頭とか空白とか特殊文字といった都合のいい条件のない完璧なパーザを目指そうとすると、とたんにスクリプトが巨大になります。この例だと、それこそ XML パーザを書く勢いが必要です。しかし、いまほしいのは書き捨てのテキストフィルターです。妥協が肝心です。ビルドスクリプトに組み込むとかでなければ、一発で完全に処理しようとしないほうが幸せです。

2012/12/15

TeX がむかついたので実装したけど挫折してる話

いきなり私見ですが、「TeX むかつく」という声の大半は、「インストールが苦痛」または「マクロが意味不明で思い通りならない」に分類できるんじゃないでしょうか。 でもまあ、便利なインストーラ付属の分かりやすいLaTeX2eの入門書が改訂を重ねて書店で売ってたりするのだし、これらは普通の用途においては解決策がある問題ということにしておきましょう。

インストールの壁をクリアして、ちょっと凝ったページの出力もできるようになると、 基本的な組版機能に不足はないし、数式にいたってはほぼ唯一無二だし、自作マクロでちょっとした自動化さえできるわけで、 「とにかくTeXむかつく」という心境から、「いっけん意味不明な制約やなぞの挙動には目をつぶるとして TeX しかないな」という心境に至る人も多いはずです。 わたし自身も、組版に関しては、だいたいそういう心境です。残念ながら TeX しかない。

しかし、それでも TeX には実際むかつくことがある! それは、「TeX がドキュメントシステムに向いてない」ことです。「自由すぎる」ともいえます。

ドキュメントというのは、木構造になっているべきです。 キーワードやフォント指定用語などのインライン要素から構成される章、節、段落、図、数式といったブロック要素が、根っこで一つのドキュメントへと束ねられてるようなイメージです。 散文一般とドキュメントに区別があるとしたら、この木構造が必須か否かという点かもしれません。 ドキュメントは、なんらかの木構造と一対一対応がないと、最悪、読み手によって意味が変わってしまいます。 小説ならそれでいいんですが、たとえばジェット機の整備マニュアルなんかだと、それではまずい。 だから、まっとうなドキュメントには木構造が求められるわけです。XMLとかSGMLが重宝されるゆえんです。

だからドキュメントをシステマチックに扱うには、木構造を反映できるファイル形式が不可欠です。 さらに、ドキュメントを木として編集するための仕組みもあればうれしいでしょう。

TeX がドキュメントシステムに向いてないと思うのは、この木構造の観点が欠落しているからです。 もちろん、書き手が注意して執筆することで、 TeX を使って木構造のドキュメントを作ることはできます。 しかし、 TeX で執筆された原稿を扱う汎用の仕組みを作ろうとすると、構文解析すら無理ゲーなことに気づきます。 ドキュメントシステムの処理系を作ろうとしたら、原稿から木を作ってそれを内部表現として扱うと思うのですが、 TeX の場合、原稿を書くための基本的な構文も内臓のマクロ言語によって提供されているため、原稿から木を作りたいだけなのに、原理的にはこのマクロ言語の処理系を書かないと完全なものにならない。そこで実践では「サポートする TeX の構文」を決めうちすることで TeX マクロ言語処理系を実装する手間は省くことになってしまい、だから世の中には「汎用の LaTeX→XML コンバーター」のような仕組みが見当たらないのです。

ないから作った

ないなら作ればいいというわけで、TeXマクロ言語の処理系を Gauche で実装してみたことがありました。TeX-modoki といいます(その後「tef」に改名)。基本的なマクロの展開が扱えます。久しぶりにソース見たら \afterassignment\aftergroup も未実装だったけど、奇数個の \expandafter の塊くらいはまじめに処理できます。

tex-modoki$ cat sample/expandafter-test.tex
\def\A#1#2#3{#1#2#3a}\def\B#1#2{#1#2b}\def\C#1{#1c}\def\D{d}

\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\A
\expandafter\expandafter\expandafter\B\expandafter\C\D

tex-modoki$ gosh -I. main.scm -o text sample/expandafter-test.tex
 dcba

トークンのカテゴリコードもまじめに扱ってるので、それ相応の判定結果が得られます。

tex-modoki$ cat sample/ifcat-test.tex
\catcode`[=13 \catcode`]=13
\def[{*}

\ifcat[* yes \else no \fi % yes

\ifcat\noexpand[\noexpand] yes \else no \fi % yes

\ifcat\noexpand[* yes \else no \fi % no

tex-modoki$ gosh -I. main.scm sample/ifcat-test.tex
 yes   yes  no

未実装の機能が多いので制限だらけですが、簡単な整数計算のマクロなら実行できるっぽいです。「スヤァTeX」に登場する、修正ユリウス通日を求めるマクロも、少し修正は必要ですが実行できました(できるように今朝ちょっと実装を追加した)。「\relax」の変わりに「( ˘ω˘ )スヤァ…」を使うには(ry

tex-modoki$ gosh -I. main.scm -o text sample/julian.tex
   56286

tex-modoki$ cat sample/julian.tex
\input sample/plain % tex-modoki 用に修正した plain TeX マクロを指定

\newcount\xx@cnta
\newcount\xx@cntb
\newcount\xx@year   % 4桁の整数の格納に mathchar を使わない!
\newcount\xx@month  % 2桁の整数の格納に char を使わない!

\def\julian#1{%     % LaTeXじゃないので newcommand を def に
  \edef\xx@tmpa{#1}%
  \expandafter\xx@julian@a\xx@tmpa///\relax
}
\def\xx@julian@a#1/#2/#3/#4\relax{%
  \xx@cnta=#1\relax
  \xx@cntb=#2\relax
  \ifnum\xx@cnta<1 \xx@cnta=\@ne \fi  % 条件式の \@ne を 1 に
  \ifnum\xx@cntb<1 \xx@cntb=\@ne \fi  % 条件式の \@ne を 1 に
  \ifnum\xx@cntb<3\relax
    \advance\xx@cntb by 12\relax
    \advance\xx@cnta by -1\relax
  \fi
  \xx@year=\xx@cnta
  \xx@month=\xx@cntb
  \multiply\xx@cnta by 1461\relax
  \divide\xx@cnta by 4\relax
  \xx@cntb=\xx@year
  \divide\xx@cntb by 100\relax  
  \advance\xx@cnta by -\xx@cntb
  \divide\xx@cntb by 4\relax
  \advance\xx@cnta by \xx@cntb
  \xx@cntb=\xx@month
  \advance\xx@cntb by -2\relax
  \multiply\xx@cntb by 520\relax
  \divide\xx@cntb by 17\relax
  \advance\xx@cnta by \xx@cntb
  \advance\xx@cnta by #3\relax
  \advance\xx@cnta by -678912
  \edef\thejulian{\number\xx@cnta}%
}

\julian{2012/12/25}
\thejulian

というわけでこの記事も TeX & LaTeX Advent Calendar 2012 の15日目に向けて書きました。12日ぶり2回目。

TeX の数式を HTML で出力

さらに副産物として、「素TeXで書いた数式の HTML 出力」ができます! もともと TeX の組版機能を実装するつもりはなく、その力もないのですが、TeX の数式を木構造へと変換できるところまでいったので HTML+CSS でも出力できるようにしてみました。

出力例はこんな感じで、残念なとこもあるけど、まあ見られるかなというレベル。

tex-modoki-1

ソースは、こちらのような、単純な TeX ファイルです。 しかし、 LaTeX はもとより、ごく基本的な TeX の標準マクロさえないので、手元にある LaTeX の数式を変換しても内部表現を吐き出すだけだと思います。

結局挫折

けっこうがんばってここまで作ってたのですが、今では開発をほぼ中止しています。なぜなら、TeX では字句解析時の文脈もマクロからいじれてしまう(ヘンタイ!)という事実に後から気が付いたので、完全なものにするにはゼロから作り直す必要があるからです。TeX むかつく!

数式変換機能の線でもうちょっとがんばって開発を続けようかなと思ったこともありましたが、 LaTeX や ams の膨大な遺産を前に挫折! やっぱり TeX むかつく!

2012/12/03

TeXを電卓として使おう(あるいは、TeXでベキ乗根)

プログラミング言語 TeX にはインタプリタがついてきます(ツッコミ禁止)。そこで他の多くの言語のインタプリタのように、TeX インタプリタを「電卓」として使ってみましょう。(なお、この記事は TeX & LaTeX Advent Calendar のために書いたものです。明日は @neruko3114 さん!)

まずは TeX インタプリタの起動です。日本語の組版をするなら ptex を実行するところですが、電卓に日本語はいらないので、今回は世界標準らしい pdftex を使います。 TeX インタプリタの標準プロンプトは * (アスタリスク)ですが、起動時だけは興奮気味に ** と2つ重ねて出力されるので、お約束として一言目は毎回 \relax と入力して TeX インタプリタを落ち着けてください。

$ pdftex
This is pdfTeXk, Version 3.141592-1.40.3 (Web2C 7.5.6)
 %&-line parsing enabled.
** \relax
*

さっそく 1+2 と入力してリターンを押したいところですが、TeX で単に数字や演算子を書いてしまうと、通常の文脈ではドキュメントへと印字する文字列とみなされます。いかにもドキュメント処理システムっぽいですね。数字を整数値として扱うには、整数用の \count レジスタというハコを用意してそこに値を格納し、そのレジスタ上で演算を行います。整数の加算からやってみましょう。

* \newcount\a
* \a=1
* \advance\a by 2
* \showthe\a
> 3.
<*> \showthe\a

?      % ここで単にリターン

*

1行めの \newcount\a で新しい \count レジスタ \a を用意し、2行めで\a に整数値の1を代入、 \advance\a by 2 という命令で \a 「2」の値に2を加算し、 \showthe というコマンドで \a の中身を出力しています。結果は赤字の「3」です。 \showthe で結果を表示したあとはプロンプトが ? に変わりますが、気にせずリターンを押せば元の * に戻ります。簡単ですね。なお \advance コマンドの引く数にマイナス記号をつければ(\advance\a by -2)、減算ができます。減算専用のプリミティブは用意されていません。

続いて、整数の乗算と除算です。それぞれ \multiply\divide という命令が用意されています。先程のセッションに続けて以下のように入力してみましょう(したがって、いま \a には 3 が入ってます)。

* \multiply\a by 3
* \showthe\a
> 9.
<*> \showthe\a

?      % ここで単にリターン

* \divide\a by 2
* \showthe\a
> 4.

最後の行は「9÷2」の結果なので「4.5」になるはずですが、 \count は整数用のハコなので端数は切り捨てられます。キャストとかされません。というか、そもそも実数そのものを扱うハコがありません。

では、結果が実数になるような複雑な計算は実現できないのでしょうか。試しにベキ乗根のつもりで \sqrt\a とかしてみましょう。

* \sqrt\a
! Missing $ inserted.

$がない、つまり数式組版モードじゃないと言われました! 当たり前と言えば当たり前なのですが、TeX というのは本来はドキュメントシステムなので、 sqrt はもちろん、 + や * 、 さらには数字にいたるまで、ふつうの言語なら数値やその演算を提供するであろう要素はほとんど組版の機能に利用されてしまっています。

しかし TeX は、「かっこいい出力ができる汎用プログラミング言語」でもあるはずです。ベキ乗根の近似値くらい計算できないはずがない。そこで、ベキ乗根の近似値をかっこよく組版で使うための TeX マクロ \sqrtd を作ってみましょう。目標は、こんな入力から、

You can get \sqrtd{2}, \sqrtd{2.5}, \sqrtd{3} and \sqrtd{3.14} 
by Newton's method with initial guess $1.0$.

こんな出力を得ることです。

newtonmethod-output

(2のベキ乗根があまり精度よくないのは目をつぶってください><)

TeXでニュートン法を実装しよう!

ベキ乗根の近似値を得る方法として一番簡単なのはニュートン法でしょう。まずはニュートン法を使って値 x のベキ乗根を求める手順のおさらいです。

  1. ベキ乗根になりそうな値 y を適当に推測します。
  2. その推測値 y のベキ乗 y2 と、元の値 x との差を調べます。
    • この差が十分に小さかったら、その推測値 y はだいたいベキ乗根に近いということで、答は y です。
    • この差が十分に小さくなかったら、推測値 y と x/y の真ん中の値をあらためて推測値 y とし、1.に戻ります。

いきなり TeX はきついので、とりあえず Scheme で書いてみます(詳しくはSICPの1.1.7節を参照)。

(define (sqrt-iter guess x)
  (if (good-enough? guess x)
      guess
      (sqrt-iter (improve guess x)
                 x)))

(define (good-enough? guess x)
  (< (abs (- (square guess) x)) 0.001))

(define (improve guess x)
  (/ (+ guess (/ x guess))) 2))

このアルゴリズムを TeX で実装したいわけですが、 TeX にはそのものずばりな実数値がありません。ただ、TeX本来の機能である自動組版を制御するために、値が実数になる「寸法(dimension)」というデータ型は用意されています。このデータ型上にも、整数値のときと同じ演算命令(\advance\multiply\divide)が制限付きながら用意されているので、そこに上記のアルゴリズムの x や y を格納すれば原理的にはベキ乗根が求められるだろう、という算段です。

ここで厄介なのが、寸法上の演算の制限です。寸法の値を自由に掛け算したり割り算したりすることはできません。寸法に対して可能な乗算と除算は、具体的には以下のとおり。

  1. 寸法は、整数倍したり、整数で割ったりすることはできる。
  2. 寸法の前に、何か数字っぽいものを書くと、その数字の値が寸法の値に掛けられる。

上記の 1. は、寸法の値に実数を掛けたり割ったりすることは基本的にできない、つまり、実数どうしの乗算と除算が寸法型のままではできないということです。

上記の 2. の手段を使えば、寸法に実数を掛けることはできます。ここでちょっと実際の TeX コードを見てみましょう。寸法データ型を用意して「3.14 ポイント」を格納し、その寸法を 1.5倍するには、例えばこう書きます。

\newdimen\x      % 新しい寸法データ\xを用意
\x=3.14 pt       % 寸法\xに単位付きの実数を格納
\x=1.5\x         % 寸法\xを1.5倍

寸法の単位はいくつかありますが、ここではポイント(pt)を使っています。2行めでポイントの単位「pt」を省略して 3.14 という実数値を寸法に代入しようとしてはいけません(TeX に苦情を言われます)。3行めの結果、 \x には「4.71 ポイント」に相当する値が入ります。

ここで、実数に実数を掛けようとして次のように書いたらどうなるか考えてみましょう。

\newdimen\x      % 新しい寸法データ\xを用意
\newdimen\y      % 新しい寸法データ\yを用意
\x=3.14 pt       % 寸法\xに単位付きの実数を格納
\y=1.5 pt        % 寸法\yに単位付きの実数を格納
\x=\number\y\x   % 寸法\xを1.5倍?

最後の行に出てくる \number は、直後のデータ型の数字表現を返すコマンドだと考えてください。上記を TeX に渡すと「Dimension too large.」というエラーになり、 \x16383.99998pt という寸法になるはずです。これは、 TeX の寸法の上限(をポイント単位で表したもの)です。

何が起こったのでしょう。\number\y は 1.5 ではなく、「ポイントで表すには "too large" だ」と TeX に言われてしまう何か巨大な寸法であり、TeX はとりあえずの処置としてポイント表示で最大な数字を \x に入れたのです。ちなみにその巨大な寸法とは、 1.5 ポイントを sp という単位で表した数字に 3.14 ポイントを掛けた寸法であり、308674.56pt です。実をいうと、TeX は内部では sp という単位の整数倍で寸法を扱っています。だから、寸法で実数が扱えるというのも実はまやかしで、ポイントくらい大雑把な単位で見れば小数表示になる寸法を実数とみなして扱える、ということだったりします。

というわけで、寸法そのものは単位付きの値なので、その単位で表した場合の実数値を示す数字として使うには、何とかして単位の部分をひっぺがす必要があります。そのための仕組みがこちらの \strippt です。

\newdimen\zero \zero=0pt
\begingroup
  \catcode`P=12
  \catcode`T=12
  \lowercase{
    \def\x{\def\rempt##1.##2PT{##1\ifnum##2>\zero.##2\fi}}}
  \expandafter\endgroup\x

\def\strippt{\expandafter\rempt\the}

何を言ってるのか分からないと思うが、私もこんなのゼロから書けるわけありません。これは LaTeX のコアで定義されているマクロをほぼそのまま拝借してきたものです。 \expandafter のとこだけ説明しておくと、ポイントを表す「pt」の各文字をちょっと特殊な文字とみなす閉じた環境で「なんとか.なんとかpt」というトークン列にマッチさせて \rempt というマクロを定義したいのだけど、それを外から使えるようにするために環境を閉じる \endgroup の展開を遅らせているのが最初の \expandafter で、その\remptを「なんとか.なんとかpt」という形の引数に対して使うべく \the で先に展開するために配置されているのが 2つめの \expandafter です。

以上の下準備でようやく、ニュートン法を実装するのに必要なベキ乗のためのマクロ \square が定義できるようになりました。

\newdimen\tempdimen \tempdimen=\zero

\def\square#1{\tempdimen=#1%
  #1=\strippt \tempdimen \tempdimen
  \tempdimen=\zero}

この \square を使ってニュートン法のアルゴリズム全体を書き下してみます。

\newdimen\prevguess
\newdimen\val
\newdimen\guess

\def\sqrtiter{%
  \prevguess=\guess
  \square\prevguess
  \advance\prevguess by -\val  % 推測値のベキ乗と元の値との差
  \ifdim\zero>\prevguess       % 差の絶対値
    \prevguess=-\prevguess\fi
  \ifdim\prevguess<0.001pt     % 差が十分小さかったら、
    \let\next=\relax           % おしまい。
  \else                        % 十分小さくなかったら、
    \improve\guess\val         % 推測値を改善して、
    \let\next=\sqrtiter        % 再帰。
  \fi\next}

上記でまだ未定義なのは、ニュートン法のアルゴリズムで推測値を改善させる部分を担うマクロ \improve です。次はこれを実装します。\improve には「実数÷実数」の演算がでてきますが、寸法の値を使った除算には直接の手段はなさそうなので、やや面倒な工夫が必要です。全体はこんな定義になりました。

\newcount\tempcountA
\newcount\tempcountB
\newdimen\tempdimenA
\newdimen\tempdimenB

\def\improve#1#2{%
  \tempcountA=#2%                   % いったん割られる数を整数にして、
  \multiply\tempcountA by 1000%     % 十分な倍率で大きくしておく。
  \tempcountB=#1%                   % 割る数も整数にする。
  \tempdimenB=#1%
  \divide\tempcountA by \tempcountB%% ここで割り算。
  \tempdimenA=\tempcountA pt%       % 結果をポイント単位で寸法に戻す。
  \divide\tempdimenA by 1000%       % 倍率を戻す。
  \advance\tempdimenA by \tempdimenB% 
  \divide\tempdimenA by 2%
  #1=\tempdimenA}

\newcount は、整数を格納できるハコを作ります。そこに「寸法」を代入すると、先に説明したsp単位の整数に変換されて格納されます。整数どうしは \divide というプリミティブで割り算できるので、これだけでもよさそうに思えますが、 \divide は剰余を切り捨てて結果を整数に丸めてしまうので、これだけだと結果の誤差が大きくなりすぎます。そこで、割られる数だけ十分な倍率を掛けておいて、割り算して結果を寸法に変換し直したあとで倍率を戻しました。

\improve の実装に必要な残りの演算は、単純な足し算と整数2による除算なので、特にこれ以上の工夫はいりません。これで寸法 \val\guess にそれぞれベキ乗根を求めたい数と初期推測値を入れておいて \sqrtiter を起動すると、ベキ乗根の値にポイントの単位がついたものが \guess に代入されます。あとは、こんなコマンドを定義すれば目標達成です。

\def\sqrtd#1{\val=#1 pt%
  \guess=1.0pt%
  \sqrtiter%
  $\sqrt{\number#1} \approx \strippt \guess$%
}

You can get \sqrtd{2}, \sqrtd{2.5}, \sqrtd{3} and \sqrtd{3.14} 
by Newton's method with initial guess $1.0$!

今回書いた TeX スクリプトの全体はこちら。なお、 LaTeX で実数を本気で使いたいなら、こんな面倒なことをしなくても、fpパッケージという便利な浮動小数点数演算のための仕組みがあります。そちらを使いましょう。