C/C++で、次のコードを使用せずにabs()
またはfabs()
を使用して変数の絶対値を見つける必要があるのはなぜですか?
int absoluteValue = value < 0 ? -value : value;
それは、より低いレベルでより少ない命令で何か関係がありますか?
提案する「条件付きabs」は、浮動小数点数のstd::abs
(またはfabs
)と同等ではありません。
#include <iostream>
#include <cmath>
int main () {
double d = -0.0;
double a = d < 0 ? -d : d;
std::cout << d << ' ' << a << ' ' << std::abs(d);
}
出力:
-0 -0 0
-0.0
と0.0
が同じ実数 '0'を表す場合、この差は結果の使用方法に応じて重要な場合と重要でない場合があります。ただし、IEEE754で指定されているabs関数では、結果の符号ビットを0にすることが義務付けられており、結果-0.0
が禁止されます。個人的には、「絶対値」を計算するために使用されるものはすべて、この動作と一致するはずです。
整数の場合、両方のバリアントはランタイムと動作の両方で同等になります。 ( ライブの例 )
しかし、std::abs
(または適切なCの同等物)は正確で読みやすいことが知られているので、常にそれらを好むべきです。
最初に頭に浮かぶのは読みやすさです。
次の2行のコードを比較します。
int x = something, y = something, z = something;
// Compare
int absall = (x > 0 ? x : -x) + (y > 0 ? y : -y) + (z > 0 ? z : -z);
int absall = abs(x) + abs(y) + abs(z);
ほとんどの場合、コンパイラは両方の最下層で同じことを行います-少なくとも最新の有能なコンパイラ。
ただし、少なくとも浮動小数点の場合、無限大、非数(NaN)、負のゼロなどの特殊なケースをすべて処理する場合は、数十行を書くことになります。
同様に、abs
が絶対値をとるのは、ゼロよりも小さい場合はそれを否定するよりも絶対値をとる方が簡単です。
コンパイラが「愚か」である場合、if
を強制するため(それが隠されていても)、a = (a < 0)?-a:a
に対してより悪いコードを実行する可能性があります。そのプロセッサ上の浮動小数点abs命令(特別な値の複雑さは別として)
Clang(6.0-pre-release)とgcc(4.9.2)の両方が、2番目のケースのWORSEコードを生成します。
私はこの小さなサンプルを書きました:
#include <cmath>
#include <cstdlib>
extern int intval;
extern float floatval;
void func1()
{
int a = std::abs(intval);
float f = std::abs(floatval);
intval = a;
floatval = f;
}
void func2()
{
int a = intval < 0?-intval:intval;
float f = floatval < 0?-floatval:floatval;
intval = a;
floatval = f;
}
clangはfunc1に対してこのコードを作成します。
_Z5func1v: # @_Z5func1v
movl intval(%rip), %eax
movl %eax, %ecx
negl %ecx
cmovll %eax, %ecx
movss floatval(%rip), %xmm0 # xmm0 = mem[0],zero,zero,zero
andps .LCPI0_0(%rip), %xmm0
movl %ecx, intval(%rip)
movss %xmm0, floatval(%rip)
retq
_Z5func2v: # @_Z5func2v
movl intval(%rip), %eax
movl %eax, %ecx
negl %ecx
cmovll %eax, %ecx
movss floatval(%rip), %xmm0
movaps .LCPI1_0(%rip), %xmm1
xorps %xmm0, %xmm1
xorps %xmm2, %xmm2
movaps %xmm0, %xmm3
cmpltss %xmm2, %xmm3
movaps %xmm3, %xmm2
andnps %xmm0, %xmm2
andps %xmm1, %xmm3
orps %xmm2, %xmm3
movl %ecx, intval(%rip)
movss %xmm3, floatval(%rip)
retq
g ++ func1:
_Z5func1v:
movss .LC0(%rip), %xmm1
movl intval(%rip), %eax
movss floatval(%rip), %xmm0
andps %xmm1, %xmm0
sarl $31, %eax
xorl %eax, intval(%rip)
subl %eax, intval(%rip)
movss %xmm0, floatval(%rip)
ret
g ++ func2:
_Z5func2v:
movl intval(%rip), %eax
movl intval(%rip), %edx
pxor %xmm1, %xmm1
movss floatval(%rip), %xmm0
sarl $31, %eax
xorl %eax, %edx
subl %eax, %edx
ucomiss %xmm0, %xmm1
jbe .L3
movss .LC3(%rip), %xmm1
xorps %xmm1, %xmm0
.L3:
movl %edx, intval(%rip)
movss %xmm0, floatval(%rip)
ret
2番目の形式では両方のケースが特に複雑であり、gccの場合はブランチを使用することに注意してください。 Clangはより多くの命令を使用しますが、ブランチは使用しません。どのプロセッサモデルでどちらが高速かはわかりませんが、より多くの命令がより良いことはめったにありません。
条件付き否定の代わりにabs()またはfabs()を使用する理由
さまざまな理由がすでに述べられていますが、abs(INT_MIN)
は避けるべきであるため、条件付きコードの利点を考慮してください。
整数のnegative絶対値が求められる場合、abs()
の代わりに条件付きコードを使用する正当な理由があります。
// Negative absolute value
int nabs(int value) {
return -abs(value); // abs(INT_MIN) is undefined behavior.
}
int nabs(int value) {
return value < 0 ? value : -value; // well defined for all `int`
}
正の絶対関数が必要であり、value == INT_MIN
が本当の可能性である場合、abs()
、その明確さと速度のすべてが、コーナーケースに失敗します。さまざまな選択肢
unsigned absoluteValue = value < 0 ? (0u - value) : (0u + value);
特定のアーキテクチャでは、条件分岐よりも効率的な低レベルの実装が存在する場合があります。たとえば、CPUにはabs
命令、または分岐のオーバーヘッドなしで符号ビットを抽出する方法があります。算術右シフトがレジスタを埋めることができると仮定するとr数値が負の場合は-1で、正の場合は0で、abs x
は(x+r)^r
になります(そしてMats Peterssonの答えを見て、 g ++は実際にx86でこれを行います)。
IEEE浮動小数点の状況については、他の回答がありました。
ライブラリを信頼する代わりに条件分岐を実行するようにコンパイラに指示しようとすると、おそらく時期尚早な最適化になります。
複雑な式をabs()
に入れることができると考えてください。 expr > 0 ? expr : -expr
を使用してコーディングする場合、式全体を3回繰り返す必要があり、2回評価されます。
さらに、2つの結果(コロンの前後)が異なるタイプ(signed int
/unsigned int
など)になり、returnステートメントでの使用が無効になる場合があります。もちろん、一時変数を追加することもできますが、それはその一部のみを解決するものであり、いずれにしても改善されません。
コンパイラーがabs()と条件付き否定の両方が同じ目標を達成しようとしていると判断できないと仮定すると、条件付き否定は比較命令、条件付きジャンプ命令、および移動命令にコンパイルされますが、abs()はそのようなことをサポートする命令セット、またはビット単位で、符号ビットを除いてすべて同じものを保持する命令セットで、実際の絶対値命令にコンパイルします。上記のすべての命令は通常1サイクルであるため、abs()を使用すると、少なくとも条件付き否定と同じか、または高速になる可能性があります(条件付き否定を使用するときに絶対値を計算しようとしていることをコンパイラがまだ認識している可能性があるため、とにかく絶対値命令を生成します)。コンパイルされたコードに変更がなくても、abs()は条件付き否定よりも読みやすくなっています。
Abs()の背後にある意図は、「(無条件に)この数の符号を正に設定する」です。番号の現在の状態に基づいて条件として実装する必要があったとしても、より複雑な「if ... this ... that」ではなく、単純な「do this」と考えることができると便利です。 。
...それをマクロにすると、複数の評価が必要になる可能性があります(副作用)。考慮してください:
#define ABS(a) ((a)<0?-(a):(a))
そして使用:
f= 5.0;
f=ABS(f=fmul(f,b));
に展開されます
f=((f=fmul(f,b)<0?-(f=fmul(f,b)):(f=fmul(f,b)));
関数呼び出しには、この意図しない副作用はありません。