Existe-t-il une fonction de signe standard (signum, sgn) en C / C ++?


409

Je veux une fonction qui renvoie -1 pour les nombres négatifs et +1 pour les nombres positifs. http://en.wikipedia.org/wiki/Sign_function Il est assez facile d'écrire le mien, mais il semble que quelque chose devrait se trouver quelque part dans une bibliothèque standard.

Edit: Plus précisément, je cherchais une fonction travaillant sur les flotteurs.


13
Que doit-il retourner pour 0?
Craig McQueen

61
@Craig McQueen; cela dépend si c'est un zéro positif ou un zéro négatif.
ysth

1
J'ai remarqué que vous avez spécifié la valeur de retour sous forme d'entier. Êtes-vous à la recherche d'une solution qui accepte des nombres entiers ou à virgule flottante?
Mark Byers

6
@ysth @Craig McQueen, faux pour les flotteurs aussi, non? La définition de sgn (x) indique de retourner 0 si x==0. Selon IEEE 754 , le zéro négatif et le zéro positif devraient se comparer comme égaux.
RJFalconer

5
@ysth "cela dépend du zéro positif ou du zéro négatif". En fait, ce n'est pas le cas.
RJFalconer

Réponses:


506

Surpris, personne n'a encore publié la version C ++ de type sécurisé:

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

Avantages:

  • Implémente en fait le signum (-1, 0 ou 1). Les implémentations ici utilisant copysign ne renvoient que -1 ou 1, ce qui n'est pas le signe. De plus, certaines implémentations renvoient ici un float (ou T) plutôt qu'un int, ce qui semble inutile.
  • Fonctionne pour les entiers, les flottants, les doubles, les shorts non signés ou tout type personnalisé pouvant être construit à partir de l'entier 0 et pouvant être commandé.
  • Vite! copysignest lent, surtout si vous devez promouvoir puis rétrécir à nouveau. Ceci est sans branche et optimise parfaitement
  • Conforme aux normes! Le hack bitshift est soigné, mais ne fonctionne que pour certaines représentations de bits, et ne fonctionne pas lorsque vous avez un type non signé. Il pourrait être fourni en tant que spécialisation manuelle le cas échéant.
  • Précis! Des comparaisons simples avec zéro peuvent maintenir la représentation interne de haute précision de la machine (par exemple 80 bits sur x87) et éviter un arrondi prématuré à zéro.

Mises en garde:

  • C'est un modèle, il peut donc prendre plus de temps à compiler dans certaines circonstances.
  • Apparemment, certaines personnes pensent que l'utilisation d'une nouvelle fonction de bibliothèque standard quelque peu ésotérique et très lente qui n'implémente même pas vraiment signum est plus compréhensible.
  • La < 0partie de la vérification déclenche l' -Wtype-limitsavertissement de GCC lorsqu'il est instancié pour un type non signé. Vous pouvez éviter cela en utilisant certaines surcharges:

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

    (Ce qui est un bon exemple de la première mise en garde.)


18
@GMan: GCC a récemment (4.5) cessé d'avoir un coût quadratique par rapport au nombre d'instanciations pour les fonctions de modèle, et elles sont toujours considérablement plus chères à analyser et à instancier que les fonctions écrites manuellement ou le préprocesseur C standard. L'éditeur de liens doit également faire plus de travail pour supprimer les instanciations en double. Les modèles encouragent également # includes-in # includes, ce qui rend le calcul des dépendances plus long et de petites modifications (souvent l'implémentation, pas l'interface) pour forcer la recompilation de plus de fichiers.

15
@Joe: Oui, et il n'y a toujours pas de coût perceptible. C ++ utilise des modèles, c'est juste quelque chose que nous devons tous comprendre, accepter et surmonter.
GManNickG

42
Attendez, qu'est-ce que c'est que ce "copysign est lent" ...? L'utilisation de compilateurs actuels (g ++ 4.6+, clang ++ 3.0), std::copysignsemble me donner un excellent code: 4 instructions (en ligne), sans branchement, utilisant entièrement le FPU. La recette donnée dans cette réponse, en revanche, génère un code bien pire (beaucoup plus d'instructions, y compris une multiplication, un va-et-vient entre l'unité entière et le FPU) ...
snogglethorpe

