先日の某キャンペーン☘️向けの某記事で「TeXのキーワード」について解説した。その中で「TeXの絶対単位の一覧」を紹介した。
| 単位 | 意味 | TeXでの定義 |
|---|---|---|
pt |
ポイント(point) | |
in |
インチ(inch) | 1in=(7227/100)pt |
pc |
パイカ(pica) | 1pc=12pt |
cm |
センチメートル | 1cm=(7227/254)pt |
mm |
ミリメートル | 1mm=(7227/2540)pt |
bp |
ビッグポイント (big point) |
1bp=(7227/7200)pt |
dd |
ディドーポイント (Didot point) |
1dd=(1238/1157)pt |
cc |
シセロ(Cicero) | 1cc=(14856/1157)pt |
sp |
スケールドポイント (scaled point) |
1sp=(1/65536)pt |
この中の「TeXでの定義」をよく見ると、「1 mm=(7227/2540) pt」のように各単位はptの分数倍として定義されている。TeXの内部において実数は有理数ではなく小数(整数部16ビット・小数部16ビットの二進固定小数点数)で表されていることを考慮すると、これは一見奇妙に思える。
TeX処理系の単位の実装の話
実際のTeXの実装コードを見てみよう。
@ The necessary conversion factors can all be specified exactly as
fractions whose numerator and denominator sum to 32768 or less.
(……中略……)
@<Scan for \(a)all other units and adjust |cur_val| and |f|...@>=
if scan_keyword("in") then set_conversion(7227)(100)
@.in@>
else if scan_keyword("pc") then set_conversion(12)(1)
@.pc@>
else if scan_keyword("cm") then set_conversion(7227)(254)
@.cm@>
else if scan_keyword("mm") then set_conversion(7227)(2540)
@.mm@>
else if scan_keyword("bp") then set_conversion(7227)(7200)
@.bp@>
else if scan_keyword("dd") then set_conversion(1238)(1157)
@.dd@>
else if scan_keyword("cc") then set_conversion(14856)(1157)
@.cc@>
説明文に「conversion factors can all be specified exactly as fractions」とある通りで、実際にpt以外の絶対単位1はptの分数倍として実装されていることがわかる。
もちろん「実数の内部表現が固定小数点数である」ことも確かなので、最終的な計算結果には丸め2が入る。例えば10.5cmが表す寸法値(sp単位)は次のように求められる。
- ① 十進小数
10.5を固定小数点数で表す3。 - ② 1 cm=(7227/254) pt なので、(①の値)×7227÷254 を計算してその値を固定小数点数で表す。(これがpt単位の値になる。)
- ③ (②の値)に65536を乗じてsp単位に直す。
これにより、10.5cmが表す値は 19 579 138 sp(pt単位で298.753 936 767 578pt)と求められる。(この値を\message等で表示させると298.75394ptとなる4。)
TeXコードの単位の実装の話
TeXの文法では寸法値の単位としてpt・mm・emなどの“本来の単位”の他に「寸法型の内部値」も使用できる。例えばパラメタ\baselineskipは寸法型の内部値の一種なので、2\baselineskipは有効な寸法値の表記となる。
この性質を利用したTeX言語のコーディング上の技法として「単位の代わりに寸法レジスタを使う」というものがある。
\newdimen\mm \mm=1mm % 以後 \mm は定数として使う
このように寸法レジスタ\mmを用意しておくと、0.6\mmは「\mm(=1 mm)の寸法を単位にしてその0.6倍」だから結局 0.6 mm になるはずである。実際、この場合は0.6\mmと0.6mmは同じ寸法値になる5。
\dimen0=0.6mm \message{\the\dimen0} %==>"1.70717pt" \dimen0=0.6\mm \message{\the\dimen0} %==>"1.70717pt"
この技法は、高速化のために行われることもあるし、また「マクロの仕様において寸法の単位を可変にしたい(例えばLaTeXのpicture環境における\unitlengthの使用など)」という理由で行われることもある。
mm と \mm が同じにならない話
しかし先述の「TeXの単位は分数として定義されている」ということを勘案するとこの技法には注意すべき点があることが推測できる。寸法レジスタ(などの内部値)がもつ寸法値は普通の固定小数点数で表されていて分数ではない。だから“本物の単位”と“単位レジスタ”では内部の処理が異なるはずである。
実際、先の例で数値を0.6から1.2に変えてみると、mm単位と\mm単位の表す寸法値は異なるものになる6。
\dimen0=1.2mm \message{\the\dimen0} %==>"3.41432pt" \dimen0=1.2\mm \message{\the\dimen0} %==>"3.41429pt"
両者の計算を追跡すると以下のようになる。
1.2mmは【((7227/2540) ×【1.2の丸め】)の丸め】という計算になる。1.2\mmは【(【(7227/2540) の丸め】×【1.2の丸め】)の丸め】という計算になる。
つまり、“レジスタ単位”を利用した後者の場合は、単位の換算係数の箇所に丸めが新たに発生し、その分だけ結果の誤差が増えるわけである7。
もちろん、実用のTeX言語プログラミングでは「寸法値は計算誤差を含む」ことを前提にすることが常識であるため、特に「単位は分数である」という性質が問題になることはないだろう。しかし何か特別な用途があって敢えて「寸法値の等値比較を行う」処理を実装する場合、この性質を知っておく必要がある。
pt と \p@ が同じになる話
“レジスタ単位”という技法の最も有名な使用例はplain TeXの\p@であろう。(LaTeXカーネルにも継承されている。)
\newdimen\p@ \p@=1pt % this saves macro space and time
ではこれまでの話と同様にpt単位と\p@単位は異なる値になりえるのかというと、この場合はそういうことはない。なぜかというと、“レジスタ単位”を使う場合に新たに丸めが発生する箇所は「単位換算の係数」だからである。先のplainのコードでは単位がptであるため係数は1であり、これは整数であるから固定小数点数に丸めた結果も正確な1になる。従って、pt単位と\p@単位の表す寸法値は常に一致するのである。
まとめ
😲「アッ、また某ツイーターのサッパリ実用的でない記事が増えてるぞ!」
(ざんねん🙃)
-
ただし
spは扱いが別になっている。この記事ではspについては扱わないことにする。↩ -
①での丸めは四捨五入(例えば
0.99999999ptは1ptになる)、②での丸めは切り捨てである。③は「整数16ビット+小数16ビット」の固定小数点数の小数点を右端にずらして32ビット整数と見なす処理なので誤差は発生しない。↩ - TeXの小数表現は二進であるため、十進法の有限小数を正確に表せずに丸めが発生する場合があることに注意。10.5(二進法で 1010.1)は正確に表せるが、1.2(二進法では無限小数)は表せない。↩
-
TeXが寸法値を表示するときは「その表記を読んだときに元の寸法値となるような、最も小数部が短くなるpt単位の表記」が使われる。例えば
298.75394ptという表記をTeXが読むとそれは 19 579 138 sp と解釈される。↩ - 先述の「寸法値の表示の原理」からわかるように、表示が同じ寸法値は内部でも同じ寸法値であり、逆もまた然りである。↩
-
数値が1.02あたりを超えると、
mm単位と\mm単位は必ず異なる寸法値になる。↩ -
1.2mmが表す値は 223761sp (=3.414 321 899 414 06pt)、1.2\mmが表す値は 223761sp (=3.414 291 381 835 94pt)、正確な値は (21681/6350)pt (=3.414 330 7…pt)である。↩