为什么使用abs()或fabs()代替条件否定?

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

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

它与较低级别的较少指令有关吗?

Subhranil asked 2020-01-14T10:27:33Z
9个解决方案
120 votes

您建议的“有条件Abs”不等于std::abs(或0.0)的浮点数,请参见例如

#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

给定std::abs0.0表示相同的实数“ 0”,此差异可能会或可能不会影响,这取决于如何使用结果。 但是,IEEE754指定的abs函数将结果的符号位强制为0,这将禁止结果-0.0。我个人认为用于计算某些“绝对值”的任何内容都应与此行为匹配。

对于整数,这两个变量在运行时和行为上都是等效的。 (现场示例)

但是由于已知std::abs(或拟合C的等效项)是正确的并且更易于阅读,因此您应该始终偏爱那些。

Baum mit Augen answered 2020-01-14T10:28:00Z
86 votes

首先想到的是可读性。

比较这两行代码:

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);
iBug answered 2020-01-14T10:28:25Z
28 votes

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

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

读取a = (a < 0)?-a:a正在获取绝对值比读取小于零的绝对值要容易。

如果编译器“愚蠢”,则可能会为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使用更多指令,但没有分支。 我不确定哪种处理器型号上速度更快,但是显然更多的指令很少更好。

Mats Petersson answered 2020-01-14T10:29:25Z
11 votes

为什么要使用abs()或fabs()代替条件否定?

已经说明了多种原因,但考虑到条件代码的优势,因为应避免使用value == INT_MIN


当寻求整数的负绝对值时,有充分的理由使用条件代码代替value == INT_MIN

// 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);
chux answered 2020-01-14T10:29:59Z
6 votes

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

对于IEEE浮点数,其他答案已经解决了。

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

Davislor answered 2020-01-14T10:30:29Z
4 votes

考虑到您可以将一个复杂的表达式输入abs()。如果使用expr > 0 ? expr : -expr对其进行编码,则必须将整个表达式重复3次,并将其计算两次。
此外,这两个结果(在冒号之前和之后)可能会变成不同的类型(例如signed int/unsigned int),这会禁止在return语句中使用。当然,您可以添加一个临时变量,但这只能解决其中的部分问题,而且在任何方面也都没有改善。

Aganju answered 2020-01-14T10:30:54Z
4 votes

...并且将其放入宏中,可以进行多个不希望的评估(副作用)。 考虑:

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

函数调用不会有这种意外的副作用。

Paul Ogilvie answered 2020-01-14T10:31:27Z
3 votes

假设编译器无法确定abs()和条件否定都试图实现相同的目标,则条件否定编译为比较指令,条件跳转指令和移动指令,而abs()要么 编译成实际的绝对值指令,在支持此类事物的指令集中或按位执行,并且除符号位外所有内容均保持不变。 上面的每条指令通常为1个周期,因此使用abs()的速度可能至少与条件求反的速度相同或更快(因为编译器可能仍会识别出您在使用条件求反时试图计算绝对值,并且 生成绝对值指令)。 即使编译后的代码没有变化,abs()仍比条件取反更具可读性。

Cpp plus 1 answered 2020-01-14T10:31:49Z
3 votes

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

StarWeaver answered 2020-01-14T10:32:09Z
translate from https://stackoverflow.com:/questions/48608993/why-use-abs-or-fabs-instead-of-conditional-negation