Знак расширяет девятибитное число в C
У меня короткий, instr
, который выглядит следующим образом:
1110xxx111111111
Мне нужно вытащить биты 0-9, что я делаю с (instr & 0x1FF)
. Затем это количество сохраняется в новой короткой форме. Проблема в том, что когда это происходит, оно становится 0x0000000111111111
, а не 0x1111111111111111
, как я хочу. Как я могу это исправить? Спасибо!
ИЗМЕНИТЬ
Здесь код:
short instr = state->mem[state->pc];
unsigned int reg = instr >> 9 & 7; // 0b111
state->regs[reg] = state->pc + (instr & 0x1FF);
Это симулятор, который читается в сборке. state
- это машина, regs[]
- регистры, а pc
- адрес текущей команды в mem[]
.
Это нормально, если последние девять бит представляют собой положительное число, но если они представляют -1, они сохраняются как все 1, что интерпретируется как положительное значение моим кодом.
Ответы
Ответ 1
Вы можете сделать это вручную: (instr & 0x1FF) | ((instr & 0x100) ? 0xFE00 : 0)
. Это проверяет бит знака (самый верхний бит, который вы сохраняете, 0x100
), и устанавливает все биты над ним, если бит знака установлен. Вы можете расширить это до 5 бит, адаптировав маски к 0x1F
, 0x10
и 0xFFE0
, будучи младшими 5 битами, 5-й бит и все биты 5-16 соответственно.
Или вы можете найти какое-то оправдание для присвоения бит верхней части подписанного коротки и сдвинуть их вниз (получение расширения знака в процессе): short x = (instr & 0x1FF) << 7; x >>= 7;
Последний может фактически оказаться более простым в сборе и не будет включать ветку. Если знак instr
подписан, это можно сделать в одном выражении: (instr & 0x1FF) << 7 >> 7
. Поскольку это уже удаляет верхние биты, он упрощается до instr << 7 >> 7
. Замените 7 на 11 для 5 бит (16-5).
Ответ 2
* Не требуется разветвление *
См. http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend для списка очень полезных бит-хаков. В частности, знак, расширяющий число, прост, как:
/* generate the sign bit mask. 'b' is the extracted number of bits */
int m = 1U << (b - 1);
/* Transform a 'b' bits unsigned number 'x' into a signed number 'r' */
int r = (x ^ m) - m;
Вам может потребоваться очистить верхние биты "x", если они не равны нулю (x = x & ((1U << b) - 1);
), прежде чем использовать описанную выше процедуру.
Если количество бит "b" известно во время компиляции (например, 5 бит в вашем случае), существует даже более простое решение (это может вызвать специальную инструкцию для расширения знака, если процессор поддерживает ее, а компилятор - умный достаточно):
struct {signed int x:5;} s;
r = s.x = x;
Ответ 3
(instr & 0x1FF) * (1 - ((unsigned short)(instr & 0x100) >> 7))
Как это работает? Он выбирает бит знака и сдвигает его на 2 позиции. Это используется для создания либо значения 1 (если ваш бит знака отсутствует), либо -1 (если ваш бит знака присутствовал).
Это решение является ветвящимся и не зависит от поведения undefined.
Ответ 4
Я не уверен, как вы получаете 13 1 бит после маскировки с помощью 0x1ff
, но это должно подписать расширение 9-разрядного числа в 16-разрядное короткое. Не красиво (или особенно эффективно), но он работает:
(instr & 0x1ff) | (0xfe00 * ((instr & 0x100) >> 8))
Вычеркните знаковый бит, переместитесь в позицию 1, чтобы получить 0/1. Умножьте это на верхние биты, если знак равен 1, тогда 9-битное число будет OR'ed с 0xfe
, которое установит все верхние биты в 1.
Ответ 5
Просто наткнулся на это, ища что-то еще, может быть, немного поздно, но, возможно, это будет полезно для кого-то другого. AFAIAC все программисты C должны начать программировать ассемблер.
В любом случае расширение знака намного проще, чем другие 2 предложения. Просто убедитесь, что вы используете подписанные переменные, а затем используете 2 смены.
short instr = state->mem[state->pc];
unsigned int reg = (instr >> 9) & 7; // 0b111
instr &= 0x1ff; // get lower 9 bits
instr = ((instr << 7) >> 7); // sign extend
state->regs[reg] = state->pc + instr;
код >
Если переменная подписана, компилятор C переводит → в Арифметический сдвиг вправо, который сохранил знак. Это поведение не зависит от платформы.
Итак, если предположить, что instr начинается с 0x1ff, то мы имеем < < 7 будет SL (сдвинуть влево) значение, так что instr теперь 0xff80, тогда → 7 будет ASR значение, так что instr теперь 0xffff.
Ответ 6
Это скорее уточнение предыдущих ответов, но пока не представлено полностью общее решение. Этот макрос будет подписывать значение v
с sb
, обозначающим бит-бит на основе знака.
#define SIGNEX(v, sb) ((v) | (((v) & (1 << (sb))) ? ~((1 << (sb))-1) : 0))
int32_t x;
SIGNEX(x, 15); // Sign bit is bit-15 (16th from the right)
SIGNEX(x, 23); // Sign bit is bit-23 (24th from the right)
Он использует ветвление, чтобы максимизировать переносимость на платформах, которым не хватает аппаратного умножения или баррелей.
Ответ 7
Более простым решением является то, что для x
, являющегося 5-битным 2 номером дополнения, посмотрите:
z = (x^16)-16