¿Existe una función de signo estándar (signum, sgn) en C / C ++?


409

Quiero una función que devuelva -1 para números negativos y +1 para números positivos. http://en.wikipedia.org/wiki/Sign_function Es bastante fácil escribir el mío, pero parece algo que debería estar en una biblioteca estándar en alguna parte.

Editar: Específicamente, estaba buscando una función trabajando en flotantes.


13
¿Qué debería devolver por 0?
Craig McQueen

61
@Craig McQueen; eso depende de si es un cero positivo o un cero negativo.
ysth

1
Noté que especificaste el valor de retorno como un entero. ¿Estás buscando una solución que tome números enteros o números de punto flotante?
Mark Byers

66
@ysth @Craig McQueen, falso también para carrozas, ¿no? La definición de sgn (x) dice que devuelva 0 si x==0. Según IEEE 754 , el cero negativo y el cero positivo deberían compararse como iguales.
RJFalconer

55
@ysth "depende de cero positivo o cero negativo". De hecho, no.
RJFalconer

Respuestas:


506

Sorprendido, nadie ha publicado la versión C ++ segura de tipos todavía:

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

Beneficios:

  • Realmente implementa signum (-1, 0 o 1). Las implementaciones aquí que usan copysign solo devuelven -1 o 1, lo que no es signum. Además, algunas implementaciones aquí están devolviendo un flotante (o T) en lugar de un int, lo que parece un desperdicio.
  • Funciona para ints, flotantes, dobles, cortos sin signo o cualquier tipo personalizado que se pueda construir desde el entero 0 y que se pueda pedir.
  • ¡Rápido! copysignes lento, especialmente si necesita promocionar y luego volver a reducir. Esto no tiene ramificaciones y se optimiza excelentemente
  • ¡Cumple con los estándares! El truco de BitShift está ordenado, pero solo funciona para algunas representaciones de bits, y no funciona cuando tiene un tipo sin signo. Podría proporcionarse como una especialización manual cuando sea apropiado.
  • ¡Preciso! Las comparaciones simples con cero pueden mantener la representación interna de alta precisión de la máquina (por ejemplo, 80 bits en x87) y evitar un redondeo prematuro a cero.

Advertencias:

  • Es una plantilla, por lo que puede tomar más tiempo compilar en algunas circunstancias.
  • Aparentemente, algunas personas piensan que el uso de una función de biblioteca estándar nueva, algo esotérica y muy lenta que ni siquiera implementa realmente signum es más comprensible.
  • La < 0parte de la verificación activa la -Wtype-limitsadvertencia de GCC cuando se instancia para un tipo sin signo. Puede evitar esto usando algunas sobrecargas:

    template <typename T> inline constexpr
    int signum(T x, std::false_type is_signed) {
        return T(0) < x;
    }
    
    template <typename T> inline constexpr
    int signum(T x, std::true_type is_signed) {
        return (T(0) < x) - (x < T(0));
    }
    
    template <typename T> inline constexpr
    int signum(T x) {
        return signum(x, std::is_signed<T>());
    }

    (Lo cual es un buen ejemplo de la primera advertencia).


18
@GMan: GCC recién ahora (4.5) dejó de tener un costo cuadrático para el número de instancias para funciones de plantilla, y todavía son drásticamente más caras de analizar e instanciar que las funciones escritas manualmente o el preprocesador C estándar. El vinculador también tiene que hacer más trabajo para eliminar las instancias duplicadas. Las plantillas también fomentan # incluye-en # incluye, lo que hace que el cálculo de dependencia tome más tiempo y pequeños cambios (a menudo de implementación, no de interfaz) para forzar la recompilación de más archivos.

15
@ Joe: Sí, y todavía no hay un costo notable. C ++ usa plantillas, eso es algo que todos tenemos que entender, aceptar y superar.
GManNickG

42
Espera, ¿qué es este negocio "Copysign es lento" ...? El uso de compiladores actuales (g ++ 4.6+, clang ++ 3.0) std::copysignparece dar como resultado un código excelente para mí: 4 instrucciones (en línea), sin ramificación, usando completamente la FPU. La receta dada en esta respuesta, por el contrario, genera un código mucho peor (muchas más instrucciones, incluida una multiplicación, moviéndose hacia adelante y hacia atrás entre la unidad entera y la FPU) ...
snogglethorpe

14
@snogglethorpe: si está llamando copysigna un int, promueve flotar / doble, y debe reducirse nuevamente al regresar. Su compilador puede optimizar esa promoción, pero no puedo encontrar nada que sugiera que está garantizado por el estándar. Además, para implementar signum a través de copysign, debe manejar manualmente el caso 0; asegúrese de incluirlo en cualquier comparación de rendimiento.

