Existe uma função de sinal padrão (signum, sgn) em C / C ++?

409

Eu quero uma função que retorne -1 para números negativos e +1 para números positivos. http://en.wikipedia.org/wiki/Sign_function É bastante fácil escrever o meu, mas parece que algo deveria estar em uma biblioteca padrão em algum lugar.

Edit: Especificamente, eu estava procurando uma função trabalhando em carros alegóricos.

extravagantemente
fonte
13
O que deve retornar para 0?
22715 Craig McQueen
61
@Craig McQueen; isso depende se é um zero positivo ou zero negativo.
ysth 15/12/09
1
Notei que você especificou o valor de retorno como um número inteiro. Você está procurando uma solução que utilize números inteiros ou números de ponto flutuante?
22815 Mark Byers
6
@ysth @ Craig McQueen, falso para carros alegóricos também, não? A definição de sgn (x) diz para retornar 0 se x==0. De acordo com a IEEE 754 , o zero negativo e o zero positivo devem ser comparados como iguais.
precisa saber é o seguinte
5
@ysth "depende do zero positivo ou do zero negativo". na verdade, não.
perfil completo de RJFalconer

Respostas:

506

Surpreendido, ninguém postou a versão C ++ com segurança de tipo ainda:

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

Benefícios:

  • Na verdade, implementa signum (-1, 0 ou 1). As implementações aqui usando copysign retornam apenas -1 ou 1, o que não é signum. Além disso, algumas implementações aqui estão retornando um float (ou T) em vez de um int, o que parece um desperdício.
  • Funciona para entradas, carros alegóricos, duplos, curtos não assinados ou quaisquer tipos personalizados construtíveis a partir do número 0 e ordenáveis.
  • Rápido! copysigné lento, especialmente se você precisar promover e estreitar novamente. Isso é sem ramificação e otimiza excelentemente
  • Em conformidade com os padrões! O truque de deslocamento de bits é puro, mas funciona apenas para algumas representações de bits e não funciona quando você tem um tipo não assinado. Pode ser fornecido como uma especialização manual, quando apropriado.
  • Preciso! Comparações simples com zero podem manter a representação interna de alta precisão da máquina (por exemplo, 80 bits em x87) e evitar um arredondamento prematuro para zero.

Ressalvas:

  • É um modelo, portanto, pode levar mais tempo para compilar em algumas circunstâncias.
  • Aparentemente, algumas pessoas pensam que o uso de uma nova função de biblioteca padrão um tanto esotérica e muito lenta que nem sequer implementa signum é mais compreensível.
  • A < 0parte da verificação aciona o -Wtype-limitsaviso do GCC quando instanciada para um tipo não assinado. Você pode evitar isso usando algumas 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>());
    }

    (Que é um bom exemplo da primeira advertência.)

Pharap
fonte
18
@GMan: o GCC agora (4.5) parou de custar quadrático ao número de instanciações para funções de modelo, e ainda é drasticamente mais caro analisar e instanciar do que funções escritas manualmente ou o pré-processador C padrão. O vinculador também precisa trabalhar mais para remover instâncias duplicadas. Os modelos também incentivam # include-in # includes, o que faz com que o cálculo da dependência leve mais tempo e pequenas alterações (geralmente implementação, não interface) para forçar a recompilação de mais arquivos.
15
@ Joe: Sim, e ainda não há nenhum custo perceptível. O C ++ usa modelos, isso é algo que todos nós temos que entender, aceitar e superar.
GManNickG
42
Espere, o que é esse negócio de "copysign é lento" ...? O uso de compiladores atuais (g ++ 4.6+, clang ++ 3.0) std::copysignparece resultar em um excelente código para mim: 4 instruções (incluídas), sem ramificação, usando inteiramente o FPU. A receita dada nesta resposta, pelo contrário, gera código muito pior (muitos mais instruções, incluindo uma multiplicação, e para trás entre a unidade inteira e FPU em movimento) ...
snogglethorpe
14
@snogglethorpe: Se você está chamando copysignum int, ele promove flutuar / dobrar e deve diminuir novamente no retorno. Seu compilador pode otimizar essa promoção, mas não consigo encontrar nada sugerindo que seja garantido pelo padrão. Também para implementar o signum via copysign, você precisa lidar manualmente com o caso 0 - inclua isso em qualquer comparação de desempenho.
53
A primeira versão não é sem ramo. Por que as pessoas pensam que uma comparação usada em uma expressão não gera um ramo? Será na maioria das arquiteturas. Somente processadores que possuem um cmove (ou predicação) geram código sem ramificação, mas também o fazem para ternários ou se / ou se for uma vitória.
Patrick Schlüter
271

