为什么使用abs()或fabs()而不是条件否定?

时间:2018-02-04 14:17:13

标签: c++ c variables negation absolute-value

在C / C ++中,为什么不使用abs()fabs()来查找变量的绝对值而不使用以下代码?

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

是否与较低级别的指令较少有关?

9 个答案:

答案 0 :(得分:118)

&#34;条件abs&#34;你建议的浮点数不等于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代表相同的实数&#39; 0&#39;,这种差异可能会或可能不会重要,具体取决于结果的使用方式。但是,IEEE754规定的abs函数要求结果的signbit为0,这将禁止结果-0.0。我个人认为用于计算某些绝对值的任何东西&#34;应该符合这种行为。

对于整数,两种变体在运行时和行为上都是等价的。 (Live example

但是,当std::abs(或拟合C等价物)已知正确且易于阅读时,您应该总是更喜欢这些。

答案 1 :(得分:86)

首先想到的是可读性。

比较这两行代码:

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

答案 2 :(得分:28)

编译器很可能在底层对两者都做同样的事情 - 至少是一个现代的合格编译器。

但是,至少对于浮点数,如果你想处理无穷大,非数字(NaN),负零等所有特殊情况,你最终会写几十行。

同样更容易阅读abs取绝对值而不是读取如果它小于零,则取消它。

如果编译器是&#34;愚蠢&#34;,它最终可能会为a = (a < 0)?-a:a做更糟糕的代码,因为它会强制if(即使它被隐藏) ,这可能比该处理器上的内置浮点abs指令更糟糕(除了特殊值的复杂性)

Clang(6.0-pre-release)和gcc(4.9.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

请注意,在第二种形式中,两种情况都明显更复杂,而在gcc情况下,它使用分支。 Clang使用了更多指令,但没有分支。我不确定哪种处理器型号更快,但很明显更多指令很少更好。

答案 3 :(得分:10)

  

为什么使用abs()或fabs()而不是条件否定?

已经说明了各种原因,但是应该考虑条件码优势,因为abs(INT_MIN)应该避免。

当寻求整数的绝对值时,有充分的理由使用条件代码代替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);

答案 4 :(得分:5)

在给定的体系结构上,可能存在比条件分支更高效的低级实现。例如,CPU可能具有abs指令,或者提取符号位而无需分支开销的方法。假设算术右移可以填充寄存器 r ,如果数字为负数,则为-1;如果为正数,则为0,abs x可能变为(x+r)^r(并且看到 Mats Petersson的回答,g ++实际上是在x86上做到这一点。

其他答案已经超越了IEEE浮点的情况。

尝试告诉编译器执行条件分支而不是信任库可能是过早的优化。

答案 5 :(得分:4)

考虑您可以将复杂的表达式提供给abs()。如果使用expr > 0 ? expr : -expr对其进行编码,则必须重复整个表达式三次,并且将对其进行两次评估。
此外,两个结果(冒号前后)可能会变成不同类型(如signed int / unsigned int),这会禁用在return语句中的使用。 当然,你可以添加一个临时变量,但它只能解决部分变量,并且不会更好。

答案 6 :(得分:3)

假设编译器无法确定abs()和条件否定都试图实现相同的目标,条件否定将编译为比较指令,条件跳转指令和移动指令,而abs ()或者编译为实际的绝对值指令,在支持这样的事物的指令集中,或者按位,并且除了符号位之外保持一致。上面的每条指令通常都是1个周期,因此使用abs()可能至少与条件否定一样快或更快(因为编译器可能仍然认识到您在使用条件否定时尝试计算绝对值,并且无论如何都要生成一个绝对值指令)。即使编译代码没有变化,abs()仍然比条件否定更具可读性。

答案 7 :(得分:3)

abs()背后的意图是“(无条件地)将此数字的符号设置为正”。即使必须根据数字的当前状态将其实现为条件,但将其视为简单的“执行此操作”可能更有用,而不是更复杂的“if ...... this ...... that”

答案 8 :(得分:3)

...并且你会把它变成一个宏,你可以有多个你可能不想要的评估(副作用)。考虑:

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

函数调用不会产生这种意想不到的副作用。