web-dev-qa-db-ja.com

条件付き否定の代わりにabs()またはfabs()を使用する理由

C/C++で、次のコードを使用せずにabs()またはfabs()を使用して変数の絶対値を見つける必要があるのはなぜですか?

int absoluteValue = value < 0 ? -value : value;

それは、より低いレベルでより少ない命令で何か関係がありますか?

51
Subhranil

提案する「条件付き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.00.0が同じ実数 '0'を表す場合、この差は結果の使用方法に応じて重要な場合と重要でない場合があります。ただし、IEEE754で指定されているabs関数では、結果の符号ビットを0にすることが義務付けられており、結果-0.0が禁止されます。個人的には、「絶対値」を計算するために使用されるものはすべて、この動作と一致するはずです。

整数の場合、両方のバリアントはランタイムと動作の両方で同等になります。 ( ライブの例

しかし、std::abs(または適切なCの同等物)は正確で読みやすいことが知られているので、常にそれらを好むべきです。

119
Baum mit Augen

最初に頭に浮かぶのは読みやすさです。

次の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);
86
iBug

ほとんどの場合、コンパイラは両方の最下層で同じことを行います-少なくとも最新の有能なコンパイラ。

ただし、少なくとも浮動小数点の場合、無限大、非数(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はより多くの命令を使用しますが、ブランチは使用しません。どのプロセッサモデルでどちらが高速かはわかりませんが、より多くの命令がより良いことはめったにありません。

28
Mats Petersson

条件付き否定の代わりに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);
10
chux

特定のアーキテクチャでは、条件分岐よりも効率的な低レベルの実装が存在する場合があります。たとえば、CPUにはabs命令、または分岐のオーバーヘッドなしで符号ビットを抽出する方法があります。算術右シフトがレジスタを埋めることができると仮定するとr数値が負の場合は-1で、正の場合は0で、abs x(x+r)^rになります(そしてMats Peterssonの答えを見て、 g ++は実際にx86でこれを行います)。

IEEE浮動小数点の状況については、他の回答がありました。

ライブラリを信頼する代わりに条件分岐を実行するようにコンパイラに指示しようとすると、おそらく時期尚早な最適化になります。

6
Davislor

複雑な式をabs()に入れることができると考えてください。 expr > 0 ? expr : -exprを使用してコーディングする場合、式全体を3回繰り返す必要があり、2回評価されます。
さらに、2つの結果(コロンの前後)が異なるタイプ(signed int/unsigned intなど)になり、returnステートメントでの使用が無効になる場合があります。もちろん、一時変数を追加することもできますが、それはその一部のみを解決するものであり、いずれにしても改善されません。

4
Aganju

コンパイラーがabs()と条件付き否定の両方が同じ目標を達成しようとしていると判断できないと仮定すると、条件付き否定は比較命令、条件付きジャンプ命令、および移動命令にコンパイルされますが、abs()はそのようなことをサポートする命令セット、またはビット単位で、符号ビットを除いてすべて同じものを保持する命令セットで、実際の絶対値命令にコンパイルします。上記のすべての命令は通常1サイクルであるため、abs()を使用すると、少なくとも条件付き否定と同じか、または高速になる可能性があります(条件付き否定を使用するときに絶対値を計算しようとしていることをコンパイラがまだ認識している可能性があるため、とにかく絶対値命令を生成します)。コンパイルされたコードに変更がなくても、abs()は条件付き否定よりも読みやすくなっています。

3
Cpp plus 1

Abs()の背後にある意図は、「(無条件に)この数の符号を正に設定する」です。番号の現在の状態に基づいて条件として実装する必要があったとしても、より複雑な「if ... this ... that」ではなく、単純な「do this」と考えることができると便利です。 。

3
StarWeaver

...それをマクロにすると、複数の評価が必要になる可能性があります(副作用)。考慮してください:

#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)));

関数呼び出しには、この意図しない副作用はありません。

3
Paul Ogilvie