Não conheço uma função padrão para isso. Aqui está uma maneira interessante de escrevê-lo:

(x > 0) - (x < 0)

Aqui está uma maneira mais legível de fazer isso:

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

Se você gosta do operador ternário, pode fazer o seguinte:

(x > 0) ? 1 : ((x < 0) ? -1 : 0)
Mark Byers
fonte
7
Mark Ransom, suas expressões dão resultados incorretos para x==0.
Avakar 14/12/2009
3
@Svante: "Cada um dos operadores <, >... produzirá 1 se a relação especificada for verdadeira e 0 se for falsa"
Stephen Canon
11
@Svante: não exatamente. Um valor de 0é "false"; qualquer outro valor é "verdadeiro"; no entanto, os operadores relacionais e de igualdade sempre retornam 0ou 1(consulte Padrão 6.5.8 e 6.5.9). - o valor da expressão a * (x == 42)é 0ou a.
pmg
21
Marca de alto desempenho, estou surpreso que você tenha perdido a tag C ++. Esta resposta é muito válida e não merece voto negativo. Além disso, eu não usaria o copysignintegral xmesmo se o tivesse disponível.
Avakar 15/12/2009
6
Alguém já verificou qual código GCC / G ++ / qualquer outro compilador emite em uma plataforma real? Meu palpite é que a versão "sem ramificação" usa duas ramificações em vez de uma. O deslocamento de bits é provavelmente muito mais rápido - e mais portátil em termos de desempenho.
Jørgen Fogh
192

Existe uma função da biblioteca matemática C99 chamada copysign (), que recebe o sinal de um argumento e o valor absoluto do outro:

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

resultará em +/- 1,0, dependendo do sinal do valor. Observe que os zeros de ponto flutuante são assinados: (+0) produzirá +1 e (-0) produzirá -1.

tempestade
fonte
57
Votou com sucesso, esta foi a resposta mais popular com menos votos. Deixando-se surpreso com o fato de a comunidade SO parecer preferir um hack ao uso de uma função de biblioteca padrão. Que os deuses da programação condenem todos vocês a tentar decifrar hacks usados ​​por programadores inteligentes que não estão familiarizados com os padrões de linguagem. Sim, eu sei que isso vai me custar uma tonelada de repetições no SO, mas eu prefiro ficar do lado da tempestade do que o resto de vocês ...
High Performance Mark
34
Isso está próximo, mas fornece a resposta errada para zero (de acordo com o artigo da Wikipedia na pergunta, pelo menos). Boa sugestão embora. +1 de qualquer maneira.
22415 Mark Byers
4
Se você deseja um número inteiro, ou se deseja o resultado exato do sinal para zeros, eu gosto da resposta de Mark Byers, que é extremamente elegante! Se você não se importa com o exposto acima, copysign () pode ter uma vantagem de desempenho, dependendo do aplicativo - se eu estivesse otimizando um loop crítico, tentaria os dois.
comingstorm 16/12/2009
10
1) C99 não é totalmente suportado em todos os lugares (considere VC ++); 2) essa também é uma questão de C ++. Essa é uma boa resposta, mas a votada também funciona e é mais amplamente aplicável.
Pavel Minaev
5
Salvador! Precisava de uma maneira de determinar entre -0,0 e 0,0
Ólafur Waage
79

Parece que a maioria das respostas perdeu a pergunta original.

Existe uma função de sinal padrão (signum, sgn) em C / C ++?

Não na biblioteca padrão, no entanto, existe o copysignque pode ser usado quase da mesma maneira via copysign(1.0, arg)e existe uma verdadeira função de sinal boost, que também pode fazer parte do padrão.

    #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

