2023年2月19日日曜日

Type Generic Mathematics Header

仕事(組み込み)で数学的な処理が必要になった。
昔なら、整数演算して、関数の類はテーブル解決にしたりして、そして出来上がったコードは複雑怪奇で、メンテナス性が低く、作った本人もすっかり忘れて、ブラックボックスになっていく...というパターン。
最近は組み込み用のマイコンでも単精度ながら浮動小数点演算器が入っているものもある(Cortex-M4とか)。
そういうのなら、整数演算でなくても良い。メンテしやすいコードでも性能が出る。

C言語の実数型は一般的に、単精度と倍精度がある。80年代には拡張精度もあった。最近では4倍精度も使える。
処理速度や計算精度の最高のバランスを得ようとするとき、実数の型(精度)を入れ替えながら検証したいところだ。

四則演算だけなら、マクロやtypedefで変数の型を定義直すことで、一発で簡単に実数の型を変更できる。
しかし、算術ライブラリmath.h内の関数を切り替えるのは、少し面倒だ。
C言語には、C++で言うところのオーバーロードの機能が無いので、やや複雑なマクロを使って切り替えなければならない。
マクロは型を認識できないためだ(少なくとも昔ながらの標準的なCコンパイラでは)。

マクロはプリプロセッサが処理する。プリプロセッサはその名の通り、前処理をするもので字面を置き換えることしかできない。
型が意味を持つのは、パーサーやコードジェネレータの段階だ。
マクロは型を知らないので、マクロで型を何にするかを示して、その値を使って型と関数の置き換えを定義するような2段階構造にしなければならなかった。

math.hが提供する関数全てにマクロを用意するのは面倒なので、リストファイルを参照(gccなら"objdump -t")し、使用しているシンボルを確認して、使っている関数のみ定義する。
すなわち、その場しのぎのやっつけ実装。
そんな感じなので、いつもやり直すことになる。

今回、移植しようとしたコードがatan()をそのまま使っていた。
atan()と言えば、この記事に示すように、そのまま使うべきではない。
y/xが無限にならない事が解っているならそれでも良いが、不要な疑念を避けるためにもatan2()に置き換えるべきだ。

それは解っているのだが、自称「おっちょこちょいリンピック日本代表」のこの私が5年前の記事をハッキリ憶えてるわけがない。
80年代からの自分の解決方法 "atanXY()" が邪魔をして、"atan2()"の名前が出てこない。プログラミングは、名前が重要だ。

思い出そうとしても無理なので、/usr/include の下の全ヘッダファイルに対して、文字列'atan'を検索してみた。
大量の一致がみつかり、絞り込みをしている時に、"/usr/include/tgmath.h" を発見した。
"/usr/include" 直下にあることから、かなり標準的なもののようだが、これは一体なんだ?
と思って、調べ始めた。

"tgmath" = "Type Generic Mathematics" の意味で、日本語にするなら「型一般化算術」とでもいうのかな?
"math.h"の代わりに、このヘッダを使うだけで、引数の型から自動的に呼び出す関数を切り替えてくれるすぐれものだ。

ISO C標準 11 (C11)で追加され、以降標準的に使えるらしい。
型を知るためにコンパイラが強化されていて、2段階の定義は要らない。
(逆に古いコンパイラではヘッダファイルだけあっても正しく動かないだろう)

引数の型を自動認識するのは強力で、イミディエート値でも、typedefし直した型でも、正しく解決してくれる。
例えば、以下のように、atan2()を呼び出すと仮定してみよう。
void func( MY_REAL x, MY_REAL y )
{
    MY_REAL angle = atan2( y, x ); 
    :
    : (省略)
    :
このとき、MY_REAL が
typedef float  MY_REAL;
のようになっていると、生成されるコードは、atan2f()を呼び出す。また、
typedef long double  MY_REAL;
のようになっていると、生成されるコードは、atan2l()を呼び出す。
生成された.oファイルを、"objdump -t"で見てみると良い。

この問題、世界中の人がうんざりしていたのかな。
速度と精度のトレードオフのために、実装した後で、単精度、倍精度、4倍精度を切り替えて評価を行うこともあるだろう。
その作業が楽になる。

整数(固定小数点)演算に置き換えるのは、計算の桁数や精度を考え、検証しなければならなかった。
この作業は実装結果だけみても、苦労が理解されにくく、評価されない。
作業量を見積もっても「なんで?」みたいになることが多い。
うっかりBugでも出そうものなら、「簡単なのに何やってるの?」みたいな。
さらに、入出力の精度や桁数に強く依存しており、出来上がったコードは奇妙奇天烈複雑怪奇魑魅魍魎渦巻く地雷原であり、再利用が困難になる。
それが単精度浮動小数点が使えるだけで、ものすごく楽になる。言うならば「素人でも実装できる」。
メンテナス性も向上するだろう。

単精度、倍精度の切り替えは、マクロで解決できる内容ではあるが、それすら用意する必要が無くなる。
いい時代になったものだ。

0 件のコメント:

コメントを投稿