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

Sub*_*nil 51 c c++ variables negation absolute-value

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

int absoluteValue = value < 0 ? -value : value;
Run Code Online (Sandbox Code Playgroud)

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

Bau*_*gen 119

您建议的"条件绝对值"不等于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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

-0 -0 0
Run Code Online (Sandbox Code Playgroud)

给定-0.00.0表示相同的实数"0",这种差异可能或可能不重要,这取决于结果的使用方式.但是,IEEE754规定的abs函数要求结果的signbit为0,这将禁止结果-0.0.我个人认为用于计算某些"绝对值"的任何东西都应该符合这种行为.

对于整数,两种变体在运行时和行为上都是等价的.(实例)

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

  • 浮点中的"负零点"确实需要考虑. (18认同)
  • @liliscent`abs(INT_MIN)`是UB甚至,至少在C++中,但这是语言的一个基本限制.您希望修复返回类型以匹配源类型(例如,在IEEE754中指定),因此输入的运行时问题"INT_MIN"无论如何都无法真正解决. (15认同)
  • 这个答案是有益的.但是,对于像`abs()`这样的函数,即使是标准工具也不是完美的.`INT_MIN <0 && abs(INT_MIN)<0`为真. (5认同)
  • @BaummitAugen 你是对的,即使在 C99 中它也是 UB http://port70.net/~nsz/c/c99/n1256.html#7.20.6.1p2。 (2认同)
  • @Nebr 这确实是它产生影响的情况之一。https://wandbox.org/permlink/37Xk8h7qSJbHqFxl (2认同)

iBu*_*Bug 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);
Run Code Online (Sandbox Code Playgroud)

  • @ luk32问题是"为什么选择一件事而不是另一件事?" 一个答案是可读性,因为它们确实存在很大差异.我不会称之为"挑剔". (77认同)
  • @ luk32当然,但是标准库作者有用地预见到你可能想要这样做,所以他们为你编写了这个函数,所以你不需要自己编写它.它被称为`abs`.为什么还要编写自己的`cabs`函数来做同样的事情呢?一个原因可能是你不知道`abs`存在,但是既然你现在这样做,那就不像你从中吐出它了. (42认同)
  • 哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇哇噢 问题显然不是语法而是实现.你的"问题"很容易解决......而你的回答很有问题"我们为什么要将单行包装成函数?" (11认同)
  • @ SH7890可读性是没有问题的,因为你可以编写一个函数来防止这种情况,或者甚至是一个宏.它需要一行,看起来完全一样.这是一个可读性修复:`int cabs(int a){return a> 0?a:-a;}`.甚至有一个提示,它是关于实施的.没有人会在每个用例上复制粘贴整个实现.来吧. (4认同)
  • @ luk32你明白了我的意思.可读性无需担心,因为我可以使用该行代码定义宏/函数.我一直在寻找较低等级或不对称. (3认同)
  • @moopet最后一次.对我而言,它表明,有多少程序员忽略了这一点,并且能够注意到无关紧要的事情,只是为了找到问题.这不回答这个问题.它回答了一个不同的问题.我同意immibis的每一点,我不会重新实现`abs`,可读性对我来说非常重要......但就这个问题而言,它不是一个问题.显然对大多数人来说,我错了,我接受了.我更喜欢人们对可读性过于敏感,而不是忘记,所以我很好.干杯. (3认同)
  • @immibis"为什么还要编写自己的出租车功能呢?" - 我不知道,但这是OP的问题,因为我理解它.`a> 0有什么区别?a:-a`和标准`abs()`......而且它不可读.你的所有积分都是有效的.我只是觉得调用可读性(你没有)是荒谬的.无论如何,这是一个受欢迎的投票,我尊重这一点.我表达了我的意见.每个人的虚拟啤酒. (2认同)
  • @luk32 这不是很多赞成票,因为它回答了问题 - 我认为投票统计数据很有趣,因为它所做的是展示程序员有多少关心可读性。 (2认同)

Mat*_*son 28

编译器最有可能在底层做同样的事情 - 至少是一个现代的合格编译器.

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

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

如果编译器是"愚蠢的",它可能最终会做更糟糕的代码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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

  • 这个答案说现代有能力的编译器很可能会为两者做同样的事情,然后显示汇编代码,证明所选编译器没有做同样的事情.这是一个相互矛盾,令人困惑的消息.选定的编译器是无能还是不现代?为什么用它们作为例子?或者说现代有能力的编译器最有可能对这两个错误做同样的事情?为什么生成的代码有差异? (20认同)
  • "*很明显,更多的指示很少会更好.*"我不同意,特别是当你比较分支时.它会导致预取和乱序执行.它很复杂,可能不安全. (10认同)
  • @Calchas:我使用了 -O2,这是我通常用于“优化”代码的内容。但是 -O3 并没有以任何明显的方式改变代码。较新版本的 gcc 可能有所不同 - 但我现在没有其中一个 - 我的电脑即将升级,所以也会得到一个新的编译器 [它不是今天早上开始的,让它运行这个晚上,在途中更换位]。 (2认同)

chu*_*ica 11

为什么使用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`
}
Run Code Online (Sandbox Code Playgroud)

当需要一个正的绝对函数并且value == INT_MIN是一个真正的可能性时abs(),尽管它的清晰度和速度都没有失败.各种替代品

unsigned absoluteValue = value < 0 ? (0u - value) : (0u + value);
Run Code Online (Sandbox Code Playgroud)

  • @manavm-n C规格规格是UB."如果无法表示结果,则行为未定义." §7.22.6.12 (6认同)
  • abs(INT_MIN)的+1是未定义的行为.我不知道.图书馆实施者为何未定义? (2认同)
  • @ manavm-n考虑可能的结果是`abs(INT_MIN)-&gt; INT_MIN`或`abs(INT_MIN)-&gt; INT_MAX`或程序模具等。普遍都不推荐使用,因此最好允许实现与其他值保持一致,并让`abs(INT_MIN)-&gt; UB`包含所有实现。[同意不同意](https://en.wikipedia.org/wiki/Agree_to_disagree) (2认同)

Dav*_*lor 6

在给定的体系结构上,可能存在比条件分支更高效的低级实现.例如,CPU可能有一条abs指令或一种提取符号位而无需分支开销的方法.假设算术右移可以填写一个登记册[R与-1,如果是负数,或者0,如果积极的,abs x能成为(x+r)^r(和看到垫皮特森的回答,G ++实际上做这在x86).

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

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