Catskul
fonte
5
Essa deve ser a resposta mais votada, pois fornece a solução mais próxima possível do que é solicitado na pergunta.
BartoszKP
Eu estive pensando nos últimos minutos por que a biblioteca padrão não tem função de sinal. É tão comum - definitivamente mais comumente usado do que a função gama, que pode ser encontrada no cabeçalho cmath.
Taozi 22/02
4
A explicação que geralmente recebo para perguntas semelhantes é "é fácil o suficiente para se implementar". Qual IMO não é um bom motivo. Ela esconde completamente os problemas de onde padronização, casos extremos não óbvios e onde colocar uma ferramenta tão amplamente usada.
Catskul 22/02
77

Aparentemente, a resposta para a pergunta do pôster original é não. Não há função C ++ padrãosgn .

John
fonte
2
@ SR_ Você não está correto. copysign()não fará seu primeiro parâmetro 0,0 se o segundo for 0,0. Em outras palavras, John está correto.
Alexis Wilke
30

Existe uma função de sinal padrão (signum, sgn) em C / C ++?

Sim, dependendo da definição.

C99 e posterior possui a signbit()macro em<math.h>

int signbit(flutuante real x);
A signbitmacro retorna um valor diferente de zero se e somente se o sinal do valor do argumento for negativo. C11 §7.12.3.6


No entanto, o OP quer algo um pouco diferente.

Eu quero uma função que retorne -1 para números negativos e +1 para números positivos. ... uma função trabalhando em carros alegóricos.

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

Mais profundo:

O post não é específico nos seguintes casos: x = 0.0, -0.0, +NaN, -NaN.

Um clássico signum()retorna +1no x>0, -1no x<0e 0no x==0.

Muitas respostas já abordaram isso, mas não abordam x = -0.0, +NaN, -NaN. Muitos são voltados para um ponto de vista inteiro que geralmente não possui números não-números ( NaN ) e -0,0 .

Respostas típicas funcionam como signnum_typical() Ativado -0.0, +NaN, -NaN, elas retornam 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;
}

Em vez disso, proponho esta funcionalidade: Sim -0.0, +NaN, -NaN, ela retorna -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;
}
chux - Restabelecer Monica
fonte
1
Ah, exatamente o que eu estou procurando. Isso acabou de mudar no Pharo Smalltalk github.com/pharo-project/pharo/pull/1835 e eu me perguntei se havia algum tipo de padrão (IEC 60559 ou ISO 10967) ditando o comportamento para o comportamento negativo de zero e nan ... javascript sign developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
aka.nice
29

Mais rápidas que as soluções acima, incluindo a mais alta classificada:

(x < 0) ? -1 : (x > 0)
xnx
fonte
1
Que tipo é x? Ou você está usando um #define?
Chance
3
Seu tipo não é mais rápido. Isso causará uma falta de cache com bastante frequência.
Jeffrey Drake
19
Falta de cache? Não sei bem como. Talvez você quis dizer uma predição errada do ramo?
Catskul
2
Parece-me que isso resultará em um aviso de tipos inteiros e booleanos confusos!
sergiol
como isso será rápido com o ramo?
Nick
16

Existe uma maneira de fazer isso sem ramificação, mas não é muito bonito.

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

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

Muitas outras coisas interessantes e excessivamente inteligentes nessa página também ...

Tim Sylvester
fonte
1
Se eu li o link corretamente, ele retorna apenas -1 ou 0. Se você quiser -1, 0 ou +1, então é sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));ou sign = (v > 0) - (v < 0);.
Z boson
1
isto implica que vé um tipo inteiro não mais largo do que int
phuclv
12

Se tudo que você deseja é testar o sinal, use signbit (retorna true se o argumento tiver um sinal negativo). Não sabe por que você deseja que -1 ou +1 sejam retornados; copysign é mais conveniente para isso, mas parece que ele retornará +1 para zero negativo em algumas plataformas com apenas suporte parcial para zero negativo, em que presumivelmente o signbit retornaria true.

ysth
fonte
7
Existem muitas aplicações matemáticas nas quais o sinal (x) é necessário. Caso contrário, eu apenas faria if (x < 0).
Chance
5

