Czy możliwe jest zaimplementowanie operatorów bitowych przy użyciu arytmetyki całkowitej?

Stoję przed dość osobliwym problemem. Pracuję nad kompilatorem dla architektury, która nie obsługuje operacji bitowych. Jednak obsługuje podpisaną 16-bitową arytmetykę całkowitą i zastanawiałem się, czy można zaimplementować operacje bitowe używając tylko:

  • dodawanie (c = a + b )
  • odejmowanie (c = a-b )
  • Dywizja (c = a / b )
  • mnożenie (c = a * b )
  • moduł (c = a % b )
  • Minimum (c = min (a, b))
  • maksimum (c = max (A, b))
  • porównania (c = (A )
  • skoki (goto, for, et.c.)

Operacje bitowe, które chcę obsługiwać, to:

  • lub (c = a / b )
  • i (c = a & b )
  • Xor (c = a ^ b )
  • Left Shift (c = a )
  • przesunięcie w prawo (c = a > > b )
    • (wszystkie liczby całkowite są podpisane, więc jest to problem)
  • Signed Shift (c = a > > > b )
  • dopełniacz (a = ~ b )
    • (już znalazłem rozwiązanie, zobacz poniżej)

Zwykle problem jest odwrotnie; jak osiągnąć optymalizację arytmetyczną za pomocą bitowych hacków. Jednak nie w tym przypadku.

Pamięć zapisywalna jest bardzo rzadka w tej architekturze, stąd potrzeba operacji bitowych. Same funkcje bitowe nie powinny używać wielu zmiennych tymczasowych. Jednak stała pamięć danych i instrukcji tylko do odczytu jest obfita. Na marginesie należy również zauważyć, że skoki i gałęzie nie są drogie a wszystkie dane są łatwo buforowane. Skoki kosztują połowę cykli jak instrukcje arytmetyczne (w tym load/store). Innymi słowy, wszystkie powyższe obsługiwane funkcje kosztują dwa razy cykle pojedynczego skoku.

Kilka myśli, które mogą pomóc:


Doszedłem do wniosku, że można wykonać dopełniacz (negować bity) następującym kodem:

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

Pamiętam też stare Shift hack przy dzieleniu z potęgą dwóch, aby bitowe przesunięcie mogło być wyrażona jako:

// 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;

Przez resztę bitowych operacji jestem nieco nieświadomy. Szkoda, że architekci tej architektury nie dostarczyli bit-operacji.

Chciałbym również wiedzieć, czy istnieje szybki/łatwy sposób obliczania mocy dwóch (dla operacji zmianowych) bez użycia tabeli danych pamięci. Naiwnym rozwiązaniem byłoby wskoczenie w pole mnożenia:

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;
}

Lub podejście 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;
}
Author: Statement, 2010-06-06

6 answers

Pierwsze rozwiązania przesunięcia (przesunięcie jest odległością przesunięcia, nie może być ujemne, a jest operandem, który ma być przesunięty i zawiera również wynik po wykonaniu). Tabela mocy jest używana przez wszystkie trzy operacje zmiany.

// 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];
    }
}

Dla AND, OR I XOR nie mogłem wymyślić prostego rozwiązania, więc zrobię to z zapętleniem każdego bitu. Może jest na to lepsza sztuczka. Pseudokod zakłada, że a i b są operandami wejściowymi, c jest wartością wyniku, x jest licznikiem pętli (każda pętla musi Uruchom dokładnie 16 razy):

// 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;
}

To przy założeniu, że wszystkie zmienne mają 16 bitów i wszystkie operacje zachowują się jak podpisane (więc

EDIT: właściwie przetestowałem wszystkie możliwe wartości operandu (od -32768 do 32767) dla przesunięć w zakresie od 0 do 31 dla poprawności i działa poprawnie (zakładając dzielenie liczb całkowitych). Dla kodu i/lub/XOR wyczerpujący test trwa zbyt długo na moim komputerze, ale ponieważ kod do nich jest dość prosty, nie powinno być przypadków edge w każdym razie.

 21
Author: Durandal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-26 10:42:41

W tym środowisku najlepiej byłoby skonfigurować operatory arytmatyczne do oddzielania składników liczb całkowitych.

E. G.

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

Transformaty dla tych operatorów są wystarczająco oczywiste, jeśli ograniczysz RHS do stałej mocy 2.

Odrywanie dwóch lub czterech bitów jest również łatwe do zrobienia.

 4
Author: Joshua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-06 01:36:44

Niekompletna odpowiedź na Stare pytanie, tutaj koncentrując się na i, lub, XOR. Po znalezieniu rozwiązania dla jednej z tych operacji bitowych można uzyskać dwie pozostałe. Jest kilka sposobów, jeden jest pokazany w następującym programie testowym (skompilowanym na gcc w wersji 4.6.3 (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
Author: Baard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 11:30:04

Można operować bit po bitach (jak zasugerował Mark Byers), wydobywając każdy bit, który będzie wolny.

Lub możesz przyspieszyć proces i użyć tabel wyszukiwania 2d, które przechowują wyniki, powiedzmy, dla dwóch 4-bitowych operandów i działają na nich. Będziesz potrzebował mniej ekstrakcji niż gdybyś operował na bitach.

Możesz również zrobić wszystko za pomocą dodawania, odejmowania i operacji>=. Każda operacja bitowa może być rozwinięta do czegoś takiego za pomocą makr:

/*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;
}

Będziesz potrzeba 3 zmiennych, aby to zaimplementować.

Każda operacja bitowa będzie obracać się wokół makr podobnych do AND_MACRO - porównujesz pozostałe wartości a i b do "maski" (która jest parametrem "c"). następnie dodaj maskę do wyniku w gałęzi if, która jest odpowiednia dla twojej operacji. I odejmujesz maskę od wartości, jeśli bit jest ustawiony.

W zależności od platformy może to być szybsze niż ekstrakcja każdego bitu za pomocą % i/, a następnie odłożenie go z powrotem za pomocą mnożenia.

Zobacz dla siebie, cokolwiek jest dla ciebie lepsze.

 2
Author: SigTerm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-06 02:34:19

Tak długo, jak jesteś skłonny, żeby to było bardzo drogie, tak.

Ogólnie rzecz biorąc, wyraźnie umieścisz liczbę w reprezentacji Bazy-2. Robisz to tak, jak wstawiasz liczbę do bazy-10 (np. aby ją wydrukować), czyli powtarzając dzielenie.

To zamienia Twoją liczbę w tablicę boolów (lub ints w zakresie 0,1), następnie dodajemy funkcje do operowania na tych tablicach.

Nie, że jest to znacznie droższe niż operacje bitowe, i że prawie każda architektura będzie dostarczać operatory bitowe.

W C (Oczywiście w C masz operatory bitowe, ale...) implementacja może być:

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
Author: tpdi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-06 03:08:24

Tylko dwa inne podejścia

Na przykład 16 bitowe i:

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;
}

Oto zabawny 2 Bit i bez pętli lub wyszukiwania tabeli:

int and(int a, int b) {
    double x=a*b/12;
    return (int) (4*(sign(ceil(tan(50*x)))/6+x));
}
 0
Author: Bob Genom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-15 22:11:33