web-dev-qa-db-fra.com

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

Je veux une fonction qui retourne -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 cela semble être quelque chose qui devrait être dans une bibliothèque standard quelque part.

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

377
batty

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

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

Avantages:

  • Implémente réellement le signum (-1, 0 ou 1). Les implémentations utilisant ici copysign ne renvoient que -1 ou 1, ce qui n’est pas signum. De plus, certaines implémentations ici renvoient un float (ou T) plutôt qu'un int, ce qui semble inutile.
  • Fonctionne pour les ints, les floats, les doubles, les shorts non signés ou tout type personnalisé pouvant être construit à partir de l'entier 0 et pouvant être commandé.
  • Vite! copysign est lent, surtout si vous devez promouvoir et resserrer à nouveau. Ceci est sans branche et optimise parfaitement
  • Conforme aux normes! Le bidouillage de 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 sous forme de spécialisation manuelle, le cas échéant.
  • Précis! De simples comparaisons avec zéro permettent de conserver la représentation haute précision interne de la machine (par exemple, 80 bits sur x87) et d'éviter un arrondi prématuré à zéro.

Mises en garde:

  • C'est un modèle, donc la compilation peut prendre plus de temps 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 partie < 0 de la vérification déclenche l'avertissement -Wtype-limits de GCC lorsqu'elle est instanciée pour un type non signé. Vous pouvez éviter cela en utilisant des 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.)

474
user79758

Je ne connais pas de fonction standard pour cela. Voici un moyen intéressant de l'écrire:

(x > 0) - (x < 0)

Voici un moyen 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)
261
Mark Byers

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, en fonction du signe de la valeur. Notez que les zéros en virgule flottante sont signés: (+0) donnera +1 et (-0) donnera -1.

183
comingstorm

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

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

Pas dans la bibliothèque standard, cependant il y a copysign qui peut être utilisé presque de la même manière via copysign(1.0, arg) et il existe une vraie fonction de signe dans boost , ce qui pourrait 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

74
Catskul

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

74
John

Plus rapide que les solutions ci-dessus, y compris la solution la mieux notée:

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

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 macro signbit() dans <math.h>

int signbit (variable réelle x);
La macro signbit renvoie une valeur différente de zéro 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 retourne -1 pour les nombres négatifs et +1 pour les nombres positifs. ... une fonction travaillant sur des flotteurs.

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

Plus profond:

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

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

Beaucoup de réponses ont déjà couvert cela, mais n'abordent pas x = -0.0, +NaN, -NaN. Beaucoup sont conçus pour un point de vue entier qui manque généralement de Not-a-Numbers ( NaN ) et - 0. .

Les réponses typiques fonctionnent comme signnum_typical(). Sur -0.0, +NaN, -NaN, elles renvoient 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, proposez cette fonctionnalité: Sur -0.0, +NaN, -NaN, il retourne -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;
}
23
chux

Il y a moyen de le faire sans créer de branche, 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 ...

16
Tim Sylvester

Si tout ce que vous voulez, c'est tester le signe, utilisez signbit (retourne vrai si son argument a un signe négatif). Vous ne savez pas pourquoi vous voudriez particulièrement que -1 ou +1 soit retourné; copysign est plus pratique pour cela, mais il semblerait que cela renvoie +1 pour le zéro négatif sur certaines plates-formes ne prenant en charge que partiellement le zéro négatif, où signbit renverrait vraisemblablement.

11
ysth

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

En dehors de cela, je crois 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 réalité un non-argument une fois que vous prenez en compte deux mises en garde importantes:

  • Une fonction signum doit toujours renvoyer le type de son opérande, de la même manière qu'une fonction abs(), car signum est généralement utilisé pour la multiplication avec une valeur absolue après celle-ci. a été traité en quelque sorte. Par conséquent, le principal cas d'utilisation de signum n'est pas une comparaison, mais une arithmétique, cette dernière ne devant impliquer aucune conversion coûteuse en nombres entiers/en points flottants.

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

En ce qui concerne C, je pense que le meilleur moyen d’aller de l’avant pour les types intégraux consiste à utiliser l’expression (x > 0) - (x < 0), car elle devrait être traduite sans branches 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 _Generic pour 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, tout simplement parce qu'elles sont également très susceptibles de ne pas avoir de branches, De plus, il n'est pas nécessaire de convertir le résultat d'un entier en une valeur à virgule flottante. Vous devriez probablement dire clairement que vos implémentations en virgule flottante de signum ne renverront pas zéro en raison des particularités des valeurs nulles en virgule flottante, des considérations de temps de traitement, mais aussi parce que cela est souvent très utile en arithmétique en virgule flottante. recevoir le bon signe -1/+ 1, même pour des valeurs nulles.

5
Tabernakel

Ma copie de C in a Nutshell 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?

4

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 x) {
    return T(0) < x;  
}

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

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

3
SamVanDonut

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 ) )
3
chattering

Un 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 depuis "standard" sgn (), car elle est le plus souvent utilisée dans le code suivant:

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

vs.

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

il n'y a pas de transtypage pour les types non signés et pas de désavantage 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
Nick

Voici une implémentation conviviale pour les branches:

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 la moitié des chiffres ne soit égal à zéro dans vos données, le prédicteur de branche choisira l'une des branches comme étant la plus courante. Les deux branches ne comportent que des opérations simples.

Alternativement, sur certains compilateurs et architectures de CPU, une version complètement autonome 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 format à virgule flottante binaire double précision IEEE 754: binary64 .

0
Serge Rogatch

Bien que la solution entière dans la réponse acceptée soit assez élégante, cela me gênait de ne pas pouvoir renvoyer NAN pour les types doubles, aussi 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 renvoi d'un NAN à virgule flottante, par opposition à un NAN codé en dur, entraîne la définition du bit de signe dans quelques implémentations , donc la sortie pour val = -NAN et val = NAN vont être identiques quoi qu'il en soit (si vous préférez une sortie "nan" sur un -nan, vous pouvez mettre un abs(val) avant le retour ...)

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

Cette fonction suppose:

  • binary32 représentation des nombres en virgule flottante
  • un compilateur qui crée une règle exception concernant le crénelage strict lors de l'utilisation de l'union nommée
0
Gigi

Vous pouvez utiliser la méthode boost::math::sign() à partir de boost/math/special_functions/sign.hpp si le boost est disponible.

0
khkarens