Целочисленное вычитание с оберткой для N бит
В принципе, поведение, которое вы получаете при переполнении целых чисел с вычитанием, но для заданного количества бит. Очевидный способ, предполагающий подписанное целое число:
template <int BITS>
int sub_wrap(int v, int s) {
int max = (1<<(BITS));
v -= s;
if (v < -max) v += max*2;
// or if branching is bad, something like:
// v += (max*2) * (v < -max)
return v;
}
// For example subtracting 24 from -16 with 5 bit wrap,
// with a range of -32, 31
sub_wrap<5>(-16, 28); -> 20
Есть ли опрятный способ сделать это менее уродливым и желательно быстрее, чем выше?
ОБНОВЛЕНИЕ: Извините за путаницу. Я бездумно включил путаную нотацию использования количества бит, исключая бит взрыва. Таким образом, в приведенном выше примере замените 5 бит на 6 бит для более разумного использования.
Ответы
Ответ 1
Для беззнаковой арифметики и маскирования результатов, например:
template<int bits>
unsigned
sub_wrap( unsigned v, unsigned s )
{
return (v - s) & ((1 << bits) - 1);
}
В более общем плане вы можете использовать оператор modulo:
template<int modulo>
unsigned
sub_wrap( unsigned v, unsigned s )
{
return (v - s) % modulo;
}
(Обертка на битах n
эквивалентна по модулю 2 ^ n.)
Для подписанной арифметики это немного сложнее; используя маску, вам придется подписать расширенные результаты (предположим, что 2 дополнения).
EDIT: использование предложения для подписанной арифметики:
template<int bits>
int
sub_wrap( int v, int s )
{
struct Bits { signed int r: bits; } tmp;
tmp.r = v - s;
return tmp.r;
}
Учитывая это, sub_wrap<5>( -16, 28 )
дает -12
(что является правильным, обратите внимание, что 28
не может быть представлено как подписанный int в 5 битах); sub_wrap<6>( -16, 28 )
дает 20
.
Ответ 2
Я полагаю, это должно сработать:
struct bits
{
signed int field : 5;
};
bits a = { -16 };
bits b = { 28 };
bits c = { a.field - b.field };
std::cout << c.field << std::endl;
Я уверен, что ширина поля не будет работать с аргументом const template... и, следовательно, это будет менее общим. Однако он должен избегать ручного возиться. Скоро будет опубликован тест
Обновить. В конце концов, мой ответ не был некорректным. Просто ввод образца (28) не может быть представлен в 5 битах (подписан). Результат выше -12 (см. http://ideone.com/AUrXy).
Вот, для полноты, шаблонная версия в конце концов:
template<int bits>
int sub_wrap(int v, int s)
{
struct helper { signed int f: bits; } tmp = { v };
return (tmp.f -= s);
}
Ответ 3
Вот как я буду делать это без условных ветвей и умножения:
#include <stdio.h>
// Assumptions:
// - ints are 32-bit
// - signed ints are 2 complement
// - right shifts of signed ints are sign-preserving arithmetic shifts
// - signed overflows are harmless even though strictly speaking they
// result in undefined behavior
//
// Requirements:
// - 0 < bits <= 32
int sub_wrap(int v, int s, unsigned bits)
{
int d = v - s;
unsigned m = ~0u >> (32 - bits);
int r = d & m | -((d >> (bits - 1)) & 1) & ~m;
return r;
}
#define BITS 2
int main(void)
{
int i, j;
for (i = -(1 << (BITS - 1)); i <= (1 << (BITS - 1)) - 1; i++)
for (j = -(1 << (BITS - 1)); j <= (1 << (BITS - 1)) - 1; j++)
printf("%d - %d = %d\n", i, j, sub_wrap(i, j, BITS));
return 0;
}
Вывод:
-2 - -2 = 0
-2 - -1 = -1
-2 - 0 = -2
-2 - 1 = 1
-1 - -2 = 1
-1 - -1 = 0
-1 - 0 = -1
-1 - 1 = -2
0 - -2 = -2
0 - -1 = 1
0 - 0 = 0
0 - 1 = -1
1 - -2 = -1
1 - -1 = -2
1 - 0 = 1
1 - 1 = 0
Ответ 4
Это моделирует операцию n-целого числа:
#include <iostream>
#include <cstdlib>
template< typename T >
T sub_wrap(T a, T b, int nBits)
{
T topBit, mask, tmp;
topBit=T(1) << (nBits-1);
mask=(topBit << 1)-1;
tmp=((a&mask)+((~b+1)&mask))&mask;
if (tmp & topBit) tmp=-((~tmp&mask)+1);
return tmp;
}
int main(int argc, char* argv[])
{
std::cout << sub_wrap< int >(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]))
<< std::endl;
return 0;
}
Результаты:
$ ./sim 5 6 4
-1
$ ./sim 7 3 4
4
$ ./sim 7 -1 4
-8
$ ./sim -16 28 4
4
$ ./sim -16 28 5
-12
$ ./sim -16 28 6
20
Кажется, вы просчитали свой размер типа на 1 бит.