Знак расширяет девятибитное число в 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