libreria labs float comando biblioteca absf abs c++ c variables negation absolute-value

c++ - labs - ¿Por qué usar abs() o fabs() en lugar de negación condicional?



int abs (9)

En C / C ++, ¿por qué debería uno usar abs() o fabs() para encontrar el valor absoluto de una variable sin usar el siguiente código?

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

¿Tiene algo que ver con menos instrucciones en el nivel inferior?


¿Por qué usar abs () o fabs () en lugar de negación condicional?

Ya se han establecido varias razones, sin embargo, considere las ventajas del código condicional, ya que se deben evitar los abs(INT_MIN) .

Hay una buena razón para usar el código condicional en lugar de abs() cuando se busca el valor absoluto negativo de un entero

// 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` }

Cuando se necesita una función absoluta positiva y el value == INT_MIN es una posibilidad real, abs() , para toda su claridad y velocidad falla un caso de esquina. Varias alternativas

unsigned absoluteValue = value < 0 ? (0u - value) : (0u + value);


... y si lo convierte en una macro, puede tener múltiples evaluaciones que quizás no desee (efectos secundarios). Considerar:

#define ABS(a) ((a)<0?-(a):(a))

y use:

f= 5.0; f=ABS(f=fmul(f,b));

que se expandiría a

f=((f=fmul(f,b)<0?-(f=fmul(f,b)):(f=fmul(f,b)));

Las llamadas a funciones no tendrán estos efectos secundarios no deseados.


La intención detrás de abs () es "(incondicionalmente) establecer el signo de este número en positivo". Incluso si eso tuviera que implementarse como un condicional basado en el estado actual del número, probablemente sea más útil poder considerarlo como un simple "hacer esto", en lugar de un "si ... esto ... eso" más complejo .


Lo más probable es que el compilador haga lo mismo para ambos en la capa inferior, al menos un compilador competente moderno.

Sin embargo, al menos para el punto flotante, terminarás escribiendo unas pocas docenas de líneas si quieres manejar todos los casos especiales de infinito, no un número (NaN), cero negativo, etc.

Además, es más fácil leer que abs está tomando el valor absoluto que leer que si es menor que cero, lo niega.

Si el compilador es "estúpido", puede terminar haciendo peor código para a = (a < 0)?-a:a , porque fuerza un if (incluso si está oculto), y eso podría ser peor que el instrucciones de abs de coma flotante incorporadas en ese procesador (aparte de la complejidad de los valores especiales)

Tanto Clang (6.0-pre-release) como gcc (4.9.2) generan un código PEOR para el segundo caso.

Escribí esta pequeña muestra:

#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 hace este código para 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

Tenga en cuenta que ambos casos son notablemente más complejos en la segunda forma, y ​​en el caso de gcc, utiliza una rama. Clang usa más instrucciones, pero no rama. No estoy seguro de cuál es más rápido en qué modelos de procesador, pero claramente más instrucciones rara vez son mejores.


Lo primero que viene a la mente es la legibilidad.

Compare estas dos líneas de códigos:

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


Los "abdominales condicionales" que usted propone no son equivalentes a std::abs (o fabs ) para números de coma flotante, vea por ejemplo

#include <iostream> #include <cmath> int main () { double d = -0.0; double a = d < 0 ? -d : d; std::cout << d << '' '' << a << '' '' << std::abs(d); }

salida:

-0 -0 0

Dado que -0.0 y 0.0 representan el mismo número real ''0'', esta diferencia puede o no importar, dependiendo de cómo se use el resultado. Sin embargo, la función abs especificada por IEEE754 exige que el signo del resultado sea 0, lo que prohibiría el resultado -0.0 . Personalmente, creo que cualquier cosa utilizada para calcular algún "valor absoluto" debería coincidir con este comportamiento.

Para enteros, ambas variantes serán equivalentes tanto en tiempo de ejecución como en comportamiento. ( Ejemplo en vivo )

Pero como se sabe que std::abs (o los equivalentes C adecuados) son correctos y más fáciles de leer, siempre debe preferirlos.


Puede haber una implementación de bajo nivel más eficiente que una rama condicional, en una arquitectura dada. Por ejemplo, la CPU podría tener una instrucción abs , o una forma de extraer el bit de signo sin la sobrecarga de una rama. Suponiendo que un desplazamiento aritmético a la derecha puede llenar un registro r con -1 si el número es negativo, o 0 si es positivo, abs x podría convertirse en (x+r)^r (y al ver la respuesta de Mats Petersson, g ++ realmente hace esto en x86).

Otras respuestas han explicado la situación del punto flotante IEEE.

Intentar decirle al compilador que realice una ramificación condicional en lugar de confiar en la biblioteca es probablemente una optimización prematura.


Suponiendo que el compilador no podrá determinar que tanto abs () como la negación condicional intentan alcanzar el mismo objetivo, la negación condicional se compila en una instrucción de comparación, una instrucción de salto condicional y una instrucción de movimiento, mientras que abs () tampoco compila a una instrucción de valor absoluto real, en conjuntos de instrucciones que admiten tal cosa, o un bit a bit y que sigue todo igual, excepto el bit de signo. Cada instrucción anterior es típicamente de 1 ciclo, por lo que es probable que el uso de abs () sea al menos tan rápido o más rápido que la negación condicional (ya que el compilador aún puede reconocer que está intentando calcular un valor absoluto cuando usa la negación condicional, y generar una instrucción de valor absoluto de todos modos). Incluso si no hay cambios en el código compilado, abs () es aún más legible que la negación condicional.


Tenga en cuenta que podría alimentar una expresión complicada en abs() . Si lo codifica con expr > 0 ? expr : -expr expr > 0 ? expr : -expr , debe repetir la expresión completa tres veces, y se evaluará dos veces.
Además, los dos resultados (antes y después de los dos puntos) pueden ser de diferentes tipos (como signed int / unsigned int ), lo que desactiva el uso en una declaración de devolución. Por supuesto, podría agregar una variable temporal, pero eso resuelve solo partes de ella, y tampoco es mejor de ninguna manera.