C/C++において、符号付き整数の絶対値を求める関数abs()
, labs()
, llabs()
および整数型のstd::abs()
関数オーバーロードには罠があり、「整数オーバーフロー」が考慮されていません。
これは、JavaやC#のような近代的な言語と違って、C/C++の整数内部表現が2の補数系であるとは限らない、ということも関係しています。
C言語では、2の補数系において、整数型を受け取り整数型を返す絶対値関数にINT_MIN
やLONG_MIN
などが渡されたときには、各絶対値がそれぞれの型int
やlong
などで表現不可能なため、未定義動作 (undefined behavior) を引き起こします。要するにそれらの値を渡した場合は実際に何が起きるか分からないので絶対に渡すな、事前にチェックしろということです。
- abs, labs, llabs, imaxabs - cppreference.com (日本語)
- fabs, fabsf, fabsl - cppreference.com (日本語)
- abs, labs, llabs, imaxabs - cppreference.com (英語)
- fabs, fabsf, fabsl, fabsd32, fabsd64, fabsd128 - cppreference.com (英語)
同様にC++では、2の補数系において、整数型を受け取り整数型を返すstd::abs()
関数オーバーロードにINT_MIN
やLONG_MIN
などが渡されたときには、未定義動作 (undefined behavior) を引き起こします。
なお、C++03以前では、<cmath>
には浮動小数点数型を受け取るstd::abs()
オーバーロードのみが存在しますが、C++11以降では整数型を受け取るオーバーロードも存在します。
また、C++14以前では、<cstdlib>
には整数型を受け取るstd::abs()
オーバーロードのみが存在しますが、C++17以降では浮動小数点数型を受け取るオーバーロードも存在します。
- std::abs, std::labs, std::llabs, std::imaxabs - cppreference.com (日本語)
- std::abs(float), std::fabs, std::fabsf, std::fabsl - cppreference.com (日本語)
- std::abs, std::labs, std::llabs, std::imaxabs - cppreference.com (英語)
- std::abs(float), std::fabs, std::fabsf, std::fabsl - cppreference.com (英語)
- http://www.cplusplus.com/reference/cmath/abs/ (cmath)
- http://www.cplusplus.com/reference/cstdlib/abs/ (cstdlib)
C# (.NET) のMath.Abs()
メソッドではOverflowException
例外がスローされます。
JavaのMath.abs()
メソッドでは入力と同じ値を返します。
- Math (Java Platform SE 8) (
abs(int)
) - Math (Java Platform SE 8) (
abs(long)
)
C/C++では、関数呼び出し側が入力の範囲を保証するべき(オーバーヘッドを最小化するために関数内部での入力チェックを省く)、という設計思想の関数が多いんですが、いちいちアプリケーションコードで入力チェックするラッパーを書くのもダルいので、C/C++でもsafe_abs()
みたいな関数を標準化して欲しいですね。
成否をbool
型またはerrno_t
型の戻り値で返して、成功すれば絶対値を引数T&
or T*
で返すとか。あるいはC++17で追加されたstd::optional
を使って戻り値として返す設計でも良いと思います。
整数オーバーフローが発生すると、以後の計算結果が不正になる可能性が高いので、いっそのこと.NETのように例外を投げてしまい、強制的に処理を中断するというのもアリだと思います。以下ではJavaでラッパーメソッドsafeAbs()
を実装して例外処理する手法が紹介されています。
ちなみにC++20からは、ついに符号付き整数型が2の補数表現であることが規定されました。現実がまったく見えていない、旧世紀に生きているC++標準化委員会の干からびた老人どもがようやく重い腰を上げた、といったところですが、JavaやC#に対して実に20年以上遅れていましたね。
Cの最新規格C17 (C18) では今のところ規定されていませんが、次期標準規格 C2x では2の補数表現のみが許容されるようになることが N2412 によって提案されているそうです。
今後、C言語でも2の補数表現が規定されるようになったら、少なくともJavaのように、絶対値関数が返す値を規定して未定義動作を排除するか、あるいは返す値を規定した標準ラッパーを定義して欲しいところです。
余談ですが、C/C++はUnicodeサポートもかなり遅れています。JavaやC#はともに20年以上前の登場当初からUTF-16を標準サポートしていたのに、C/C++では未だにchar16_t文字列を標準出力することすらままなりません。時代遅れとかいうレベルを超越しています。現実が何ひとつ見えていない。それだけでなく、UTF-8リテラルの型も結局charから後出しのchar8_tに変更されるなど、最近は仕様策定ミスを撤回するために互換性を破壊する仕様変更が頻繁に入るようになりました。ちょっと考えれば分かりそうなことなのに、標準化委員会は後先考えることもできない無能集団以外の何物でもありません。自称・C++のエキスパートらしいですが、現役のプログラマーやエンジニアですらない頭でっかちなダボハゼ連中です。
※2023-02-19追記:
C++23でようやくUnicode文字列の標準出力をサポートするstd::vprint_unicode()
が追加されるようです。すさまじい周回遅れ。
C/C++よりも安全なシステムプログラミング言語としてRustが台頭してきたので、C/C++の標準化委員会は焦っているんでしょうね。しかしC/C++には依然として未定義動作・未規定動作や処理系依存動作が多数残っており、安全性の問題は解決されていないので、このままだと確実にC/C++は時代遅れの化石となることが目に見えています。