53
La primera versión no es sin ramas. ¿Por qué la gente piensa que una comparación utilizada en una expresión no generará una rama? Lo hará en la mayoría de las arquitecturas. Solo los procesadores que tienen un cmove (o predication) generarán código sin ramificación, pero lo harán también para ternaries o si / si es una victoria.
Patrick Schlüter

271

No sé de una función estándar para ello. Aquí hay una forma interesante de escribirlo:

(x > 0) - (x < 0)

Aquí hay una forma más legible de hacerlo:

if (x > 0) return 1;
if (x < 0) return -1;
return 0;

Si le gusta el operador ternario, puede hacer esto:

(x > 0) ? 1 : ((x < 0) ? -1 : 0)

77
Mark Ransom, sus expresiones dan resultados incorrectos para x==0.
avakar

3
@Svante: "Cada uno de los operadores <, >... dará 1 si la relación especificada es verdadera y 0 si es falsa"
Stephen Canon

11
@Svante: no exactamente. Un valor de 0es "falso"; cualquier otro valor es "verdadero"; sin embargo, los operadores relacionales y de igualdad siempre regresan 0o 1(ver Estándar 6.5.8 y 6.5.9). - el valor de la expresión a * (x == 42)es 0o a.
pmg

21
Marca de alto rendimiento, me sorprende que te hayas perdido la etiqueta C ++. Esta respuesta es muy válida y no merece un voto negativo. Además, no lo usaría copysignpara integral xincluso si lo tuviera disponible.
avakar

66
¿Alguien ha verificado realmente qué código GCC / G ++ / cualquier otro compilador emite en una plataforma real? Supongo que la versión "sin ramas" usa dos ramas en lugar de una. Bitshifting es probablemente mucho más rápido y más portátil en términos de rendimiento.
Jørgen Fogh

192

Hay una función de biblioteca matemática C99 llamada copysign (), que toma el signo de un argumento y el valor absoluto del otro:

result = copysign(1.0, value) // double
result = copysignf(1.0, value) // float
result = copysignl(1.0, value) // long double

le dará un resultado de +/- 1.0, dependiendo del signo de valor. Tenga en cuenta que los ceros en coma flotante están firmados: (+0) producirá +1 y (-0) producirá -1.


57
Votó esta respuesta, la respuesta más popular. Se sorprendió al ver que la comunidad SO parece preferir un truco para usar una función de biblioteca estándar. Que los dioses de la programación los condenen a todos por tratar de descifrar los hacks utilizados por programadores inteligentes que no están familiarizados con los estándares del lenguaje. Sí, sé que esto me va a costar una tonelada de repetición en SO, pero prefiero ponerme del lado de la tormenta que el resto de ustedes ...
High Performance Mark

34
Esto está cerca, pero da la respuesta incorrecta para cero (al menos según el artículo de Wikipedia en la pregunta). Buena sugerencia sin embargo. +1 de todos modos.
Mark Byers

44
Si quieres un número entero, o si quieres el resultado exacto de la señal para ceros, me gusta la respuesta de Mark Byers, ¡que es endiabladamente elegante! Si no le importa lo anterior, copysign () podría tener una ventaja de rendimiento, dependiendo de la aplicación; si estuviera optimizando un ciclo crítico, probaría ambos.
Próxima tormenta

10
1) C99 no es totalmente compatible en todas partes (considere VC ++); 2) esta también es una pregunta de C ++. Esta es una buena respuesta, pero la votada también funciona y es más ampliamente aplicable.
Pavel Minaev

55
¡Salvador! Se necesitaba una forma de determinar entre -0.0 y 0.0
Ólafur Waage

79

Parece que la mayoría de las respuestas omitieron la pregunta original.

¿Existe una función de signo estándar (signum, sgn) en C / C ++?

No está en la biblioteca estándar, sin embargo, hay una copysignque se puede usar casi de la misma manera copysign(1.0, arg)y hay una verdadera función de signo boost, que también podría ser parte de la norma.

    #include <boost/math/special_functions/sign.hpp>

    //Returns 1 if x > 0, -1 if x < 0, and 0 if x is zero.
    template <class T>
    inline int sign (const T& z);

http://www.boost.org/doc/libs/1_47_0/libs/math/doc/sf_and_dist/html/math_toolkit/utils/sign_functions.html


55
Esta debería ser la respuesta más votada, ya que brinda la solución más cercana posible a lo que se hace en la pregunta.
BartoszKP

