Esiste una funzione di segno standard (signum, sgn) in C / C ++?


409

Voglio una funzione che ritorni -1 per i numeri negativi e +1 per i numeri positivi. http://it.wikipedia.org/wiki/Sign_function È abbastanza facile scrivere il mio, ma sembra qualcosa che dovrebbe essere in una libreria standard da qualche parte.

Modifica: In particolare, stavo cercando una funzione che funziona sui float.


13
Cosa dovrebbe restituire per 0?
Craig McQueen,

61
@Craig McQueen; dipende se si tratta di uno zero positivo o zero negativo.
ysth

1
Ho notato che hai specificato il valore restituito come intero. Stai cercando una soluzione che accetta numeri interi o in virgola mobile?
Mark Byers,

6
@ysth @Craig McQueen, falso anche per i galleggianti, no? La definizione di sgn (x) dice di restituire 0 se x==0. Secondo IEEE 754 , lo zero negativo e lo zero positivo dovrebbero comparare come uguali.
RJFalconer,

5
@ysth "dipende da zero positivo o zero negativo". In realtà no.
RJFalconer,

Risposte:


506

Sorpreso nessuno ha ancora pubblicato la versione C ++ sicura dei tipi:

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

Benefici:

  • Implementa effettivamente signum (-1, 0 o 1). Le implementazioni qui usando copysign restituiscono solo -1 o 1, che non è signum. Inoltre, alcune implementazioni qui stanno restituendo un float (o T) anziché un int, che sembra dispendioso.
  • Funziona con ints, float, double, short senza segno o qualsiasi tipo personalizzato costruibile da intero 0 e ordinabile.
  • Veloce! copysignè lento, soprattutto se devi promuovere e poi restringere di nuovo. Questo è senza rami e ottimizza in modo eccellente
  • Conforme agli standard! L'hacks bitshift è pulito, ma funziona solo per alcune rappresentazioni di bit e non funziona quando si dispone di un tipo senza segno. Potrebbe essere fornito come specializzazione manuale quando appropriato.
  • ! accurate Semplici confronti con zero possono mantenere la rappresentazione interna ad alta precisione della macchina (ad es. 80 bit su x87) ed evitare un giro prematuro a zero.

Avvertenze:

  • È un modello quindi potrebbe richiedere più tempo per la compilazione in alcune circostanze.
  • Apparentemente alcune persone pensano di usare una nuova funzione di libreria standard, un po 'esoterica e molto lenta, che non implementa nemmeno davvero signum sia più comprensibile l' significato.
  • La < 0parte del controllo attiva l' -Wtype-limitsavvertimento di GCC quando viene istanziata per un tipo senza segno. Puoi evitarlo usando alcuni sovraccarichi:

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

    (Che è un buon esempio del primo avvertimento.)


18
@GMan: GCC solo ora (4.5) ha smesso di avere un costo quadratico rispetto al numero di istanze per le funzioni modello, e sono ancora drasticamente più costosi da analizzare e creare istanze rispetto alle funzioni scritte manualmente o al preprocessore C standard. Il linker deve anche fare più lavoro per rimuovere le istanze duplicate. I modelli incoraggiano anche # include-in # include, il che richiede tempi di calcolo delle dipendenze più lunghi e piccoli (spesso implementazione, non interfaccia) per forzare la ricompilazione di più file.

15
@Joe: Sì, e non ci sono ancora costi evidenti. C ++ utilizza modelli, è solo qualcosa che tutti dobbiamo capire, accettare e superare.
GManNickG,

42
Aspetta, cos'è questo business "copysign is slow" ...? L'uso degli attuali compilatori (g ++ 4.6+, clang ++ 3.0), mi std::copysignrisulta in un codice eccellente : 4 istruzioni (in linea), nessuna ramificazione, usando interamente la FPU. La ricetta data in questa risposta, al contrario, genera un codice molto peggiore (molte più istruzioni, tra cui un moltiplicazione, spostandosi avanti e indietro tra unità intera e FPU) ...
snogglethorpe