14
@snogglethorpe: Si vous appelez copysignun int, il promeut de flotter / doubler et doit se resserrer à nouveau. Votre compilateur peut optimiser cette promotion, mais je ne trouve rien suggérant que cela soit garanti par la norme. De plus, pour implémenter signum via copysign, vous devez gérer manuellement le cas 0 - assurez-vous de l'inclure dans toute comparaison de performances.

53
La première version n'est pas sans branche. Pourquoi les gens pensent-ils qu'une comparaison utilisée dans une expression ne générera pas de branche? Il le sera sur la plupart des architectures. Seuls les processeurs qui ont un cmove (ou une prédication) généreront du code sans branche, mais ils le feront également pour les ternaires ou si / sinon si c'est une victoire.
Patrick Schlüter

271

Je ne connais pas de fonction standard pour cela. Voici une façon intéressante de l'écrire:

(x > 0) - (x < 0)

Voici une façon plus lisible de le faire:

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

Si vous aimez l'opérateur ternaire, vous pouvez le faire:

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

7
Mark Ransom, vos expressions donnent des résultats incorrects pour x==0.
avakar

3
@Svante: "Chacun des opérateurs <, >... doit donner 1 si la relation spécifiée est vraie et 0 si elle est fausse"
Stephen Canon

11
@Svante: pas exactement. Une valeur de 0est "false"; toute autre valeur est "vraie"; cependant, les opérateurs relationnels et d'égalité renvoient toujours 0ou 1(voir Normes 6.5.8 et 6.5.9). - la valeur de l'expression a * (x == 42)est soit 0ou a.
pmg

21
Marque hautes performances, je suis étonné que vous ayez raté la balise C ++. Cette réponse est tout à fait valable et ne mérite pas un vote négatif. De plus, je n'utiliserais pas l' copysignintégrale xmême si je l'avais disponible.
avakar

6
Quelqu'un a-t-il réellement vérifié quel code GCC / G ++ / tout autre compilateur émet sur une plate-forme réelle? Je suppose que la version "sans branche" utilise deux branches au lieu d'une. Le Bitshifting est probablement beaucoup plus rapide - et plus portable en termes de performances.
Jørgen Fogh

192

Il existe une fonction de bibliothèque mathématique C99 appelée copysign (), qui prend le signe d'un argument et la valeur absolue de l'autre:

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

vous donnera un résultat de +/- 1.0, selon le signe de la valeur. Notez que les zéros à virgule flottante sont signés: (+0) donnera +1 et (-0) donnera -1.


57
A voté pour celle-ci, a voté pour la réponse la plus populaire. Laissé stupéfait que la communauté SO semble préférer un hack à l'utilisation d'une fonction de bibliothèque standard. Que les dieux de la programmation vous condamnent tous à essayer de déchiffrer les hacks utilisés par des programmeurs intelligents peu familiarisés avec les normes de langage. Ouais, je sais que ça va me coûter une tonne de répétition sur SO, mais je préfère être du côté de la tempête que le reste d'entre vous ...
High Performance Mark

34
C'est proche, mais cela donne la mauvaise réponse pour zéro (selon l'article Wikipedia dans la question au moins). Belle suggestion cependant. +1 de toute façon.
Mark Byers

4
Si vous voulez un entier, ou si vous voulez le résultat exact pour les zéros, j'aime la réponse de Mark Byers, qui est d'une élégance diabolique! Si vous ne vous souciez pas de ce qui précède, copysign () peut avoir un avantage en termes de performances, selon l'application - si j'optimisais une boucle critique, j'essaierais les deux.
comingstorm

10
1) C99 n'est pas entièrement pris en charge partout (pensez à VC ++); 2) c'est aussi une question C ++. C'est une bonne réponse, mais celle qui a été votée fonctionne également et est plus largement applicable.
Pavel Minaev

5
Sauveur! Besoin d'un moyen de déterminer entre -0,0 et 0,0
Ólafur Waage

79

Il semble que la plupart des réponses aient manqué la question initiale.

Existe-t-il une fonction de signe standard (signum, sgn) en C / C ++?

