Goで整数の絶対値を求めるのに math.Abs() を使わない方がいい理由

Go

1. はじめに

Goで整数の絶対値を求めるとき、特に何も考えずに math.Abs() を使っていました。しかし、math.Abs()float64 を引数に取る関数なので、本当に問題がないのか気になって調べてみました。その結果、整数の絶対値を求めるときに math.Abs() を使うには注意が必要だということがわかりました。

注意が必要なのは以下の2点です。

以下に詳しく説明します。

2. int64 の大きい整数だと間違った値を返してしまう

int64 の整数の絶対値を求める場合、math.Abs()func Abs(x float64) float64 の形式の関数なので、int64(math.Abs(float64(x))) のように、float64 に変換してから、math.Abs() を適用する必要があります。しかし、64ビットの浮動小数点数表現である float64 の仮数部は52ビット[1]なので、52ビットに収まらない大きさの int64 の整数は正確に表現できない場合があります。

確かめるために、以下のように Test関数を作りました。

テストを実行したところ、以下のようになりました。

x = 9007199254740993 (0000000000100000000000000000000000000000000000000000000000000001) で int64(math.Abs(float64(x))) を使って絶対値を求めると、正しい絶対値の値とは異なり、エラーになっています。
このように、int64 の大きい整数だと、int64(math.Abs(float64(x))) で絶対値を求めると、間違った値を返してしまうことがあります。

3. if を使った場合よりも遅い

もうひとつわかったことは、math.Abs() を使って整数の絶対値を求めるときの速度が、if を使った場合よりも遅いということです。

こちらも確かめるために、以下のように Benchmarkを作成しました。今回は int32 の整数を使用しています。0 から 99 までの整数 x に対して、x-x の絶対値を求める速度を計測しています。

絶対値を計算する関数の3つ目の Abs3() はネットで見つけたテクニックを実装したものです。
手元のPCでBenchmarkを実行したところ、以下のようになりました。(int64 でもBenchmarkを実行してみましたが、結果は同様でした。)

この結果からわかるように、math.Abs() を使って整数の絶対値を求める関数 Abs1() が、if を使って絶対値を求める関数 Abs2() よりも3倍近く実行時間がかかっていることがわかります。また、ネットで見つけた整数の絶対値を求めるテクニックを使った関数 Abs3() よりも Abs2() の方が若干速いこともわかります。

4. おわりに

Goで整数の絶対値を求めるときに math.Abs() を使う場合に、注意が必要な点についてまとめてみました。実行速度のことも考えると、math.Abs() ではなく、if を使った関数で絶対値を求める方がよさそうです。

間違いやおかしい点などございましたら、@hideaki_sakai までお知らせください。

参考文献

  1. 倍精度浮動小数点数