14
@snogglethorpe: se stai chiamando copysignun int, questo promuove a float / double, e deve restringere di nuovo al ritorno. Il tuo compilatore potrebbe ottimizzare quella promozione ma non riesco a trovare nulla che suggerisca che è garantito dallo standard. Inoltre, per implementare signum tramite copysign è necessario gestire manualmente il caso 0 - assicurarsi di includerlo in qualsiasi confronto delle prestazioni.

53
La prima versione non è senza rami. Perché le persone pensano che un confronto usato in un'espressione non genererà un ramo? Sarà sulla maggior parte delle architetture. Solo i processori che hanno un cmove (o una previsione) genereranno un codice senza rami, ma lo faranno anche per i ternari o se / altrimenti se si tratta di una vittoria.
Patrick Schlüter,

271

Non conosco una funzione standard per questo. Ecco un modo interessante per scriverlo però:

(x > 0) - (x < 0)

Ecco un modo più leggibile per farlo:

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

Se ti piace l'operatore ternario puoi farlo:

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

7
Mark Ransom, le tue espressioni danno risultati errati per x==0.
avakar,

3
@Svante: "Ciascuno degli operatori <, >... produrrà 1 se la relazione specificata è vera e 0 se è falsa"
Stephen Canon,

11
@Svante: non esattamente. Un valore di 0è "false"; qualsiasi altro valore è "vero"; tuttavia, gli operatori relazionali e di uguaglianza ritornano sempre 0o 1(vedere Standard 6.5.8 e 6.5.9). - il valore dell'espressione a * (x == 42)è o 0o a.
pmg

21
Mark ad alte prestazioni, sono sorpreso che tu abbia perso il tag C ++. Questa risposta è molto valida e non merita un voto negativo. Inoltre, non userei copysignper integrale xanche se lo avessi disponibile.
avakar,

6
Qualcuno ha effettivamente verificato quale codice GCC / G ++ / qualsiasi altro compilatore emette su una piattaforma reale? La mia ipotesi è che la versione "senza rami" usi due rami invece di uno. Il bitshifting è probabilmente molto più veloce - e più portatile in termini di prestazioni.
Jørgen Fogh,

192

Esiste una funzione di libreria matematica C99 chiamata copysign (), che accetta il segno da un argomento e il valore assoluto dall'altro:

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

ti darà un risultato di +/- 1.0, a seconda del segno di valore. Si noti che gli zero in virgola mobile sono firmati: (+0) produrrà +1 e (-0) produrrà -1.


57
Ha votato a fondo questo, ha declassato la risposta più popolare. Lasciato a bocca aperta lo stupore che la comunità SO sembra preferire un hack per utilizzare una funzione di libreria standard. Possano gli dei della programmazione condannare tutti voi a provare a decifrare gli hack utilizzati da programmatori intelligenti che non hanno familiarità con gli standard linguistici. Sì, so che questo mi costerà un sacco di rappresentante su SO, ma preferirei schierarmi con la tempesta che con il resto di voi ...
High Performance Mark

34
Questo è vicino, ma dà la risposta sbagliata per zero (almeno secondo l'articolo di Wikipedia nella domanda). Bel suggerimento però. +1 comunque.
Mark Byers,

4
Se vuoi un numero intero, o se vuoi il risultato del segno esatto per gli zeri, mi piace la risposta di Mark Byers, che è diabolicamente elegante! Se non ti interessa quanto sopra, copysign () potrebbe avere un vantaggio in termini di prestazioni, a seconda dell'applicazione - se dovessi ottimizzare un ciclo critico, proverei entrambi.
comingstorm

10
1) C99 non è completamente supportato ovunque (considerare VC ++); 2) questa è anche una domanda C ++. Questa è una buona risposta, ma anche quella votata funziona ed è più ampiamente applicabile.
Pavel Minaev il

5
Salvatore! Necessario un modo per determinare tra -0,0 e 0,0
Ólafur Waage

79

Sembra che la maggior parte delle risposte non abbiano risposto alla domanda originale.

Esiste una funzione di segno standard (signum, sgn) in C / C ++?