Pas dans la bibliothèque standard, mais il en existe copysignqui peuvent être utilisés presque de la même manière via copysign(1.0, arg)et il existe une véritable fonction de signe dans boost, qui pourrait tout aussi bien faire partie de la norme.

    #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


5
Cela devrait être la réponse la plus votée, car elle donne la solution la plus proche possible de ce qui est demandé dans la question.
BartoszKP

Je me demande depuis quelques minutes pourquoi la bibliothèque standard n'a pas de fonction de signe. C'est tellement commun - certainement plus couramment utilisé que la fonction gamma qui pourrait être trouvée dans l'en-tête cmath.
Taozi

4
L'explication que j'obtiens souvent pour des questions similaires est "il est assez facile de vous implémenter". Quelle OMI n'est pas une bonne raison. Cela dément complètement les problèmes de normalisation, de cas de bord non évidents et où mettre un outil aussi largement utilisé.
Catskul

77

Apparemment, la réponse à la question de l'affiche originale est non. Il n'y a pas de fonction C ++ standardsgn .


2
@SR_ Vous n'avez pas raison. copysign()ne fera pas votre premier paramètre 0.0 si le second est 0.0. En d'autres termes, John a raison.
Alexis Wilke

30

Existe-t-il une fonction de signe standard (signum, sgn) en C / C ++?

Oui, selon la définition.

C99 et versions ultérieures ont la signbit()macro dans<math.h>

int signbit(flottant réel x);
La signbitmacro renvoie une valeur non nulle si et seulement si le signe de sa valeur d'argument est négatif. C11 §7.12.3.6


Pourtant, OP veut quelque chose d'un peu différent.

Je veux une fonction qui renvoie -1 pour les nombres négatifs et +1 pour les nombres positifs. ... une fonction travaillant sur les flotteurs.

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

Plus profond:

Le poste n'est pas spécifique dans les cas suivants: x = 0.0, -0.0, +NaN, -NaN.

Un classique des signum()retours +1sur x>0, -1sur x<0et 0sur x==0.

De nombreuses réponses ont déjà couvert ce point, mais n'en traitent pas x = -0.0, +NaN, -NaN. Beaucoup sont adaptés à un point de vue entier qui manque généralement de Not-a-Numbers ( NaN ) et -0.0 .

Les réponses typiques fonctionnent comme signnum_typical() On -0.0, +NaN, -NaN, elles reviennent 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;
}

Au lieu de cela, je propose cette fonctionnalité: Oui -0.0, +NaN, -NaN, elle revient -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, exactement ce que je recherche. Cela vient de changer dans Pharo Smalltalk github.com/pharo-project/pharo/pull/1835 et je me demandais s'il y avait une sorte de norme (IEC 60559 ou ISO 10967) dictant le comportement pour un comportement négatif zéro et nan ... J'aime le signe javascript developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
aka.nice

29

Plus rapide que les solutions ci-dessus, y compris la meilleure note:

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

1
De quel type est x? Ou utilisez-vous un #define?
Chance

3
Votre type n'est pas plus rapide. Cela entraînera un échec du cache assez souvent.
Jeffrey Drake

19
Cache raté? Je ne sais pas comment. Peut-être que vous vouliez dire une mauvaise interprétation de la branche?
Catskul

2
Il me semble que cela se traduira par un avertissement de confusion entre les types entier et booléen!
sergiol

comment ça va être rapide avec la branche?
Nick

16

Il y a un moyen de le faire sans ramification, mais ce n'est pas très joli.

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

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

Beaucoup d'autres choses intéressantes et trop intelligentes sur cette page aussi ...


1
Si j'ai lu correctement le lien qui ne renvoie que -1 ou 0. Si vous voulez -1, 0 ou +1, alors c'est sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));ou sign = (v > 0) - (v < 0);.
Z boson

1
cela implique que vc'est un type entier pas plus large que int
phuclv

12

Si tout ce que vous voulez est de tester le signe, utilisez signbit (retourne vrai si son argument a un signe négatif). Je ne sais pas pourquoi vous voudriez particulièrement que -1 ou +1 soit retourné; copysign est plus pratique pour cela, mais il semble qu'il renverra +1 pour zéro négatif sur certaines plates-formes avec seulement un support partiel pour zéro négatif, où signbit retournerait vraisemblablement vrai.


