В C / C ++ зачем использовать abs()
или же fabs()
найти абсолютное значение переменной без использования следующего кода?
int absoluteValue = value < 0 ? -value : value;
Это как-то связано с меньшим количеством инструкций на более низком уровне?
Предложенный вами «условный пресс» не эквивалентен std::abs
(или же fabs
) для чисел с плавающей запятой, см., например,
#include <iostream>
#include <cmath>
int main () {
double d = -.0;
double a = d < 0 ? -d : d;
std::cout << d << ' ' << a << ' ' << std::abs(d);
}
выход:
-0 -0 0
Дано -0.
а также 0.
представляют одно и то же действительное число «0», эта разница может иметь или не иметь значения, в зависимости от того, как используется результат. Однако функция abs, как указано в IEEE754, предписывает, чтобы знаковый бит результата равнялся 0, что запрещало бы результат -0.
, Я лично думаю, что все, что используется для вычисления некоторой «абсолютной величины», должно соответствовать этому поведению.
Для целых чисел оба варианта будут эквивалентны как во время выполнения, так и в поведении. (Живой пример)
Но, как std::abs
(или соответствующие С-эквиваленты), как известно, являются правильными и их легче читать, вы всегда должны их предпочитать.
Первое, что приходит на ум, — это читабельность.
Сравните эти две строки кодов:
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);
Компилятор, скорее всего, сделает то же самое для обоих на нижнем уровне — по крайней мере, современный компетентный компилятор.
Однако, по крайней мере, для чисел с плавающей запятой вы в конечном итоге напишите несколько десятков строк, если хотите обработать все особые случаи бесконечности, а не числа (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;
}
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 использует больше инструкций, но без веток. Я не уверен, что быстрее, на каких моделях процессоров, но совершенно очевидно, что больше инструкций редко лучше.
Зачем использовать 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
инструкция или способ извлечения знакового бита без издержек ветвления. Предположим, что арифметическое смещение вправо может заполнить регистр р с -1, если число отрицательное, или 0, если положительное, abs x
мог стать (x+r)^r
(и видя
Ответ Матса Петерссона, g ++ фактически делает это на x86).
Другие ответы касались ситуации с плавающей точкой IEEE.
Попытка сказать компилятору выполнить условную ветвь вместо доверия к библиотеке, вероятно, является преждевременной оптимизацией.
Учтите, что вы можете вставить сложное выражение в abs()
, Если вы закодируете это с expr > 0 ? expr : -expr
Вы должны повторить все выражение три раза, и оно будет оценено два раза.
Кроме того, два результата (до и после двоеточия) могут оказаться разных типов (например, signed int
/ unsigned int
), который отключает использование в операторе возврата.
Конечно, вы могли бы добавить временную переменную, но это решает только ее части, и ничем не лучше.
Предполагая, что компилятор не сможет определить, что и abs (), и условное отрицание пытаются достичь одной и той же цели, условное отрицание компилируется с инструкцией сравнения, инструкцией условного перехода и инструкцией перемещения, тогда как abs () либо компилируется в фактическую инструкцию абсолютного значения, в наборах команд, которые поддерживают такую вещь, или побитовую, и которая сохраняет все то же самое, за исключением знакового бита. Каждая вышеприведенная инструкция обычно составляет 1 цикл, поэтому использование abs (), вероятно, будет, по крайней мере, таким же быстрым или быстрым, чем условное отрицание (поскольку компилятор может все еще распознавать, что вы пытаетесь вычислить абсолютное значение при использовании условного отрицания, и генерировать инструкцию абсолютного значения в любом случае). Даже если в скомпилированном коде нет изменений, abs () все же более читабельна, чем условное отрицание.
Цель abs () — «(безусловно) установить знак этого числа в положительное значение». Даже если бы это должно было быть реализовано как условие, основанное на текущем состоянии числа, вероятно, было бы более полезно думать об этом как о простом «сделать это», а не как о более сложном «если… это… это» ,