Non nella libreria standard, tuttavia esiste un copysignmetodo che può essere utilizzato quasi allo stesso modo tramite copysign(1.0, arg)ed è presente una funzione di accesso reale boost, che potrebbe anche far parte dello standard.

    #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
Questa dovrebbe essere la risposta più votata, in quanto offre la soluzione più vicina possibile a ciò che viene posto nella domanda.
BartoszKP,

Mi chiedo da qualche minuto perché la libreria standard non ha la funzione sign. È così comune - sicuramente più comunemente usato della funzione gamma che si trova nell'intestazione cmath.
Taozi

4
La spiegazione che ricevo spesso per domande simili è "è abbastanza facile da implementare" Quale IMO non è una buona ragione. Smentisce completamente i problemi di dove la standardizzazione, casi marginali non visibili e dove mettere uno strumento così ampiamente usato.
Catskul,

77

Apparentemente, la risposta alla domanda del poster originale è no. Non esiste una funzione C ++ standardsgn .


2
@SR_ Non hai ragione. copysign()non renderà il tuo primo parametro 0.0 se il secondo è 0,0. In altre parole, John ha ragione.
Alexis Wilke,

30

Esiste una funzione di segno standard (signum, sgn) in C / C ++?

Sì, a seconda della definizione.

C99 e successive ha la signbit()macro in<math.h>

int signbit(reale x);
La signbitmacro restituisce un valore diverso da zero se e solo se il segno del valore dell'argomento è negativo. C11 §7.12.3.6


Eppure OP vuole qualcosa di leggermente diverso.

Voglio una funzione che ritorni -1 per i numeri negativi e +1 per i numeri positivi. ... una funzione che lavora sui galleggianti.

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

Più profondo:

Il post non è specifico nei seguenti casi: x = 0.0, -0.0, +NaN, -NaN .

Un classico signum()ritorna +1su x>0, -1su x<0e0 avantix==0 .

Molte risposte lo hanno già trattato, ma non si rivolgono x = -0.0, +NaN, -NaN. Molti sono orientati a un punto di vista intero che di solito manca di Not-a-Numbers ( NaN ) e -0.0 .

Le risposte tipiche funzionano come signnum_typical() On -0.0, +NaN, -NaN, ritornano 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;
}

Invece, propongo questa funzionalità: On -0.0, +NaN, -NaN, ritorna -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, esattamente quello che sto cercando. Questo è appena cambiato in Pharo Smalltalk github.com/pharo-project/pharo/pull/1835 e mi chiedevo se esistesse una sorta di standard (IEC 60559 o ISO 10967) che determinava comportamenti negativi per zero e nan ... javascript sign developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
aka.nice

29

Più veloce delle soluzioni di cui sopra, inclusa la più votata:

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

1
Che tipo è x? O stai usando un #define?
Probabilità del

3
Il tuo tipo non è più veloce. Provocherà una mancanza della cache abbastanza spesso.
Jeffrey Drake,

19
Miss cache? Non sono sicuro di come. Forse intendevi un errore di filiale?
Catskul,

2
Mi sembra che ciò provocherà un avvertimento di confusione di tipi interi e booleani!
sergiol,

come sarà veloce con il ramo?
Nick,

16

C'è un modo per farlo senza ramificarsi, ma non è molto carino.

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

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

Molte altre cose interessanti e troppo intelligenti su quella pagina, anche ...


1
Se leggo correttamente il link che restituisce solo -1 o 0. Se vuoi -1, 0 o +1, allora è sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));o sign = (v > 0) - (v < 0);.
Bosone Z,

1
questo implica che vè un tipo intero non più ampio di int
phuclv

12

Se tutto ciò che vuoi è testare il segno, usa signbit (restituisce vero se il suo argomento ha un segno negativo). Non sono sicuro del motivo per cui si desidera che vengano restituiti in particolare -1 o +1; copysign è più conveniente per questo, ma sembra che restituirà +1 per zero negativo su alcune piattaforme con solo il supporto parziale per zero negativo, dove signbit presumibilmente tornerebbe vero.