7
Il existe de nombreuses applications mathématiques dans lesquelles le signe (x) est nécessaire. Sinon, je ferais juste if (x < 0).
Chance

5

En général, il n'y a pas de fonction de signalisation standard en C / C ++, et l'absence d'une telle fonction fondamentale en dit long sur ces langages.

En dehors de cela, je pense que les deux points de vue majoritaires sur la bonne approche pour définir une telle fonction sont en quelque sorte corrects, et la "controverse" à ce sujet est en fait un non-argument une fois que vous prenez en compte deux mises en garde importantes:

  • Une fonction signum doit toujours retourner le type de son opérande, de la même manière qu'une abs()fonction, car signum est généralement utilisé pour la multiplication avec une valeur absolue après que cette dernière a été traitée d'une manière ou d'une autre. Par conséquent, le cas d'utilisation majeur de signum n'est pas les comparaisons mais l'arithmétique, et cette dernière ne devrait pas impliquer de conversions entières en virgule flottante coûteuses.

  • Les types à virgule flottante ne comportent pas une seule valeur zéro exacte: +0,0 peut être interprété comme "infinitésimalement supérieur à zéro" et -0,0 comme "infinitésimalement inférieur à zéro". C'est la raison pour laquelle les comparaisons impliquant zéro doivent vérifier en interne par rapport aux deux valeurs, et une expression comme x == 0.0peut être dangereuse.

En ce qui concerne C, je pense que la meilleure façon de progresser avec les types intégraux est en effet d'utiliser l' (x > 0) - (x < 0)expression, car elle doit être traduite sans branchement et ne nécessite que trois opérations de base. Définissez au mieux les fonctions en ligne qui appliquent un type de retour correspondant au type d'argument et ajoutez un C11 define _Genericpour mapper ces fonctions à un nom commun.

Avec les valeurs à virgule flottante, je pense que les fonctions en ligne basées sur C11 copysignf(1.0f, x), copysign(1.0, x)et copysignl(1.0l, x)sont la voie à suivre, simplement parce qu'elles sont également très susceptibles d'être sans branche, et en outre ne nécessitent pas de transtyper le résultat de l'entier en virgule flottante valeur. Vous devriez probablement indiquer clairement que vos implémentations en virgule flottante de signum ne retourneront pas zéro en raison des particularités des valeurs nulles en virgule flottante, des considérations de temps de traitement, et aussi parce qu'il est souvent très utile dans l'arithmétique en virgule flottante de recevoir le bon -1 / + 1 signe, même pour des valeurs nulles.


5

Ma copie de C en bref révèle l'existence d'une fonction standard appelée copysign qui pourrait être utile. Il semble que copysign (1.0, -2.0) renvoie -1.0 et copysign (1.0, 2.0) renvoie +1.0.

Assez proche hein?


Pas standard, mais peut être largement disponible. Microsoft commence par un trait de soulignement, qui est la convention qu'ils utilisent pour les extensions non standard. Cependant, ce n'est pas le meilleur choix lorsque vous travaillez avec des entiers.
Mark Ransom

5
copysign est à la fois dans les normes ISO C (C99) et POSIX. Voir opengroup.org/onlinepubs/000095399/functions/copysign.html
lhf

3
Ce que lhf a dit. Visual Studio n'est pas une référence pour la norme C.
Stephen Canon

3

Non, il n'existe pas en c ++, comme dans matlab. J'utilise une macro dans mes programmes pour cela.

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

5
Il faut préférer les modèles aux macros en C ++.
Ruslan

En C, il n'y a pas de modèle ...... helloacm.com/how-to-implement-the-sgn-function-in-c
doctorlai

J'ai pensé que c'était une bonne réponse, puis j'ai regardé mon propre code et j'ai trouvé ceci: ce #define sign(x) (((x) > 0) - ((x) < 0))qui est bien aussi.
Michel Rouzic

1
une fonction en ligne est meilleure qu'une macro en C, et en modèle C ++ est meilleure
phuclv

3

La réponse acceptée avec la surcharge ci-dessous ne déclenche en effet pas -Wtype-limits .

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

Pour C ++ 11, une alternative pourrait être.

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

Pour moi, cela ne déclenche aucun avertissement sur GCC 5.3.1.