Em geral, não há função de sinalização padrão no C / C ++, e a falta de uma função fundamental diz muito sobre essas linguagens.

Além disso, acredito que os dois pontos de vista da maioria sobre a abordagem correta para definir uma função estão de certa maneira corretos, e a "controvérsia" sobre ela é na verdade um argumento, uma vez que você leva em consideração duas advertências importantes:

  • Uma função signum sempre deve retornar o tipo de seu operando, da mesma forma que uma abs()função, porque signum é geralmente usado para multiplicação com um valor absoluto depois que o último foi processado de alguma forma. Portanto, o principal caso de uso de signum não é comparações, mas aritmética, e o último não deve envolver conversões caras de número inteiro para ponto flutuante.

  • Os tipos de ponto flutuante não apresentam um único valor exato de zero: +0,0 pode ser interpretado como "infinitesimalmente acima de zero" e -0,0 como "infinitesimalmente abaixo de zero". Essa é a razão pela qual comparações envolvendo zero devem verificar internamente os dois valores, e uma expressão como x == 0.0pode ser perigosa.

Em relação a C, acho que o melhor caminho a seguir com tipos integrais é realmente usar a (x > 0) - (x < 0)expressão, pois ela deve ser traduzida de forma livre de ramificação e requer apenas três operações básicas. É melhor definir funções embutidas que impõem um tipo de retorno correspondente ao tipo de argumento e adicionar um C11 define _Genericpara mapear essas funções para um nome comum.

Com valores de ponto flutuante, acho funções inline baseado em C11 copysignf(1.0f, x), copysign(1.0, x)e copysignl(1.0l, x)são o caminho a percorrer, simplesmente porque eles são também altamente provável que seja livre de ramo, e, adicionalmente, não necessitam de conversão do resultado de inteiro de volta para um ponto flutuante valor. Provavelmente, você deve comentar com destaque que suas implementações de signum de ponto flutuante não retornarão zero por causa das peculiaridades dos valores de ponto flutuante zero, considerações sobre o tempo de processamento e também porque geralmente é muito útil na aritmética de ponto flutuante receber o -1 / + correto 1 sinal, mesmo para valores zero.

Tabernakel
fonte
5

Minha cópia de C em poucas palavras revela a existência de uma função padrão chamada copysign que pode ser útil. Parece que o copysign (1.0, -2.0) retornaria -1.0 e o copysign (1.0, 2.0) retornaria +1.0.

Bem perto né?

Marca de alto desempenho
fonte
Não é padrão, mas pode estar amplamente disponível. A Microsoft começa com um sublinhado, que é a convenção que eles usam para extensões não padrão. Não é a melhor escolha quando você está trabalhando com números inteiros.
Mark Ransom
5
o copysign está nos padrões ISO C (C99) e POSIX. Veja opengroup.org/onlinepubs/000095399/functions/copysign.html
lhf
3
O que lhf disse. O Visual Studio não é uma referência para o padrão C.
21715 Stephen
3

Não, ele não existe no c ++, como no matlab. Eu uso uma macro em meus programas para isso.

#define sign(a) ( ( (a) < 0 )  ?  -1   : ( (a) > 0 ) )
tagarelar
fonte
5
Deve-se preferir modelos sobre macros em C ++.
Ruslan
Em C, não há nenhum modelo ...... helloacm.com/how-to-implement-the-sgn-function-in-c
doctorlai
Eu pensei que esta era uma boa resposta, então olhei para o meu próprio código e achei o seguinte: o #define sign(x) (((x) > 0) - ((x) < 0))que também é bom.
Michel Rouzic
1
uma função incorporada é melhor do que uma macro em C, e em C ++ modelo é melhor
phuclv
3

A resposta aceita com a sobrecarga abaixo de fato não aciona -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>());
}

Para C ++ 11, uma alternativa poderia 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 mim, ele não dispara nenhum aviso no GCC 5.3.1.

SamVanDonut
fonte
Para evitar o -Wunused-parameteraviso, use parâmetros sem nome.
Jonathan Wakely
Isso é realmente muito verdadeiro. Eu senti falta disso. No entanto, eu gosto mais da alternativa C ++ 11.
SamVanDonut 29/01/19
2

