在C/C++中,为什么要使用abs()
或fabs()
来查找变量的绝对值而不使用以下代码?
int absoluteValue = value < 0 ? -value : value;
这是否与较低层级的指令数量有关?
在C/C++中,为什么要使用abs()
或fabs()
来查找变量的绝对值而不使用以下代码?
int absoluteValue = value < 0 ? -value : value;
这是否与较低层级的指令数量有关?
std::abs
(或fabs
)不等价,详见: e.g.
#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语言等效函数)是正确且易于阅读的,因此您应该始终优先选择它们。
abs()
这样的函数,即使是标准实现也不完美。 INT_MIN < 0 && abs(INT_MIN) < 0
是正确的。 - llllllllllabs(INT_MIN)
,也被视为未定义行为,这在语言本身上是一种基本的限制。如果您希望将返回类型更改为与源类型匹配(例如,在IEEE754中指定),那么无论如何都不能解决输入为INT_MIN
时的运行时问题。因此,需要修复返回类型以匹配源类型。 - Baum mit Augen首先想到的是可读性。
比较一下这两行代码:
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);
int cabs(int a) {return a > 0 ? a : -a;}
。甚至还有一个提示,说明这是关于实现的。没有人会在每个用例中复制整个实现。加油。 - luk32abs
。为什么还要费力地编写你自己的 cabs
函数呢?可能有一个原因是你不知道 abs
的存在,但既然你现在知道了,就没有必要再去重复造轮子了。 - user253751编译器在底层最可能做的事情是相同的 - 至少是现代的称职编译器。
然而,至少对于浮点数,如果你想处理所有特殊情况,如无穷大、非数字 (NaN)、负零等,你将需要编写几十行代码。
同时,将绝对值操作表述为 abs
要比表述为 "如果小于零,则取反" 更易读。
如果编译器比较"愚蠢",那么对于 a = (a < 0)?-a:a
这种形式的代码,它可能生成更糟糕的代码,因为它会强制执行一个 if
判断(即使这个判断被隐藏了),而且除了特殊值的复杂性之外,这可能比处理器上内置的浮点数 abs 指令更糟糕。
Clang (6.0-pre-release) 和 gcc (4.9.2) 都为第二种情况生成了更差的代码。
我写了这个小示例:
#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使用更多指令,但没有分支语句。我不确定哪种方式在哪种处理器模型上更快,但很明显,更多的指令通常不是更好的选择。
-O3
优化级别下吗? - Calchasabs
代码中则避免了这种情况。 - Mats Petersson为什么要使用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);
abs(INT_MIN) --> INT_MIN
,或者是abs(INT_MIN) --> INT_MAX
,或者程序崩溃等等。没有一种结果是普遍优选的,因此最好让实现在处理其他值时更快,并让 abs(INT_MIN) --> UB
来涵盖所有实现。 同意不同意。 - chux - Reinstate Monicaabs(INT_MIN)
只有在二进制补码算术下才是未定义行为。在符号-大小算术下(虽然现在没有人使用它),它是完全定义良好的。 - Mark(0u - value)
是使用提升为 unsigned int
进行计算,并且结果是按模计算的,就像所有无符号算术一样。 - Ben Voigtabs()
。如果您使用 expr > 0 ? expr : -expr
的方法编码,则需要重复整个表达式三次,并且它将被评估两次。signed int
/ unsigned int
),这将禁用在返回语句中的使用。
当然,您可以添加一个临时变量,但这只解决了部分问题,并且在任何方面都不更好。abs(printf("hello, world!\n"))
? - Davislora?b:c
只评估 b
和 c
中的一个。 - L. F.如果你把它变成一个宏,你可能会有多个评估,而你可能不希望这样(副作用)。请考虑:
#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)));
函数调用不会产生意外的副作用。
abs()函数的意图是“(无条件地)将这个数的符号设为正数”。即使必须基于数字的当前状态来实现这一点,能够将其视为简单的“执行此操作”而不是更复杂的“如果……那么……”可能更有用。