Como devemos comparar dois números inteiros?

8

Recentemente, escrevi um programa que classifica uma matriz. Para isso, eu precisava escrever uma função de comparação, que passarei para ela. Minha função de comparação deveria ter retornado 1 (se x> y), -1 (se x <y) ou 0 (se x = y). Escrevi uma função regular (Função 1) usando expressões condicionais, mas fui aconselhado a escrever de maneira diferente (Função 2). É melhor escrever assim? Uma condição booleana sempre retornará 1 para a verdade? (Quero dizer, se x = 0 ey = 0 sempre teremos (x == y) == 1?)

Função 1:

int Icmp(void* x, void* y)
{
    int a = *(int*)x;
    int b = *(int*)y;
    if (a > b)
        return 1;
    else if (a < b)
        return -1;
    else
        return 0;
}

Função 2:

int Icmp(void* x, void* y)
{
    return (*(int*)x > * (int*)y) - (*(int*)x < *(int*)y);
}
Skiv Hisink
fonte
5
A vantagem nominal do segundo é que ele evita saltos condicionais e pode ter um desempenho melhor. A notação usada é horrível; as variáveis ​​devem ser localizadas como no primeiro e depois usadas no cálculo.
Jonathan Leffler
4
O segundo é um truque comum para forçá-lo sem ramificação em computadores estúpidos. Combine a legibilidade do primeiro para obter o melhor dos dois mundos. Edit: ie como Jonathan disse acima
Antti Haapala
8
Ambas as técnicas funcionam. Meça a diferença de desempenho com cuidado. Provavelmente será difícil de detectar. Prefira clareza quando a diferença for marginal.
Jonathan Leffler
1
Pergunte a quem o aconselhou por quê. Pergunte em quais situações as vantagens declaradas ocorrem. Pergunte por que se espera que essas apreensões sejam proeminentes para você. Se você não obtiver respostas satisfatórias para todas essas perguntas, procure outro consultor. Considere medir as vantagens declaradas, se possível. Considere-se quão importantes são as vantagens que você conhece, o mais importante: "Eu entendo o código" e "Acho que todos os meus colegas entendem melhor o código". Então decida você mesmo.
Yunnosch 15/10/19
2
@ JoëlHecht, se não for 1, não será um compilador C. Por que você gostaria de usar um compilador não C para compilar código C ?!
Antti Haapala

Respostas:

14

A maneira preferida de escrever o código sem ramificação seria usar uma variável local para os operandos:

int icmp(const void *x, const void *y)
{
    int a = *(const int *)x;
    int b = *(const int *)y;
    return (a > b) - (a < b);
}

A expressão é um idioma comum nas funções de comparação e, se escrita usando variáveis ​​em vez de desreferências de ponteiros no local, também é legível.

O código baseia-se no fato de que o resultado de uma comparação usando >, <ou mesmo, ==é do tipo int1 ou 0. Isso é exigido pelo padrão C - qualquer compilador que gere valores como 42 ou -1 é, por definição, não um compilador C .

É fácil ver que máx. um de a > bou a < bpode ser verdade em um dado momento, e o resultado é tanto 1 - 0, 0 - 1ou 0 - 0.

Quanto ao motivo do código sem ramificação - embora os compiladores possam gerar exatamente o mesmo código para ambas as funções, geralmente não o fazem. Por exemplo, o GCC e o ICC mais recentes parecem gerar uma ramificação para a primeira função no x86-64, mas um código sem ramificação com execução condicional para a última. E para quem disser que as ramificações não importam, remeto para o controle de qualidade mais votado de todos os tempos no Stack Overflow .

Antti Haapala
fonte
Esta é a maneira correta de fazer isso. É muito legível, o código de máquina compilado é idêntico e evita ramificações em favor de um pouco de aritmética adicional. O preditor de ramificação pode causar muitos problemas de desempenho ao classificar grandes matrizes que estão em um alto estado de entropia. Isso evita esse problema.
3ch0 15/10/19
5
Apenas uma observação de que a ausência de uma ifdeclaração-não significa que nenhuma ramificação será gerada, nem que, se houver uma ifdeclaração -de que haverá uma ramificação. De fato, depende se o compilador pode otimizá-los.
G. Sliepen 15/10/19
@ G.Sliepen realmente ICC seria perfeitamente capaz de otimizar-los para fora, mas ele chegou a uma conclusão que o ramo não iria provavelmente ser tomada porque foi escrito usando ramos ...
Antti Haapala
3

É melhor escrever assim?

Eu diria que não.

Para desempenho; ou isso não importa (provavelmente para compiladores modernos), ou não deve ser uma função separada (e deve ser incorporada ao código usado para classificação), ou você não deve classificar de forma alguma (por exemplo, dados classificados na criação e não classificado após a criação).

Para facilitar a leitura (manutenção do código, chance de ver erros na versão original, risco de apresentar erros posteriormente), eu prefiro sua versão original; especialmente ao trabalhar em equipe, e especialmente quando outros membros da equipe estão mais familiarizados com outras 10 linguagens de programação, cada uma com regras muito diferentes para C.

Especificamente; Eu gosto disso (porque os lançamentos no código real tornam as coisas mais difíceis de ler):

    int a = *(int*)x;
    int b = *(int*)y;

..e eu reescreveria o resto para ficar assim:

    if (a > b) {
        return 1;
    }
    if (a < b) {
        return -1;
    }
    return 0;
}

..ou para ficar assim:

    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
}

..porque elseé desnecessário após a return; e porque "if sem chaves seguido de declaração em sua própria linha" cria o risco de alguém inserir acidentalmente uma nova linha sem perceber e quebrar tudo (por exemplo, consulte https://dwheeler.com/essays/apple-goto- fail.html ).

Brendan
fonte
0

Se você estiver usando a função de comparação com qsort, a função precisará apenas retornar valores + ve, -ve ou zero.

Nesse caso, você pode apenas subtrair os números

int Icmp(const void* x, const void* y)
{
    return (*(int*)x - *(int*)y);
}
Rishikesh Raje
fonte
Isso é realmente mais eficiente do que (a > b) - (a < b), mas o problema a - bé que a subtração pode ser insuficiente. Portanto, essa é uma expressão ruim.
chmike
-2

Inteiros

echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

Flutuadores

echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

Cordas

echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
Milan Maniya
fonte
A pergunta é sobre como comparar números inteiros em C. Sua resposta claramente não é um código C válido e, portanto, não fornece informações úteis para a pessoa que fez a pergunta. Verifique se suas respostas estão no tópico.
G. Sliepen 21/10/19