web-dev-qa-db-fra.com

Est-il possible d'implémenter des opérateurs binaire utilisant des arithmétiques entier?

Je suis confronté à un problème plutôt particulier. Je travaille sur un compilateur pour une architecture qui ne prend pas en charge les opérations binaire. Cependant, il gère des arithmétiques entier signés de 16 bits et je me demandais s'il serait possible de mettre en œuvre des opérations binaire en utilisant uniquement:

  • Ajout ( C = A + B)
  • Soustraction ( C = A - B)
  • division ( C = A/B)
  • multiplication ( c = a * B)
  • Modulus ( C = A% B)
  • minimum ( c = min (A, B))
  • maximum ( c = max (A, B))
  • comparaisons ( c = (a <b), c = (a == b), c = (A <= B), ET.C.)
  • saute ( goto, car, etc.)

Les opérations bitwises que je veux pouvoir soutenir sont:

  • o ( C = A | B)
  • et ( c = A & B)
  • xor ( c = a ^ b)
  • Changement de gauche ( C = A << B)
  • DROIT SHIFT ( C = A >> B)
  • (Tous les entiers sont signés de sorte que ceci est un problème)
  • SIGNÉ SHIFT ( C = A >>> B)
  • son complément ( A = ~ B)
  • (Déjà trouvé une solution, voir ci-dessous)

Normalement, le problème est l'inverse; Comment réaliser des optimisations arithmétiques à l'aide de hacks Bitwise. Cependant pas dans ce cas.

La mémoire écritable est très rare sur cette architecture, d'où la nécessité d'opérations bitwises. Les fonctions bitwises elles-mêmes ne doivent pas utiliser beaucoup de variables temporaires. Cependant, la lecture constante des données et des instructions en lecture seule est abondante. Une note latérale ici est également que les sauts et les branches ne sont pas coûteux et que toutes les données sont facilement cachées. Les sauts coûtent la moitié des cycles sous forme d'instructions arithmétiques (y compris charge/stocke). En d'autres termes, toutes les fonctions appuyées ci-dessus coûtent deux fois les cycles d'un seul saut.


Quelques pensées qui pourraient aider:

J'ai compris que vous pouvez faire son complément (nier des bits) avec le code suivant:

// Bitwise one's complement
b = ~a;
// Arithmetic one's complement
b = -1 - a;

Je me souviens également de l'ancien piratage de quart lors de la division avec une puissance de deux, donc le Bitwise Shift peut être exprimé comme suit:

// Bitwise left shift
b = a << 4;
// Arithmetic left shift
b = a * 16; // 2^4 = 16

// Signed right shift
b = a >>> 4;
// Arithmetic right shift
b = a / 16;

Pour le reste des opérations bitwises, je suis légèrement désemparé. Je souhaite que les architectes de cette architecture auraient fourni des opérations bit-bit.

Je voudrais également savoir s'il existe un moyen rapide/facile de calculer la puissance de deux (pour les opérations de décalage) sans utiliser de table de données de mémoire. Une solution naïve serait de sauter dans un champ de multiplications:

b = 1;
switch (a)
{
  case 15: b = b * 2;
  case 14: b = b * 2;
  // ... exploting fallthrough (instruction memory is magnitudes larger)
  case 2: b = b * 2;
  case 1: b = b * 2;
}

Ou une approche de jeu et de saut:

switch (a)
{
  case 15: b = 32768; break;
  case 14: b = 16384; break;
  // ... exploiting the fact that a jump is faster than one additional mul
  //     at the cost of doubling the instruction memory footprint.
  case 2: b = 4; break;
  case 1: b = 2; break;
}
55
Statement

Premières solutions pour le déplacement (décalage est la distance de décalage, ne doit pas être négative, A est l'opérande à décaler et contient également le résultat lorsque vous avez terminé). La table d'alimentation est utilisée par les trois opérations de quart.

// table used for shift operations
powtab = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, -32768 };

// logical shift left
if (shift > 15) {
     a = 0; // if shifting more than 15 bits to the left, value is always zero
} else {
     a *= powtab[shift];
}

// logical shift right (unsigned)
if (shift > 15) {
    a = 0; // more than 15, becomes zero
} else if (shift > 0) {
    if (a < 0) {
        // deal with the sign bit (15)
        a += -32768;
        a /= powtab[shift];
        a += powtab[15 - shift];
    } else {
        a /= powtab[shift];
    }
}

// arithmetic shift right (signed)
if (shift >= 15) {
    if (a < 0) {
        a = -1;
    } else {
        a = 0;
    }
} else if (shift > 0) {
    if (a < 0) {
        // deal with the sign bit
        a += -32768;
        a /= powtab[shift];
        a -= powtab[15 - shift];
    } else {
        // same as unsigned shift
        a /= powtab[shift];
    }
}

