Каков наиболее эффективный способ вычитания целочисленных данных со знаком в двоичном формате (биты)?

Я работаю на C на ПК, пытаясь использовать как можно меньше С++, работая с двоичными данными, хранящимися в формате unsigned char, хотя другие форматы, безусловно, возможны, если они того стоят. Цель состоит в том, чтобы вычесть два целых числа со знаком (которые могут быть целыми, целыми со знаком, длинными, длинными со знаком, короткими со знаком и т. д.) в двоичном формате без преобразования в другие форматы данных. Тем не менее, необработанные данные просто предварительно упакованы как unsigned char, и пользователь в основном знает, какой из форматов целых чисел со знаком следует использовать для чтения (т.е. мы знаем, сколько байтов нужно прочитать за один раз). Несмотря на то, что данные хранятся в виде массива символов без знака, данные предназначены для чтения со знаком в виде целых чисел с дополнением до двух.

Один из распространенных способов, которому нас часто учат в школе, — это добавление отрицания. Отрицание, в свою очередь, часто учат выполнять как переворачивание битов и добавление 1 (0x1), в результате чего получается два сложения (может быть, это плохо?); или, как указывают другие сообщения, переворачивая биты после первого нуля, начиная с MSB. Мне интересно, есть ли более эффективный способ, который не может быть легко описан как операция с ручкой и бумагой, но работает из-за того, как данные хранятся в битовом формате. Вот несколько прототипов, которые я написал, возможно, это не самый эффективный способ, но они обобщают мой прогресс на основе методологии учебника.

Дополнения передаются по ссылке на случай, если мне придется вручную расширить их, чтобы сбалансировать их длину. Любая обратная связь будет оценена! Заранее спасибо за рассмотрение.

void SubtractByte(unsigned char* & a, unsigned int & aBytes,
              unsigned char* & b, unsigned int & bBytes,
              unsigned char* & diff, unsigned int & nBytes)
{
    NegateByte(b, bBytes);

    // a - b == a + (-b)
    AddByte(a, aBytes, b, bBytes, diff, nBytes);

    // Restore b to its original state so input remains intact
    NegateByte(b, bBytes);
}

void AddByte(unsigned char* & a, unsigned int & aBytes,
             unsigned char* & b, unsigned int & bBytes,
             unsigned char* & sum, unsigned int & nBytes)
{
    // Ensure that both of our addends have the same length in memory:
    BalanceNumBytes(a, aBytes, b, bBytes, nBytes);
    bool aSign = !((a[aBytes-1] >> 7) & 0x1);
    bool bSign = !((b[bBytes-1] >> 7) & 0x1);


    // Add bit-by-bit to keep track of carry bit:
    unsigned int nBits = nBytes * BITS_PER_BYTE;
    unsigned char carry = 0x0;
    unsigned char result = 0x0;
    unsigned char a1, b1;
    // init sum
    for (unsigned int j = 0; j < nBytes; ++j) {
        for (unsigned int i = 0; i < BITS_PER_BYTE; ++i) {
            a1 = ((a[j] >> i) & 0x1);
            b1 = ((b[j] >> i) & 0x1);
            AddBit(&a1, &b1, &carry, &result);
            SetBit(sum, j, i, result==0x1);
        }
    }

    // MSB and carry determine if we need to extend:
    if (((aSign && bSign) && (carry != 0x0 || result != 0x0)) ||
        ((!aSign && !bSign) && (result == 0x0))) {
        ++nBytes;
        sum = (unsigned char*)realloc(sum, nBytes);
        sum[nBytes-1] = (carry == 0x0 ? 0x0 : 0xFF); //init
    }
}


void FlipByte (unsigned char* n, unsigned int nBytes)
{
    for (unsigned int i = 0; i < nBytes; ++i) {
        n[i] = ~n[i];
    }
}