Me he estado preguntando durante los últimos minutos por qué la biblioteca estándar no tiene función de signo. Es muy común, definitivamente se usa más comúnmente que la función gamma que se puede encontrar en el encabezado cmath.
Taozi

44
La explicación que a menudo obtengo para preguntas similares es "es bastante fácil de implementar usted mismo", lo cual no es una buena razón para la OMI. Oculta por completo los problemas de dónde está la estandarización, los casos extremos obvios y dónde colocar una herramienta tan ampliamente utilizada.
Catskul

77

Aparentemente, la respuesta a la pregunta del póster original es no. No hay unasgn función estándar de C ++ .


2
@SR_ No estás en lo correcto. copysign()no hará que su primer parámetro sea 0.0 si el segundo es 0.0. En otras palabras, John tiene razón.
Alexis Wilke

30

¿Existe una función de signo estándar (signum, sgn) en C / C ++?

Sí, dependiendo de la definición.

C99 y posterior tiene la signbit()macro en<math.h>

int signbit(flotante real x);
La signbitmacro devuelve un valor distinto de cero si y solo si el signo de su valor de argumento es negativo. C11 §7.12.3.6


Sin embargo, OP quiere algo un poco diferente.

Quiero una función que devuelva -1 para números negativos y +1 para números positivos. ... una función trabajando en flotadores.

#define signbit_p1_or_n1(x)  ((signbit(x) ?  -1 : 1)

Más adentro:

El post no es específica en los siguientes casos: x = 0.0, -0.0, +NaN, -NaN.

Un clásico signum()vuelve+1 sobre x>0, -1en x<0y 0en x==0.

Muchas respuestas ya han cubierto eso, pero no abordan x = -0.0, +NaN, -NaN . Muchos están orientados a un punto de vista entero que generalmente carece de Not-a-Numbers ( NaN ) y -0.0 .

Las respuestas típicas funcionan como signnum_typical() On -0.0, +NaN, -NaN, regresan 0.0, 0.0, 0.0.

int signnum_typical(double x) {
  if (x > 0.0) return 1;
  if (x < 0.0) return -1;
  return 0;
}

En cambio, propongo esta funcionalidad: On -0.0, +NaN, -NaN, vuelve -0.0, +NaN, -NaN.

double signnum_c(double x) {
  if (x > 0.0) return 1.0;
  if (x < 0.0) return -1.0;
  return x;
}

1
Ah, exactamente lo que busco. Esto acaba de cambiar en Pharo Smalltalk github.com/pharo-project/pharo/pull/1835 y me preguntaba si había algún tipo de estándar (IEC 60559 o ISO 10967) que dictara el comportamiento para el comportamiento negativo cero y nan ... Me gusta el javascript sign developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
aka.nice

29

Más rápido que las soluciones anteriores, incluida la mejor valorada:

(x < 0) ? -1 : (x > 0)

1
¿De qué tipo es x? ¿O estás usando un #define?
Oportunidad

3
Tu tipo no es más rápido. Causará un error de caché con bastante frecuencia.
Jeffrey Drake

19
Señorita caché? No estoy seguro de cómo. ¿Quizás quisiste decir una predicción errónea de la rama?
Catskul

2
¡Me parece que esto dará como resultado una advertencia de tipos enteros y booleanos confusos!
sergiol

¿Cómo será esto rápido con la rama?
Nick

16

Hay una manera de hacerlo sin ramificación, pero no es muy bonita.

sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));

http://graphics.stanford.edu/~seander/bithacks.html

Muchas otras cosas interesantes y demasiado inteligentes en esa página, también ...


1
Si leo el enlace correctamente, solo devuelve -1 o 0. Si quieres -1, 0 o +1, entonces es sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));o sign = (v > 0) - (v < 0);.
Z boson

1
esto implica que ves un tipo entero no más ancho que int
phuclv

12

Si todo lo que desea es probar el signo, use signbit (devuelve verdadero si su argumento tiene un signo negativo). No estoy seguro de por qué querría particularmente -1 o +1 devuelto; Copysign es más conveniente para eso, pero parece que devolverá +1 por cero negativo en algunas plataformas con solo soporte parcial para cero negativo, donde presumiblemente el signbit volvería a ser verdadero.


77
Hay muchas aplicaciones matemáticas en las que el signo (x) es necesario. De lo contrario, lo haría if (x < 0).
Chance

5

En general, no existe una función signum estándar en C / C ++, y la falta de una función tan fundamental le dice mucho acerca de estos lenguajes.