7
Esistono molte applicazioni matematiche in cui è necessario il segno (x). Altrimenti lo farei if (x < 0).
Chance

5

In generale, non esiste una funzione signum standard in C / C ++ e la mancanza di una funzione così fondamentale ti dice molto su questi linguaggi.

A parte questo, credo che entrambi i punti di vista della maggioranza sul giusto approccio per definire tale funzione siano in un modo corretto, e la "controversia" su di essa è in realtà un non-argomento una volta che si prendono in considerazione due importanti avvertenze:

  • Una funzione signum dovrebbe sempre restituire il tipo del suo operando, in modo simile a una abs()funzione, poiché signum viene solitamente utilizzato per la moltiplicazione con un valore assoluto dopo che quest'ultimo è stato in qualche modo elaborato. Pertanto, il principale caso d'uso di signum non è il confronto ma l'aritmetica, e quest'ultimo non dovrebbe comportare costose conversioni da intero a / da virgola mobile.

  • I tipi a virgola mobile non presentano un singolo valore zero esatto: +0.0 può essere interpretato come "infinitesimalmente sopra lo zero" e -0,0 come "infinitesimalmente sotto lo zero". Questo è il motivo per cui i confronti che implicano zero devono verificare internamente entrambi i valori e un'espressione simile x == 0.0può essere pericolosa.

Per quanto riguarda C, penso che il modo migliore di procedere con i tipi integrali sia effettivamente usare l' (x > 0) - (x < 0)espressione, poiché dovrebbe essere tradotta in modo privo di diramazioni e richiede solo tre operazioni di base. Definire meglio le funzioni incorporate che impongono un tipo restituito corrispondente al tipo di argomento e aggiungere un C11define _Generic per associare queste funzioni a un nome comune.

Con i valori in virgola mobile, penso che le funzioni inline basate su C11 copysignf(1.0f, x), copysign(1.0, x)e copysignl(1.0l, x)siano la strada da percorrere, semplicemente perché sono anche altamente probabili essere prive di diramazioni e inoltre non richiedono il cast del risultato dall'intero in un punto in virgola mobile valore. Dovresti probabilmente commentare in modo evidente che le tue implementazioni in virgola mobile di signum non restituiranno zero a causa delle peculiarità dei valori di zero in virgola mobile, considerazioni sui tempi di elaborazione e anche perché è spesso molto utile nell'aritmetica in virgola mobile ricevere il corretto -1 / + 1 segno, anche per valori zero.


5

La mia copia di C in breve rivela l'esistenza di una funzione standard chiamata copysign che potrebbe essere utile. Sembra che copysign (1.0, -2.0) restituisca -1.0 e copysign (1.0, 2.0) restituisca +1.0.

Abbastanza vicino eh?


Non standard, ma potrebbe essere ampiamente disponibile. Microsoft inizia con un trattino basso, che è la convenzione che usano per le estensioni non standard. Tuttavia, non è la scelta migliore quando lavori con numeri interi.
Mark Ransom,

5
copysign è conforme agli standard ISO C (C99) e POSIX. Vedi opengroup.org/onlinepubs/000095399/functions/copysign.html
lhf

3
Cosa ha detto. Visual Studio non è un riferimento per lo standard C.
Stephen Canon,

3

No, non esiste in c ++, come in matlab. Per questo uso una macro nei miei programmi.

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

5
Bisognerebbe preferire i template alle macro in C ++.
Ruslan,


Ho pensato che fosse una buona risposta, poi ho guardato il mio codice e ho trovato questo: anche questo #define sign(x) (((x) > 0) - ((x) < 0))è buono.
Michel Rouzic,

1
una funzione inline è migliore di una macro in C, e in C ++ il modello è migliore
phuclv,

3

La risposta accettata con il sovraccarico di seguito in effetti non attiva -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>());
}

Per C ++ 11 potrebbe essere un'alternativa.

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

Per me non attiva alcun avviso su GCC 5.3.1.


