Est-il possible d'implémenter des opérateurs bitwise en utilisant l'arithmétique entière?

je suis confronté à un problème assez particulier. Je travaille sur un compilateur pour une architecture qui ne supporte pas les opérations bitwise. Cependant, il gère signé 16 bits entier arithmétique et je me demandais s'il serait possible d'implémenter des opérations bitwise en utilisant seulement:

  • Addition ( c = A + b )
  • soustraction ( c = A - b )
  • Division ( c = A / B )
  • Multiplication ( c = a * b )
  • module ( 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. )
  • Sauts ( goto, de, he.C. )

les opérations bitwise que je veux pouvoir supporter sont:

  • Ou ( c = a /b )
  • et ( c = A & b )
  • Xor ( c = A ^ B )
  • poste de gauche ( c = a << b )
  • poste de droite ( c = a > > b )
    • problem)
  • poste signé ( c = A >>> b )
  • Un Complément ( a = ~b )
    • (déjà trouvé une solution, voir ci-dessous)

Normalement, le problème est l'inverse; comment obtenir des optimisations arithmétiques en utilisant des piratages bitwise. Cependant pas dans ce cas.

Écriture de la mémoire est très rares sur cette architecture, d'où la nécessité pour les opérations bit à bit. Les fonctions bitwise elles-mêmes ne devraient pas utiliser beaucoup de variables temporaires. Cependant, la mémoire constante de données et d'instructions en lecture seule est abondante. Une note secondaire ici aussi est que les sauts et les branches ne sont pas chers et toutes les données sont facilement cachées. Les sauts coûtent la moitié des cycles, comme le font les instructions arithmétiques (y compris la charge et le stockage). Sur autrement dit, toutes les fonctions supportées ci-dessus coûtent deux fois les cycles d'un seul saut.

, Quelques idées qui pourraient vous aider:


j'ai compris que vous pouvez faire le complément de l'un (négate bits) avec le code suivant:

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

je me souviens aussi de l'ancien hack shift lors de la division avec une puissance de deux de sorte que le bitwise shift peut être exprimé as:

// 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 bitwise je suis un peu paumé. J'aurais aimé que les architectes de cette architecture aient fourni des opérations bit.

je voudrais aussi savoir s'il existe un moyen rapide/facile de calculer la puissance de deux (pour les opérations de décalage) sans utiliser une 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 Set & Jump:

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;
}
48
demandé sur Statement 2010-06-06 05:03:41

6 réponses

premières solutions pour le déplacement (Déplacement est la distance du déplacement, ne doit pas être négative, a est l'opérande à déplacer et contient également le résultat lorsqu'il est fait). La table de puissance est utilisée par les trois équipes.

// 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, ou et XOR Je ne pouvais pas trouver une solution simple, donc je vais le faire avec boucle sur chaque bit simple. Il pourrait y avoir un meilleur truc pour faire ça. Le Pseudocode suppose que a et b sont des opérandes d'entrée, c est la valeur de résultat, x est le compteur de boucles (chaque boucle doit tourner 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;
}

thats supposant que toutes les variables sont 16 bits et toutes les opérations se comportent comme signé (donc a<0 est en fait vrai quand le bit 15 est défini).

EDIT: j'ai en fait testé toutes les valeurs possibles de l'opérande (-32768 à 32767) pour des décalages allant de 0 à 31 pour l'exactitude et cela fonctionne correctement (en supposant des divisions entières). 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.

21
répondu Durandal 2015-01-26 10:42:41

dans cet environnement, il pourrait être préférable si vous pouviez configurer pour réellement utiliser des opérateurs arithmétiques pour éplucher les composants des entiers.

E. G.

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

les transformations pour ces opérateurs sont assez évidentes si vous limitez RHS à une puissance constante de 2.

enlever deux ou quatre bits est également facile à faire.

4
répondu Joshua 2010-06-06 01:36:44

Une réponse incomplète sur une vieille question, ici, à se concentrer ET, OU, XOR. Une fois qu'une solution est trouvée pour l'une de ces opérations, les deux autres peuvent être dérivées. Il y a plusieurs façons, une est montrée dans le programme de test suivant (compilé sur la version 4.6.3 de gcc (Ubuntu / Linaro 4.6.3-1ubuntu5)):

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

#define XOR(a,b) (a + b - 2*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
répondu Baard 2015-04-25 11:30:04

vous pouvez opérer petit à petit (comme Mark Byers l'a suggéré), en extrayant tout ce qui sera lent.

ou vous pourriez accélérer le processus et utiliser des tables de recherche 2d qui stockent les résultats, disons, pour deux opérandes 4-bits et opérer sur ceux. Vous aurez besoin de moins d'extractions que si vous opériez sur des bits.

vous pouvez également tout faire en utilisant addition, soustraction et >= opération. Chaque opération de bitwise peut être déroulée dans quelque chose comme ça utilisation de 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 implémenter ceci.

chaque opération bitwise tournera autour de macros similaires à AND_MACRO - vous comparez les valeurs restantes de a et b au" masque "(qui est le paramètre" c"). Ensuite, ajoutez un masque au résultat dans la branche si qui convient à votre opération. Et vous soustrayez le masque des valeurs, si bit est défini.

selon votre plate-forme, il peut être plus rapide que extraire chaque bit en utilisant % et / , puis le remettre en utilisant la multiplication.

voyez par vous-même ce qui est le mieux pour vous.

2
répondu SigTerm 2010-06-06 02:34:19

tant que vous voulez que ce soit très cher, oui.

fondamentalement, vous allez explicitement mettre un nombre dans une représentation de base-2. Vous faites cela tout comme vous mettez un nombre dans la base-10 (par exemple, pour l'imprimer), c'est-à-dire, par division répétée.

cela transforme votre nombre en un tableau de bools (ou ints dans la gamme 0,1), puis nous ajoutons des fonctions pour fonctionner sur ces tableaux.

encore une fois, non pas que ce soit beaucoup plus cher que les opérations bitwise, et que presque n'importe quelle architecture fournira des opérateurs bitwise.

en C (bien sûr, en C vous avez des opérateurs bitwise, mais...) une mise en œuvre 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
répondu tpdi 2010-06-06 03:08:24

deux autres approches

par exemple un 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;
}

voici un drôle de 2 bits et sans boucles ou tables de recherche:

int and(int a, int b) {
    double x=a*b/12;
    return (int) (4*(sign(ceil(tan(50*x)))/6+x));
}
0
répondu Bob Genom 2018-01-15 22:11:33