Pour et, OR et XOR, je ne pouvais pas trouver une solution simple, alors je le ferai avec une boucle sur chaque morceau. Il pourrait y avoir un meilleur tour pour faire cela. Pseudocode suppose A et B sont des opérandes d'entrée, c est la valeur de résultat, X est le compteur de boucle (chaque boucle doit fonctionner exactement 16 fois):

// XOR (^)
c = 0;
for (x = 0; x <= 15; ++x) {
    c += c;
    if (a < 0) {
        if (b >= 0) {
            c += 1;
        }
    } else if (b < 0) {
        c += 1;
    }
    a += a;
    b += b;
}

// AND (&)
c = 0;
for (x = 0; x <= 15; ++x) {
    c += c;
    if (a < 0) {
        if (b < 0) {
            c += 1;
        }
    }
    a += a;
    b += b;
}

// OR (|)
c = 0;
for (x = 0; x <= 15; ++x) {
    c += c;
    if (a < 0) {
        c += 1;
    } else if (b < 0) {
        c += 1;
    }
    a += a;
    b += b;
}

Cela suppose que toutes les variables sont de 16 bits et que toutes les opérations se comportent comme signées (donc A <0 est réellement vraie lorsque le bit 15 est défini).

EDIT: J'ai réellement testé toutes les valeurs d'opérande possibles (-32768 à 32767) pour les décalages allant de 0 à 31 pour l'exactitude et cela fonctionne correctement (en supposant des divisions entier). Pour le code et/ou/xor, un test exhaustif prend trop de temps sur ma machine, mais puisque le code pour ceux-ci est assez simple, il ne devrait pas y avoir de cas de bord de toute façon.

24
Durandal

Dans cet environnement, il serait peut-être préférable que vous puissiez mettre en place pour utiliser des opérateurs arithmatiques pour éplucher des composants des entiers.

PAR EXEMPLE.

if (a & 16)  becomes if ((a % 32) > 15)
a &= 16 becomes if ((a % 32) < 15) a += 16

Les transformations de ces opérateurs sont suffisamment évidentes si vous limitez le RHS à une puissance constante de 2.

Pelage de deux ou quatre bits est également facile à faire.

5
Joshua

Une réponse incomplète sur une ancienne question, ici se concentrant sur et, ou, ou, xor. Une fois qu'une solution est trouvée pour l'une de ces opérations bitwises, les deux autres peuvent être dérivés. Il existe plusieurs manières, une fois dans le programme de test suivant (compilé sur la version 4.6.3 de GCC (Ubuntu/Linaro 4.6.3-1ubuntu5)).

En décembre 2018, j'ai découvert une erreur dans la solution. Le XOR commenté ci-dessous ne fonctionne que parce que les résultats intermédiaires en a+b-2*AND(a,b) sont favorisés int, qui est de plus de 16 bits pour tous les compilateurs modernes.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

//#define XOR(a,b) (a + b - 2*AND(a,b)) // Error. Intermediate overflow
#define XOR(a,b) (a - AND(a,b) +  b - AND(a,b) )
#define IOR(a,b) XOR(XOR(a,b),AND(a,b)) // Credit to Jan Gray, Gray Research LLC, for IOR
static const uint16_t andlookup[256] = {
#define C4(a,b) ((a)&(b)), ((a)&(b+1)), ((a)&(b+2)), ((a)&(b+3))
#define L(a) C4(a,0), C4(a,4), C4(a,8), C4(a,12)
#define L4(a) L(a), L(a+1), L(a+2), L(a+3)
    L4(0), L4(4), L4(8), L4(12)
#undef C4
#undef L
#undef L4
};

uint16_t AND(uint16_t a, uint16_t b) {
    uint16_t r=0, i;

    for ( i = 0; i < 16; i += 4 ) {
            r = r/16 + andlookup[(a%16)*16+(b%16)]*4096;
            a /= 16;
            b /= 16;
    }
    return r;
}

int main( void ) {
    uint16_t a = 0, b = 0;

    do {
            do {
                    if ( AND(a,b) != (a&b) ) return printf( "AND error\n" );
                    if ( IOR(a,b) != (a|b) ) return printf( "IOR error\n" );
                    if ( XOR(a,b) != (a^b) ) return printf( "XOR error\n" );
            } while ( ++b != 0 );
            if ( (a & 0xff) == 0 )
                    fprintf( stderr, "." );
    } while ( ++a != 0 );
    return 0;
}
4
Baard

Tant que vous êtes prêt à être très coûteux, oui.

