web-dev-qa-db-fra.com

Opérations binaires équivalant à supérieur à opérateur

Je travaille sur une fonction qui va essentiellement déterminer lequel des deux pouces est le plus grand. Les paramètres transmis sont 2 32 bits. Le truc est que les seuls opérateurs autorisés sont ! ~ | & << >> ^ (pas de transtypage, autres types de données que signé int, *, /, -, etc.).

Mon idée jusqu'ici est de ^ les deux fichiers binaires ensemble pour voir toutes les positions des valeurs 1 qu'ils ne partagent pas. Ce que je veux faire, c'est prendre cette valeur et isoler le 1 le plus à gauche. Ensuite, voyez lequel d’entre eux a cette valeur. Cette valeur sera alors la plus grande. (Supposons que nous utilisions des bits de 8 bits au lieu de 32 bits). Si les deux valeurs transmises étaient 01011011 et 01101001 , J'ai utilisé ^. sur eux pour obtenir 00100010. Je veux ensuite le rendre 00100000 en d'autres termes 01xxxxxx -> 01000000 Puis & avec le premier chiffre !! le résultat et le renvoyer. S'il s'agit de 1, le premier # est plus grand.

Avez-vous des idées sur la façon de 01xxxxxx -> 01000000 ou quoi que ce soit d'autre pour vous aider?

Oublié de noter: pas de si, whiles, fors etc ...

18
Gekctek

Voici une version sans boucle qui compare les entiers non signés dans les opérations O (lg b) où b est la taille en mots de la machine. Notez que l'OP n'indique aucun autre type de données que signed int; il semble donc probable que la partie supérieure de cette réponse ne respecte pas les spécifications de l'OP. (Version du spoiler en bas.)

Notez que le comportement que nous voulons capturer se produit lorsque la discordance de bit la plus significative est 1 pour a et 0 pour b. Une autre façon de penser à cela est que n'importe quel bit dans a étant plus grand que le bit correspondant dans b signifie que a est supérieur à b, tant qu'il n'y a pas de bit précédent dans a inférieur au bit correspondant dans b.

À cette fin, nous calculons tous les bits dans a supérieurs aux bits correspondants dans b, et nous calculons également tous les bits dans a inférieurs aux bits correspondants dans b. Nous voulons maintenant masquer tous les bits «supérieurs à» qui se trouvent en dessous de tous les bits «inférieurs à». Nous prenons donc tous les bits «inférieurs à» et les barbotons tous vers la droite pour en faire un masque: le bit le plus significatif est défini par le chemin vers le bit le moins significatif est maintenant 1.

Il ne nous reste plus qu'à supprimer les bits "supérieurs à" en utilisant une simple logique de masquage de bits.

La valeur résultante est 0 si a <= b et non nulle si a > b. Si nous voulons que ce soit 1 dans ce dernier cas, nous pouvons faire un truc similaire et examiner le bit le moins significatif.

#include <stdio.h>

// Works for unsigned ints.
// Scroll down to the "actual algorithm" to see the interesting code.

// Utility function for displaying binary representation of an unsigned integer
void printBin(unsigned int x) {
    for (int i = 31; i >= 0; i--) printf("%i", (x >> i) & 1);
    printf("\n");
}
// Utility function to print out a separator
void printSep() {
    for (int i = 31; i>= 0; i--) printf("-");
    printf("\n");
}

int main()
{
    while (1)
    {
        unsigned int a, b;

        printf("Enter two unsigned integers separated by spaces: ");
        scanf("%u %u", &a, &b);
        getchar();

        printBin(a);
        printBin(b);
        printSep();

            /************ The actual algorithm starts here ************/

        // These are all the bits in a that are less than their corresponding bits in b.
        unsigned int ltb = ~a & b;

        // These are all the bits in a that are greater than their corresponding bits in b.
        unsigned int gtb = a & ~b;

        ltb |= ltb >> 1;
        ltb |= ltb >> 2;
        ltb |= ltb >> 4;
        ltb |= ltb >> 8;
        ltb |= ltb >> 16;

        // Nonzero if a > b
        // Zero if a <= b
        unsigned int isGt = gtb & ~ltb;

        // If you want to make this exactly '1' when nonzero do this part:
        isGt |= isGt >> 1;
        isGt |= isGt >> 2;
        isGt |= isGt >> 4;
        isGt |= isGt >> 8;
        isGt |= isGt >> 16;
        isGt &= 1;

            /************ The actual algorithm ends here ************/

        // Print out the results.
        printBin(ltb); // Debug info
        printBin(gtb); // Debug info
        printSep();
        printBin(isGt); // The actual result
    }
}