Per evitare l' -Wunused-parameteravviso, basta usare parametri senza nome.
Jonathan Wakely, il

Questo è in realtà molto vero. Ho perso questo. Tuttavia, mi piace l'alternativa al C ++ 11 in entrambi i modi.
SamVanDonut

2

Un po 'fuori tema, ma io uso questo:

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

e ho trovato la prima funzione - quella con due argomenti, molto più utile da "standard" sgn (), perché è spesso usata in codice come questo:

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

vs.

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

non c'è cast per tipi non firmati e nessun segno negativo aggiuntivo.

infatti ho questo pezzo di codice 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 domanda è vecchia ma ora esiste questo tipo di funzione desiderata. Ho aggiunto un wrapper con no, spostamento a sinistra e dec.

È possibile utilizzare una funzione wrapper basata su signbit da C99 per ottenere esattamente il comportamento desiderato (vedere il codice più avanti).

Indica se il segno di x è negativo.
Questo può essere applicato anche a infiniti, NaN e zero (se zero è senza segno, è considerato positivo

#include <math.h>

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

NB: Uso l'operando not ("!") Perché il valore restituito di signbit non è specificato come 1 (anche se gli esempi ci fanno pensare che sarebbe sempre così) ma vero per un numero negativo:

Valore di ritorno
Un valore diverso da zero (vero) se il segno di x è negativo; e zero (falso) altrimenti.

Quindi moltiplico per due con lo spostamento a sinistra ("<< 1") che ci darà 2 per un numero positivo e 0 per uno negativo e infine diminuirò di 1 per ottenere 1 e -1 rispettivamente per i numeri positivi e negativi come richiesto da OPERAZIONE.


0 sarà positivo anche allora ... che potrebbe o meno essere ciò che OP voleva ...
Antti Haapala,

bene non potremmo mai sapere cosa volesse veramente OP se n = 0 ...!
Antonin GAVREL,

0

Mentre la soluzione intera nella risposta accettata è piuttosto elegante, mi dava fastidio che non sarebbe stato in grado di restituire NAN per i doppi tipi, quindi l'ho modificato leggermente.

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

Si noti che la restituzione di un virgola mobile NAN al contrario di un hard coded NANcause il bit del segno da impostare in alcune implementazioni , quindi l'uscita per val = -NANe val = NANstanno per essere identico qualunque cosa (se si preferisce "un nan" uscita nel corso di un -nansi può mettere un abs(val)prima del ritorno ...)



0

Ecco un'implementazione intuitiva per le ramificazioni:

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 meno che i dati non contengano zero come metà dei numeri, qui il predittore di rami sceglierà uno dei rami come il più comune. Entrambe le filiali comportano solo operazioni semplici.

In alternativa, su alcuni compilatori e architetture di CPU una versione completamente senza rami potrebbe essere più veloce:

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

Funziona con il formato a virgola mobile binario IEEE 754 a doppia precisione: binary64 .


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

Questa funzione presuppone:

  • binary32rappresentazione di numeri in virgola mobile
  • un compilatore che fa un'eccezione alla regola di aliasing rigorosa quando si utilizza un'unione denominata

3
Ci sono ancora alcuni presupposti negativi qui. Ad esempio, non credo che l'endianness del float sia garantita come endianness dell'intero. Il tuo controllo fallisce anche su qualsiasi architettura che utilizza ILP64. Davvero, stai solo reimplementando copysign; se stai usando static_asserthai C ++ 11 e potresti anche usarlo davvero copysign.

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

-3

Perché usare operatori ternari e if-else quando puoi semplicemente farlo

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

3
La tua definizione utilizza anche un operatore ternario.
Martin R,

Sì Sicuramente, ma utilizza solo un operatore ternario per separare i numeri zero e diversi da zero. Le versioni di altri includono operazioni ternarie nidificate per separare positivo, negativo e zero.
Jagreet,

L'uso di una divisione di numeri interi è molto inefficiente e abs () è solo per numeri interi.
Michel Rouzic,

Comportamento indefinito possibile quando x == INT_MIN.
chux - Ripristina Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.