void NegateByte (unsigned char* n, unsigned int nBytes)
{
    // Flip each bit:
    FlipByte(n, nBytes);
    unsigned char* one = (unsigned char*)malloc(nBytes);
    unsigned char* orig = (unsigned char*)malloc(nBytes);
    one[0] = 0x1;
    orig[0] = n[0];
    for (unsigned int i = 1; i < nBytes; ++i) {
        one[i] = 0x0;
        orig[i] = n[i];
    }
    // Add binary representation of 1
    AddByte(orig, nBytes, one, nBytes, n, nBytes);
    free(one);
    free(orig);
}

void AddBit(unsigned char* a, unsigned char* b, unsigned char* c,
unsigned char* result) {
     *result = ((*a + *b + *c) & 0x1);
     *c = (((*a + *b + *c) >> 1) & 0x1);
}

void SetBit(unsigned char* bytes, unsigned int byte, unsigned int bit,
bool val)
{
    // shift desired bit into LSB position, and AND with 00000001
    if (val) {
        // OR with 00001000
        bytes[byte] |= (0x01 << bit);
    }
    else{ // (!val), meaning we want to set to 0
        // AND with 11110111
        bytes[byte] &= ~(0x01 << bit);
    }
}

void BalanceNumBytes (unsigned char* & a, unsigned int & aBytes,
                      unsigned char* & b, unsigned int & bBytes,
                      unsigned int & nBytes)
{
    if (aBytes > bBytes) {
        nBytes = aBytes;
        b = (unsigned char*)realloc(b, nBytes);
        bBytes = nBytes;
        b[nBytes-1] = ((b[0] >> 7) & 0x1) ? 0xFF : 0x00;
    } else if (bBytes > aBytes) {
        nBytes = bBytes;
        a = (unsigned char*)realloc(a, nBytes);
        aBytes = nBytes;
        a[nBytes-1] = ((a[0] >> 7) & 0x1) ? 0xFF : 0x00;
    } else {
        nBytes = aBytes;
    }
}

person Erika Electra    schedule 28.02.2012    source источник


Ответы (1)


Первое, на что следует обратить внимание, это то, что знаковый или беззнаковый не имеет значения для сгенерированного битового шаблона в дополнении до двух. Меняется только интерпретация результата.

Второе, на что следует обратить внимание, это то, что добавление выполняется, если результат меньше любого из входных данных, когда выполняется беззнаковая арифметика.

void AddByte(unsigned char* & a, unsigned int & aBytes,
             unsigned char* & b, unsigned int & bBytes,
             unsigned char* & sum, unsigned int & nBytes)
{
    // Ensure that both of our addends have the same length in memory:
    BalanceNumBytes(a, aBytes, b, bBytes, nBytes);

    unsigned char carry = 0;
    for (int j = 0; j < nbytes; ++j) { // need to reverse the loop for big-endian
        result[j] = a[j] + b[j];
        unsigned char newcarry = (result[j] < a[j] || (unsigned char)(result[j]+carry) < a[j]);
        result[j] += carry;
        carry = newcarry;
    }
}
person Mark Ransom    schedule 28.02.2012
comment
Спасибо, что напомнили мне о порядке байтов! Я всегда забываю об этом. И спасибо за понимание проверки переносов. Я думаю, что мне все еще нужно сделать проверку MSB на случай, если нам понадобится расширить ее в случае, если MSB станет 1 для положительного сложения или 0 для отрицательного сложения. - person Erika Electra; 28.02.2012
comment
Было ли что-то особенно странное в отрицании? Например, есть ли более быстрый и чистый способ, чем выделить 0x1 и добавить его к исходному значению с перевернутым битом? В очередной раз благодарим за помощь. - person Erika Electra; 29.02.2012
comment
@Cindeselia Я думаю, вы нашли единственный случай, когда вам нужно беспокоиться о подписанных и неподписанных - переносах MSB. Для беззнаковых существует переполнение, если перенос установлен после завершения цикла. Для подписанных немного сложнее - если старший бит старшего бита разный для двух входов, то переполнение невозможно, иначе происходит переполнение, если старший бит отличается между входами и выходом. Я думаю, вы прекрасно понимаете процесс отрицания, хотя в malloc нет необходимости. - person Mark Ransom; 29.02.2012