C++でC風ライブラリを作る(絶対値編)
高木です。おはようございます。
今回は絶対を求める関数を設計&実装したいと思います。
Cの標準ライブラリには、整数用のabs
、labs
、llabs
、imaxabs
関数があります。
また、実浮動小数点枢要にfabsf
、fabs
、fabsl
関数があります。
さらに、複素数用にcabsf
、cabs、cabsl
関数があります。
このうち、複素数用はちょっと用途が異なりますし、C++では複素数はstd::complex
クラステンプレートを使うということもあって今回は対象外にします。
必要ならあとから追加することもできますからね。
というわけで、今回は(虚数ではなく)実数の絶対値だけを考えることにします。
絶対値を求める関数のうち、浮動小数点用のものについてはとくに不満はありません。
しかし、整数用のものについては結構不満があります。
整数用の絶対値関数の最大の不満は、絶対値を求めているのに結果が負になってしまうことがある点です。
厳密にいえばオーバーフローが発生して未定義の動作になっているのですが、現実に遭遇する処理系では負の値が返ってきます。
具体的には、int
型用のabs
関数にINT_MIN
を指定した場合がそうです。
この問題を解決するには、次の3つの方法が考えられます。
- 返却値を引数より大きな型とすることで、オーバーフローが発生しないようにする。
- 返却値を符合無し整数型とする。
- オーバーフローが生じた場合に例外を送出する。
多くの言語では3番目の解決方法、すなわちオーバーフローが生じた場合に例外を送出する設計を採用しているようです。
それでもいいんですが、できれば失敗しない関数にしたいという希望もあります。
だとすると、1番目か2番目の方法になってきます。
1番目の方法は、int
型より小さい型については適切な選択だと思います。
しかし、long long
型の場合はそれ以上大きな型がありません。
浮動小数点数を使う手もありますが、精度が落ちてしまう可能性がありますのでダメです。
2番目の方法については、引数と型が変わってしまうことを除けば大きな問題はありません。
というわけで、今回は2番目のと3番目の方法それぞれについて、cloverfield::abs
関数とcloverfield::uabs
関数を作成したいと思います。
作成にあたり、SFINAEを使うなど凝った技法を使いたくなったりもするのですが、今回の趣旨はなるべくシンプルに設計&実装することですので方針が合いません。
そこで、ベタベタに関数の定義を書き並べることにしましょう。
int
、long
、long long
型とそれらに対応するunsigned
、およびfloat
、double
、long double
型について関数を定義することになります。
全部掲載すると多いので、代表してint
、unsigned int
、double
の3つだけを掲載することにします。
簡単なものから掲載していきますので、最初はunsigned int
型からです。
1 2 3 4 5 6 7 8 9 10 11 12 | namespace cloverfield { constexpr unsigned int abs(unsigned int arg) { return arg; } constexpr unsigned int uabs(unsigned int arg) { return arg; } } |
次はdouble
型です。
1 2 3 4 5 6 7 8 9 10 11 12 | namespace cloverfield { constexpr double abs(double arg) { return arg < 0 ? -arg : arg; } constexpr double uabs(double arg) { return arg < 0 ? -arg : arg; } } |
ここまでは簡単ですね。
最後はint
型です。
ここで一気に複雑になります。
この手のconstexpr
関数を書くにはC++11は面倒ですが、まあ仕方ありません。
1 2 3 4 5 6 7 8 9 10 11 12 | namespace cloverfield { constexpr int abs(int arg) { return (arg < 0 ? (arg == INT_MIN ? (throw std::overflow_error("cloverfield::abs"), 0) : -arg) : arg); } constexpr unsigned int uabs(int arg) { return arg < 0 ? -static_cast<unsigned int>(arg) : arg; } } |
ちょっと横に長くなってしまいましたが、どこで改行してもとくに見やすくならないのでそのまま掲載します。
こうして定義した関数群をヘッダファイル(cloverfield/stdlib.hh
という名前が妥当ですかね)に収めればできあがりです。