syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

絶対値関数absとオーバーフロー

C/C++において、符号付き整数の絶対値を求める関数abs(), labs(), llabs()および整数型のstd::abs()関数オーバーロードには罠があり、「整数オーバーフロー」が考慮されていません。
これは、JavaC#のような近代的な言語と違って、C/C++の整数内部表現が2の補数系であるとは限らない、ということも関係しています。

C言語では、2の補数系において、整数型を受け取り整数型を返す絶対値関数にINT_MINLONG_MINなどが渡されたときには、各絶対値がそれぞれの型intlongなどで表現不可能なため、未定義動作 (undefined behavior) を引き起こします。

同様にC++では、2の補数系において、整数型を受け取り整数型を返すstd::abs()関数オーバーロードINT_MINLONG_MINなどが渡されたときには、未定義動作 (undefined behavior) を引き起こします。
なお、C++03以前では、<cmath>には浮動小数点数型を受け取るstd::abs()オーバーロードのみが存在しますが、C++11以降では整数型を受け取るオーバーロードも存在します。
また、C++14以前では、<cstdlib>には整数型を受け取るstd::abs()オーバーロードのみが存在しますが、C++17以降では浮動小数点数型を受け取るオーバーロードも存在します。

C# (.NET) のMath.Abs()メソッドではOverflowException例外がスローされます。

JavaMath.abs()メソッドでは入力と同じ値を返します。

C/C++では、関数呼び出し側が入力の範囲を保証するべき(オーバーヘッドを最小化するために関数内部での入力チェックを省く)、という設計思想の関数が多いんですが、いちいちアプリケーションコードで入力チェックするラッパーを書くのもダルいので、C/C++でもsafe_abs()みたいな関数を標準化して欲しいですね。
成否をbool型またはerrno_t型の戻り値で返して、成功すれば絶対値を引数T& or T*で返すとか。あるいはC++17で追加されたstd::optionalを使って戻り値として返す設計でも良いと思います。
整数オーバーフローが発生すると、以後の計算結果が不正になる可能性が高いので、いっそのこと.NETのように例外を投げてしまい、強制的に処理を中断するというのもアリだと思います。以下ではJavaでラッパーメソッドsafeAbs()を実装して例外処理する手法が紹介されています。

ちなみにC++20からは、ついに符号付き整数型が2の補数表現であることが規定されました。現実がまったく見えていない、旧世紀に生きているC++標準化委員会の干からびた老人どもがようやく重い腰を上げた、といったところですが、JavaC#に対して実に20年以上遅れていましたね*1

Cの最新規格C17 (C18) では今のところ規定されていませんが、次期標準規格 C2x では2の補数表現のみが許容されるようになることが N2412 によって提案されているそうです。

今後、C言語でも2の補数表現が規定されるようになったら、少なくともJavaのように、絶対値関数が返す値を規定して未定義動作を排除するか、あるいは返す値を規定した標準ラッパーを定義して欲しいところです。

*1:C/C++Unicodeサポートもかなり遅れています。JavaC#はともに20年以上前の登場当初からUTF-16を標準サポートしていたのに、C/C++では未だにchar16_t文字列を標準出力することすらままなりません。時代遅れとかいうレベルを超越しています。現実が何ひとつ見えていない。それだけでなく、UTF-8リテラルの型も結局charから後出しのchar8_tに変更されるなど、最近は仕様策定ミスを撤回するために互換性を破壊する仕様変更が頻繁に入るようになりました。ちょっと考えれば分かりそうなことなのに、標準化委員会は後先考えることもできない無能集団以外の何物でもありません。自称・C++のエキスパートらしいですが、現役のプログラマーやエンジニアですらない頭でっかちなダボハゼ連中です。