Pouco fora do tópico, mas eu uso isso:

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 eu achei a primeira função - aquela com dois argumentos, para ser muito mais útil a partir de "standard" sgn (), porque é mais frequentemente usada em códigos 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);
}

não há conversão para tipos não assinados e nenhum menos adicional.

na verdade, eu tenho esse pedaço de 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);
}
usuario
fonte
1

A questão é antiga, mas agora existe esse tipo de função desejada. Eu adicionei um invólucro com não, shift esquerdo e dec.

Você pode usar uma função de invólucro baseada no signbit do C99 para obter o comportamento exato desejado (consulte o código mais abaixo).

Retorna se o sinal de x é negativo.
Isso também pode ser aplicado a infinitos, NaNs e zeros (se zero não for assinado, será considerado positivo

#include <math.h>

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

NB: Eu uso o operando não ("!") Porque o valor de retorno do signbit não é especificado como 1 (mesmo que os exemplos pensemos que seria sempre assim), mas verdadeiro para um número negativo:

Valor de retorno
Um valor diferente de zero (true) se o sinal de x for negativo; e zero (falso) caso contrário.

Em seguida, multiplico por dois com o deslocamento à esquerda ("<< 1"), que nos dará 2 para um número positivo e 0 para um número negativo e, finalmente, decrementaremos 1 para obter 1 e -1 para os números respectivamente positivos e negativos, conforme solicitado por OP.

Antonin GAVREL
fonte
0 será positivo também ... o que pode ou não ser o que o OP queria ...
Antti Haapala
bem, talvez nunca saibamos o que o OP realmente queria se n = 0 ...!
Antonin GAVREL
0

Embora a solução inteira na resposta aceita seja bastante elegante, me incomodou o fato de não poder retornar NAN para tipos duplos, por isso modifiquei-a levemente.

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

Observe que o retorno de uma NAN de ponto flutuante em oposição a um código codificado NANfaz com que o bit de sinal seja definido em algumas implementações ; portanto, a saída val = -NANe a saída val = NANserão idênticas, independentemente do que for (se você preferir uma nansaída " " a uma, -nanvocê pode colocar um abs(val)antes do retorno ...)

mrclng
fonte
0

Você pode usar o boost::math::sign()método a partir de boost/math/special_functions/sign.hppse o impulso estiver disponível.

khkarens
fonte
Observe que isso foi sugerido antes: stackoverflow.com/a/16869019/1187415 .
Martin R
0

Aqui está uma implementação amigável para ramificação:

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 seus dados tenham zeros como metade dos números, aqui o preditor de ramificação escolherá uma das ramificações como a mais comum. Ambas as ramificações envolvem apenas operações simples.

Como alternativa, em alguns compiladores e arquiteturas de CPU, uma versão completamente sem ramificação pode ser mais rápida:

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

Isso funciona para o formato de ponto flutuante binário de precisão dupla IEEE 754: binary64 .

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

Esta função assume:

  • representação binary32 de números de ponto flutuante
  • um compilador que faz uma exceção sobre a regra estrita de alias ao usar uma união nomeada
Gigi
fonte
3
Ainda existem algumas suposições ruins aqui. Por exemplo, não acredito que o endianness do float seja garantido como endianness do número inteiro. Sua verificação também falha em qualquer arquitetura usando ILP64. Realmente, você está apenas reimplementando copysign; se você estiver usando, static_assertpossui C ++ 11 e pode muito bem usá-lo copysign.
-3
double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }
cyberion
fonte
-3

Por que usar operadores ternários e if-else quando você pode simplesmente fazer isso

#define sgn(x) x==0 ? 0 : x/abs(x)
Jagreet
fonte
3
Sua definição também usa um operador ternário.
Martin R
Sim, definitivamente, mas ele usa apenas um operador ternário para separar números zero e não zero. As versões de outros incluem operações ternárias aninhadas para separar positivo, negativo e zero.
Jagreet
Usar uma divisão inteira é muito ineficiente e abs () é apenas para números inteiros.
Michel Rouzic 19/03/19
Comportamento indefinido possível quando x == INT_MIN.
chux - Restabelece Monica