Remarque: cela devrait également fonctionner pour les entiers signés si vous inversez le bit du haut sur both des entrées, par exemple. a ^= 0x80000000.

Spoiler

Si vous voulez une réponse qui réponde à toutes les exigences (y compris 25 opérateurs ou moins):

int isGt(int a, int b)
{
    int diff = a ^ b;
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;

    diff &= ~(diff >> 1) | 0x80000000;
    diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);

    return !!diff;
}

Je vais partir en expliquant pourquoi cela fonctionne pour vous.

18
Kaganar

Un non signé variante étant donné que l’on peut utiliser logique (&&, ||) et comparaison (! =, ==).

int u_isgt(unsigned int a, unsigned int b)
{
    return a != b && (    /* If a == b then a !> b and a !< b.             */
               b == 0 ||  /* Else if b == 0 a has to be > b (as a != 0).   */
               (a / b)    /* Else divide; integer division always truncate */
           );             /*              towards zero. Giving 0 if a < b. */
}

!= et == peuvent facilement être éliminés.

int u_isgt(unsigned int a, unsigned int b)
{
    return a ^ b && (
               !(b ^ 0) ||
               (a / b)
           );
}

Pour signé on pourrait alors développer quelque chose comme:

int isgt(int a, int b)
{
    return
    (a != b) &&
    (
        (!(0x80000000 & a) && 0x80000000 & b) ||  /* if a >= 0 && b < 0  */
        (!(0x80000000 & a) && b == 0) ||
        /* Two more lines, can add them if you like, but as it is homework
         * I'll leave it up to you to decide. 
         * Hint: check on "both negative" and "both not negative". */
    )
    ;
}

Peut être plus compact/éliminer les ops. (au moins un) mais exprimez-le ainsi pour plus de clarté.

Au lieu de 0x80000000, on pourrait dire:

#include <limits.h>
static const int INT_NEG = (1 << ((sizeof(int) * CHAR_BIT) - 1));

En utilisant ceci pour tester:

void test_isgt(int a, int b)
{
    fprintf(stdout,
        "%11d > %11d = %d : %d %s\n",
        a, b,
        isgt(a, b), (a > b),
        isgt(a, b) != (a>b) ? "BAD!" : "OK!");
}

Résultat:

         33 >           0 = 1 : 1 OK!
        -33 >           0 = 0 : 0 OK!
          0 >          33 = 0 : 0 OK!
          0 >         -33 = 1 : 1 OK!
          0 >           0 = 0 : 0 OK!
         33 >          33 = 0 : 0 OK!
        -33 >         -33 = 0 : 0 OK!
         -5 >         -33 = 1 : 1 OK!
        -33 >          -5 = 0 : 0 OK!
-2147483647 >  2147483647 = 0 : 0 OK!
 2147483647 > -2147483647 = 1 : 1 OK!
 2147483647 >  2147483647 = 0 : 0 OK!
 2147483647 >           0 = 1 : 1 OK!
          0 >  2147483647 = 0 : 0 OK!
5
Morpfh

Pour convertir 001xxxxx en 00100000, vous devez d’abord exécuter:

x |= x >> 4;
x |= x >> 2;
x |= x >> 1;