Fondamentalement, vous mettra explicitement un numéro dans une représentation de base-2. Vous faites cela tout comme vous voudriez mettre un numéro dans la base-10 (par exemple, l'imprimer), c'est-à-dire par division répétée.

Cela transforme votre numéro dans un tableau de bools (ou INT dans la plage 0,1), puis nous ajoutons des fonctions à utiliser sur ces tableaux.

encore une fois, non pas que cela est extrêmement plus coûteux que les opérations binaire, et que presque toute architecture fournira des opérateurs binaire.

En C (bien sûr, en C, vous avez des opérateurs bitwises, mais ...) Une implémentation pourrait être:

include <limits.h>
const int BITWIDTH = CHAR_BIT;
typedef int[BITWIDTH] bitpattern;

// fill bitpattern with base-2 representation of n
// we used an lsb-first (little-endian) representation
void base2(char n, bitpattern array) {
  for( int i = 0 ; i < BITWIDTH ; ++i ) {
    array[i] = n % 2 ;
    n /= 2 ;
  }
}

void bitand( bitpattern op1, bitpattern op2, bitpattern result ) {
  for( int i = 0 ; i < BITWIDTH ; ++i ) {
    result[i] = op1[i] * op2[i];
  }
}


void bitor( bitpattern op1, bitpattern op2, bitpattern result ) {
  for( int i = 0 ; i < BITWIDTH ; ++i ) {
    result[i] = (op1[i] + op2[i] != 0 );
  }
}

// assumes compiler-supplied bool to int conversion 
void bitxor( bitpattern op1, bitpattern op2, bitpattern result ) {
  for( int i = 0 ; i < BITWIDTH ; ++i ) {
    result[i] = op1[i] != op2[i]  ;
  }
}
2
tpdi

Vous pouvez utiliser bit-bit (comme suggère Mark Byers), en extrayant chaque morceau qui sera lent.

Ou vous pouvez accélérer le processus et utiliser des tables de recherche 2D qui stockent des résultats, disent, pour deux opérandes de 4 bits et fonctionnent sur ceux-ci. Vous aurez besoin de moins d'extractions que si vous utilisiez des bits.

Vous pouvez également tout faire en utilisant l'addition, la soustraction et> = opération. Chaque opération bitwise peut être déroulée dans quelque chose comme celui-ci en utilisant des macros:

/*I didn't actually compile/test it, it is just illustration for the idea*/
uint16 and(uint16 a, uint16 b){
    uint16 result = 0;
    #define AND_MACRO(c) \
        if (a >= c){ \ 
            if (b >= c){\
                result += c;\
                b -= c;\
            }\
            a -= c;\
        }\
        else if (b >= c)\
            b -= c;

    AND_MACRO(0x8000)
    AND_MACRO(0x4000)
    AND_MACRO(0x2000)
    AND_MACRO(0x1000)
    AND_MACRO(0x0800)
    AND_MACRO(0x0400)
    AND_MACRO(0x0200)
    AND_MACRO(0x0100)
    AND_MACRO(0x0080)
    AND_MACRO(0x0040)
    AND_MACRO(0x0020)
    AND_MACRO(0x0010)
    AND_MACRO(0x0008)
    AND_MACRO(0x0004)
    AND_MACRO(0x0002)
    AND_MACRO(0x0001)
    #undef AND_MACRO
    return result;
}

Vous aurez besoin de 3 variables pour la mettre en œuvre.

Chaque opération bitwise va tourner autour des macros similaires à AND_MACRO - Vous comparez les valeurs restantes d'A et B sur le "masque" (qui est "C" paramètre). Ajoutez ensuite un masque au résultat dans la branche IF adaptée à votre opération. Et vous soustrayez le masque des valeurs, si le bit est défini.

Selon votre plate-forme, il peut être plus rapide que d'extraire chaque bit en utilisant% et /, puis en le ramenant en multiplication.

Voyez pour vous-même celui qui est meilleur pour vous.

2
SigTerm

Juste quelques autres approches

Par exemple A 16 bits et:

int and(int a, int b) {
    int d=0x8000;
    int result=0;
    while (d>0)  {
        if (a>=d && b>=d) result+=d;
        if (a>=d) a-=d;
        if (b>=d) b-=d;
        d/=2;
    }
    return result;
}

double solution 2 bits et sans boucles ni recherche de table:

int and(int a, int b) {
    double x=a*b/12;
    return (int) (4*(sign(ceil(tan(50*x)))/6+x));
}

entier 32 bits Solution 2 bits et:

int and(int a, int b) {
    return ((684720128*a*a -b) * a) % (b+1);
}

entier 16 bits Solution 2 bits et:

int and(int a, int b) {
    return ((121 * a) % 16) % (b+1);
}

Integer 16 bits Solution bits et:

int and(int a, int b) {
    return sign(a) * ((((-23-a) * (40+b)) % 2)+40+b) % ((10624 * ((((-23-a) * (40+b))%2)+40+b)) % (a%2 - 2 -a) - a%2 + 2 +a);
}
1
Bob Genom