Pour éviter l' -Wunused-parameteravertissement, utilisez simplement des paramètres sans nom.
Jonathan Wakely

C'est en fait très vrai. J'ai manqué ça. Cependant, j'aime plus l'alternative C ++ 11 de toute façon.
SamVanDonut

2

Peu hors sujet, mais j'utilise ceci:

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

et j'ai trouvé la première fonction - celle avec deux arguments, beaucoup plus utile dans sgn () "standard", car elle est le plus souvent utilisée dans du code comme celui-ci:

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

contre.

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

il n'y a pas de transtypage pour les types non signés et aucun inconvénient supplémentaire.

en fait, j'ai ce morceau de code en utilisant 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 question est ancienne mais il existe désormais ce type de fonction souhaitée. J'ai ajouté un wrapper avec not, shift gauche et dec.

Vous pouvez utiliser une fonction wrapper basée sur signbit de C99 afin d'obtenir le comportement souhaité exact (voir le code ci-dessous).

Renvoie si le signe de x est négatif.
Cela peut également être appliqué aux infinis, aux NaN et aux zéros (si zéro n'est pas signé, il est considéré comme positif

#include <math.h>

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

NB: J'utilise l'opérande non ("!") Car la valeur de retour de signbit n'est pas spécifiée à 1 (même si les exemples laissent penser que ce serait toujours ainsi) mais vrai pour un nombre négatif:

Valeur de retour
Une valeur non nulle (vraie) si le signe de x est négatif; et zéro (faux) sinon.

Ensuite, je multiplie par deux avec le décalage vers la gauche ("<< 1") qui nous donnera 2 pour un nombre positif et 0 pour un nombre négatif et enfin décrémenter de 1 pour obtenir 1 et -1 pour les nombres respectivement positifs et négatifs comme demandé par OP.


0 sera positif alors aussi ... ce qui pourrait ou non être ce que OP voulait ...
Antti Haapala

eh bien nous ne saurons jamais ce que OP voulait vraiment si n = 0 ...!
Antonin GAVREL

0

Bien que la solution entière dans la réponse acceptée soit assez élégante, cela m'a dérangé de ne pas pouvoir renvoyer NAN pour les types doubles, donc je l'ai légèrement modifiée.

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

Notez que le retour à virgule flottante NAN par opposition à une pré - programmée NANcause le bit de signe à régler dans certaines implémentations , de sorte que la sortie pour val = -NANet val = NANvont être identiques , peu importe ce que (si vous préférez une « nan» sortie sur une , -nanvous pouvez mettre un abs(val)avant le retour ...)



0

Voici une implémentation adaptée aux branchements:

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

À moins que vos données aient des zéros comme la moitié des nombres, ici le prédicteur de branche choisira l'une des branches comme la plus courante. Les deux branches impliquent uniquement des opérations simples.

Alternativement, sur certains compilateurs et architectures CPU, une version complètement sans branche peut être plus rapide:

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

Cela fonctionne pour le format à virgule flottante binaire double précision IEEE 754: binaire64 .


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

Cette fonction suppose:

  • représentation binaire32 des nombres à virgule flottante
  • un compilateur qui crée une exception concernant la règle d' alias stricte lors de l'utilisation d'une union nommée

3
Il y a encore de mauvaises hypothèses ici. Par exemple, je ne crois pas que l'endianité du flotteur soit garantie comme l'endianité de l'entier. Votre vérification échoue également sur toutes les architectures utilisant ILP64. Vraiment, vous ne faites que réimplémenter copysign; si vous utilisez, static_assertvous avez C ++ 11, et pourriez tout aussi bien l'utiliser copysign.

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

-3

Pourquoi utiliser des opérateurs ternaires et if-else quand vous pouvez simplement le faire

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

3
Votre définition utilise également un opérateur ternaire.
Martin R

Oui, certainement, mais il utilise juste un opérateur ternaire pour séparer les nombres zéro et non nul. D'autres versions incluent des opérations ternaires imbriquées pour séparer le positif, le négatif et le zéro.
Jagreet

L'utilisation d'une division entière est très inefficace et abs () ne concerne que les entiers.
Michel Rouzic

Comportement indéfini possible quand x == INT_MIN.
chux
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.