Aparte de eso, creo que los dos puntos de vista mayoritarios sobre el enfoque correcto para definir dicha función son correctos, y la "controversia" al respecto no es un argumento una vez que se tienen en cuenta dos advertencias importantes:

  • Una función signum siempre debe devolver el tipo de su operando, de manera similar a una abs()función, porque el signum generalmente se usa para la multiplicación con un valor absoluto después de que este último se haya procesado de alguna manera. Por lo tanto, el principal caso de uso de signum no son las comparaciones, sino la aritmética, y esta última no debería involucrar costosas conversiones de entero a / desde punto flotante.

  • Los tipos de coma flotante no presentan un solo valor cero exacto: +0.0 puede interpretarse como "infinitamente por encima de cero" y -0.0 como "infinitamente por debajo de cero". Esa es la razón por la cual las comparaciones que involucran cero deben verificar internamente contra ambos valores, y una expresión como x == 0.0puede ser peligrosa.

Con respecto a C, creo que la mejor manera de avanzar con los tipos integrales es usar la (x > 0) - (x < 0)expresión, ya que debería traducirse sin ramificaciones y requiere solo tres operaciones básicas. Defina mejor las funciones en línea que imponen un tipo de retorno que coincida con el tipo de argumento y agregue un C11 define _Genericpara asignar estas funciones a un nombre común.

Con los valores de punto flotante, creo que las funciones en línea basadas en C11 copysignf(1.0f, x), copysign(1.0, x)y copysignl(1.0l, x)son el camino a seguir, simplemente porque también es muy probable que estén libres de ramificación y, además, no requieren convertir el resultado del entero en un punto flotante valor. Probablemente debería comentar de manera destacada que sus implementaciones de coma flotante de signum no devolverán cero debido a las peculiaridades de los valores cero de coma flotante, las consideraciones de tiempo de procesamiento y también porque a menudo es muy útil en la aritmética de coma flotante para recibir el -1 / + correcto 1 signo, incluso para valores cero.


5

Mi copia de C in a Nutshell revela la existencia de una función estándar llamada copysign que podría ser útil. Parece que copysign (1.0, -2.0) devolvería -1.0 y copysign (1.0, 2.0) devolvería +1.0.

Bastante cerca ¿eh?


No es estándar, pero puede estar ampliamente disponible. Microsoft comienza con un guión bajo, que es la convención que usan para extensiones no estándar. Sin embargo, no es la mejor opción cuando trabajas con números enteros.
Mark Ransom

55
Copysign está en los estándares ISO C (C99) y POSIX. Ver opengroup.org/onlinepubs/000095399/functions/copysign.html
lhf

3
Lo que dijo. Visual Studio no es una referencia para el estándar C.
Stephen Canon

3

No, no existe en c ++, como en matlab. Yo uso una macro en mis programas para esto.

#define sign(a) ( ( (a) < 0 )  ?  -1   : ( (a) > 0 ) )

55
Uno debería preferir plantillas sobre macros en C ++.
Ruslan


Pensé que esta era una buena respuesta, luego miré mi propio código y encontré esto: #define sign(x) (((x) > 0) - ((x) < 0))que también es bueno.
Michel Rouzic

1
una función en línea es mejor que una macro en C y una plantilla en C ++ es mejor
phuclv

3

La respuesta aceptada con la sobrecarga a continuación de hecho no activa los límites de tipo .

template <typename T> inline constexpr
  int signum(T x, std::false_type) {
  return T(0) < x;
}

template <typename T> inline constexpr
  int signum(T x, std::true_type) {
  return (T(0) < x) - (x < T(0));
}

template <typename T> inline constexpr
  int signum(T x) {
  return signum(x, std::is_signed<T>());
}

Para C ++ 11, una alternativa podría ser.

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, int>::type
inline constexpr signum(T const x) {
    return T(0) < x;  
}

template <typename T>
typename std::enable_if<std::is_signed<T>::value, int>::type
inline constexpr signum(T const x) {
    return (T(0) < x) - (x < T(0));  
}

Para mí, no activa ninguna advertencia en GCC 5.3.1.


Para evitar la -Wunused-parameteradvertencia solo use parámetros sin nombre.
Jonathan Wakely

Eso es realmente muy cierto. Me lo perdí. Sin embargo, me gusta la alternativa C ++ 11 más de cualquier manera.
SamVanDonut

2

Poco fuera de tema, pero yo uso esto:

template<typename T>
constexpr int sgn(const T &a, const T &b) noexcept{
    return (a > b) - (a < b);
}

template<typename T>
constexpr int sgn(const T &a) noexcept{
    return sgn(a, T(0));
}