(ceci est pour 8 bits; pour l'étendre à 32, ajoutez des décalages de 8 et 16 au début de la séquence).

Cela nous laisse avec 00111111 (cette technique est parfois appelée "bit-smearing"). Nous pouvons ensuite couper tout sauf le premier bit:

x ^= x >> 1;

nous laissant avec 00100000.

4
caf

Une version entièrement branchless de la fonction isGt plus petite de Kaganar pourrait ressembler à ceci:

int isGt(int a, int b)
{
    int diff = a ^ b;
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;

    //1+ on GT, 0 otherwise.
    diff &= ~(diff >> 1) | 0x80000000;
    diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);

    //flatten back to range of 0 or 1.
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;
    diff &= 1;

    return diff;
}

Cela correspond à environ 60 instructions pour le calcul réel (compilateur MSVC 2010, sur un archet x86), plus environ 10 opérations de pile supplémentaires pour le prologue/épilogue de la fonction.

2
Philip Conrad

Même si je ne veux pas faire les devoirs de quelqu'un d'autre, je ne pourrais pas résister à celui-ci .. :) Je suis sûr que les autres peuvent penser à un modèle plus compact..mais voici le mien .. fonctionne bien, y compris les nombres négatifs. .

Edit: il y a quelques bugs cependant. Je laisserai au PO le soin de le trouver et de le réparer.

    #include<unistd.h>
    #include<stdio.h>
    int a, b, i, ma, mb, a_neg, b_neg, stop;

    int flipnum(int *num, int *is_neg) {
        *num = ~(*num) + 1;
        *is_neg = 1;

        return 0;
    }

    int print_num1() {
        return ((a_neg && printf("bigger number %d\n", mb)) ||
             printf("bigger number %d\n", ma));
    }

    int print_num2() {
        return ((b_neg && printf("bigger number %d\n", ma)) ||
             printf("bigger number %d\n", mb));
    }

    int check_num1(int j) {
        return ((a & j) && print_num1());
    }

    int check_num2(int j) {
        return ((b & j) && print_num2());
    }

    int recursive_check (int j) {
        ((a & j) ^ (b & j)) && (check_num1(j) || check_num2(j))  && (stop = 1, j = 0);

        return(!stop && (j = j >> 1) && recursive_check(j));
    }

    int main() {
        int j;
        scanf("%d%d", &a, &b);
        ma = a; mb = b;

        i = (sizeof (int) * 8) - 1;
        j = 1 << i;

        ((a & j) && flipnum(&a, &a_neg));

        ((b & j) && flipnum(&b, &b_neg));

        j = 1 << (i - 1);

        recursive_check(j);

        (!stop && printf("numbers are same..\n"));
    }
0
Manohar

Je pense avoir une solution avec 3 opérations:

Ajoutez un au premier nombre, soustrayez-le du plus grand nombre possible que vous puissiez représenter (tous les 1). Ajoutez ce nombre au deuxième numéro. Si elle déborde, le premier nombre est inférieur au second.

Je ne suis pas sûr à 100% si cela est correct. C’est-à-dire que vous n’aurez peut-être pas besoin d’ajouter 1, et je ne sais pas s’il est possible de vérifier le débordement (sinon, il suffit de réserver le dernier bit et de vérifier s’il est égal à 1 à la fin.)

0
Houshalter

MODIFIER:

D'accord, il y avait quelques problèmes avec le code, mais je l'ai révisé et les travaux suivants.

Cette fonction auxiliaire compare le nième chiffre significatif des nombres:

int compare ( int a, int b, int n )
{
    int digit = (0x1 << n-1);
    if ( (a & digit) && (b & digit) )
       return 0; //the digit is the same

    if ( (a & digit) && !(b & digit) )
       return 1; //a is greater than b

    if ( !(a & digit) && (b & digit) )
       return -1; //b is greater than a
}

Les éléments suivants doivent renvoyer récursivement le plus grand nombre:

int larger ( int a, int b )
{
    for ( int i = 8*sizeof(a) - 1 ; i >= 0 ; i-- )
    {
       if ( int k = compare ( a, b, i ) )
       {
           return (k == 1) ? a : b;
       }
    }
    return 0; //equal
}
0
Luchian Grigore