y encontré que la primera función, la que tiene dos argumentos, es mucho más útil de "estándar" sgn (), porque se usa con mayor frecuencia en código como este:

int comp(unsigned a, unsigned b){
   return sgn( int(a) - int(b) );
}

vs.

int comp(unsigned a, unsigned b){
   return sgn(a, b);
}

no hay conversión para tipos sin signo y sin menos adicional.

de hecho, tengo este código usando sgn ()

template <class T>
int comp(const T &a, const T &b){
    log__("all");
    if (a < b)
        return -1;

    if (a > b)
        return +1;

    return 0;
}

inline int comp(int const a, int const b){
    log__("int");
    return a - b;
}

inline int comp(long int const a, long int const b){
    log__("long");
    return sgn(a, b);
}

1

La pregunta es antigua pero ahora existe este tipo de función deseada. Agregué un contenedor con not, shift izquierdo y dec.

Puede usar una función de contenedor basada en signbit de C99 para obtener el comportamiento exacto deseado (vea el código más abajo).

Devuelve si el signo de x es negativo.
Esto también se puede aplicar a infinitos, NaNs y ceros (si cero no tiene signo, se considera positivo

#include <math.h>

int signValue(float a) {
    return ((!signbit(a)) << 1) - 1;
}

NB: uso el operando no ("!") Porque el valor de retorno de signbit no se especifica como 1 (aunque los ejemplos nos permiten pensar que siempre sería así), pero es cierto para un número negativo:

Valor de retorno
Un valor distinto de cero (verdadero) si el signo de x es negativo; y cero (falso) de lo contrario.

Luego, multiplico por dos con desplazamiento a la izquierda ("<< 1"), lo que nos dará 2 para un número positivo y 0 para uno negativo y finalmente disminuyo por 1 para obtener 1 y -1 para números positivos y negativos respectivamente según lo solicitado por OP.


0 también será positivo ... lo que podría o no ser lo que OP quería ...
Antti Haapala

bueno, tal vez nunca sepamos lo que OP realmente quería si n = 0 ...!
Antonin GAVREL

0

Si bien la solución entera en la respuesta aceptada es bastante elegante, me molestó que no pudiera devolver NAN para tipos dobles, por lo que la modifiqué un poco.

template <typename T> double sgn(T val) {
    return double((T(0) < val) - (val < T(0)))/(val == val);
}

Tenga en cuenta que devolver un NAN de coma flotante en lugar de un código rígido NANhace que el bit de signo se establezca en algunas implementaciones , por lo que el resultado val = -NANy val = NANserá idéntico nanpase lo -nanque pase (si prefiere un " " resultado sobre un un abs(val)antes del regreso ...)



0

Aquí hay una implementación amigable de ramificación:

inline int signum(const double x) {
    if(x == 0) return 0;
    return (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

A menos que sus datos tengan ceros como la mitad de los números, aquí el predictor de rama elegirá una de las ramas como la más común. Ambas ramas solo implican operaciones simples.

Alternativamente, en algunos compiladores y arquitecturas de CPU, una versión completamente sin ramificaciones puede ser más rápida:

inline int signum(const double x) {
    return (x != 0) * 
        (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

Esto funciona para el formato de punto flotante binario de doble precisión IEEE 754: binary64 .


-1
int sign(float n)
{     
  union { float f; std::uint32_t i; } u { n };
  return 1 - ((u.i >> 31) << 1);
}

Esta función asume:

  • binario32representación de números de coma flotante
  • un compilador que hace una excepción sobre la estricta regla de alias cuando se usa una unión con nombre

3
Todavía hay algunas malas suposiciones aquí. Por ejemplo, no creo que el endianness del float esté garantizado como el endianness del entero. Su verificación también falla en cualquier arquitectura que use ILP64. Realmente, solo estás reimplementando copysign; si está usando static_assert, tiene C ++ 11, y bien podría usarlo copysign.

-3
double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }

-3

¿Por qué usar operadores ternarios y if-else cuando simplemente puede hacer esto?

#define sgn(x) x==0 ? 0 : x/abs(x)

3
Su definición también utiliza un operador ternario.
Martin R

Sí Definitivamente, pero solo usa un operador ternario para separar los números cero y no cero. Las versiones de otros incluyen operaciones ternarias anidadas para separar positivo, negativo y cero.
Jagreet

Usar una división de enteros es muy ineficiente y abs () es solo para enteros.
Michel Rouzic

Comportamiento indefinido posible cuando x == INT_MIN.
chux - Restablece a Mónica
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.