Как подписать расширение 9-разрядного значения при преобразовании из 8-разрядного значения?
Я реализую функцию относительного ветвления в моей простой виртуальной машине.
По сути, мне дают 8-битное относительное значение. Затем я сдвигаю его влево на 1 бит, чтобы сделать его 9-битным значением. Так, например, если бы вы сказали "ветвь +127", это действительно означало бы 127 инструкций и, таким образом, добавило бы 256 к IP.
Мой текущий код выглядит так:
uint8_t argument = 0xFF; //-1 or whatever
int16_t difference = argument << 1;
*ip += difference; //ip is a uint16_t
Я не верю, что разница когда-либо будет обнаружена как меньше 0 с этим как бы то ни было. Я ржавый о том, как подписан на неподписанные работы. Кроме того, я не уверен, что разница будет правильно вычтена из IP в случае, если аргумент скажет -1 или -2 или что-то еще.
По сути, я хочу что-то, что удовлетворяло бы этим "тестам"
//case 1
argument = -5
difference -> -10
ip = 20 -> 10 //ip starts at 20, but becomes 10 after applying difference
//case 2
argument = 127 (must fit in a byte)
difference -> 254
ip = 20 -> 274
Надеюсь, это делает это немного более понятным.
Во всяком случае, как бы я сделал это дешево? Я видел одно "решение" подобной проблемы, но оно включало разделение. Я работаю с медленными встраиваемыми процессорами (предположительно без эффективных способов умножения и деления), так что я бы хотел этого избежать.
3 ответа
Все мои цитаты взяты из стандарта C, раздел 6.3.1.3. Неподписанный в подписанный хорошо определен, когда значение находится в пределах диапазона подписанного типа:
1 Когда значение с целочисленным типом преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно не изменяется.
Подпись к неподписанному четко определена:
2 В противном случае, если новый тип является беззнаковым, значение преобразуется путем многократного сложения или вычитания на единицу больше максимального значения, которое может быть представлено в новом типе, до тех пор, пока значение не окажется в диапазоне нового типа.
Без знака на подпись, когда значение выходит за пределы диапазона, который не слишком хорошо определен:
3 В противном случае новый тип подписывается и значение не может быть представлено в нем; либо результат определяется реализацией, либо определяется сигнал реализации.
К сожалению, ваш вопрос лежит в области пункта 3. C не гарантирует какой-либо неявный механизм для преобразования значений вне диапазона, поэтому вам нужно явно указать его. Первый шаг - решить, какое представление вы намереваетесь использовать: дополнение к единице, дополнение к двум или знак и величина.
Используемое вами представление повлияет на используемый вами алгоритм перевода. В приведенном ниже примере я буду использовать дополнение к двум: если бит знака равен 1, а все биты значения равны 0, это соответствует вашему наименьшему значению. Ваше самое низкое значение - это еще один выбор, который вы должны сделать: в случае дополнения до двух, имеет смысл использовать любой из INT16_MIN
(-32768) или INT8_MIN
(-128). В случае двух других, имеет смысл использовать INT16_MIN - 1
или же INT8_MIN - 1
из-за наличия отрицательных нулей, которые, вероятно, следует переводить, чтобы они были неотличимы от обычных нулей. В этом примере я буду использовать INT8_MIN
, так как это имеет смысл, что (uint8_t) -1
следует перевести на -1 как int16_t
,
Отделяйте бит знака от битов значения. value
должно быть абсолютным значением, за исключением случая, когда минимальное значение дополнения до двух sign
будет 1 и value
будет 0. Конечно, знаковый бит может быть там, где вам нравится, хотя обычно он находится в крайней левой части. Следовательно, сдвиг вправо на 7 позиций дает обычный бит знака:
uint8_t sign = input >> 7;
uint8_t value = input & (UINT8_MAX >> 1);
int16_t result;
Если бит знака равен 1, мы назовем это отрицательным числом и добавим к INT8_MIN, чтобы построить знак, чтобы мы не оказались в той же загадке, с которой мы начали, или, что еще хуже: неопределенное поведение (какова судьба одного из других ответов).
if (sign == 1) {
result = INT8_MIN + value;
}
else {
result = value;
}
Это может быть сокращено до:
int16_t result = (input >> 7) ? INT8_MIN + (input & (UINT8_MAX >> 1)) : input;
... или еще лучше:
int16_t result = input <= INT8_MAX ? input
: INT8_MIN + (int8_t)(input % (uint8_t) INT8_MIN);
Тест знака теперь включает проверку, находится ли он в положительном диапазоне. Если это так, значение остается неизменным. В противном случае мы используем сложение и модуль для получения правильного отрицательного значения. Это довольно согласуется с языком стандарта C выше. Это хорошо работает для двоих, потому что int16_t
а также int8_t
гарантированно использовать представление дополнения до двух внутри. Тем не менее, такие типы, как int
не требуется использовать представление дополнения до двух внутри. При конвертации unsigned int
в int
например, должна быть еще одна проверка, чтобы мы рассматривали значения, меньшие или равные INT_MAX, как положительные, а значения, большие или равные (unsigned int) INT_MIN, как отрицательные. Любые другие значения должны быть обработаны как ошибки; В этом случае я рассматриваю их как нули.
/* Generate some random input */
srand(time(NULL));
unsigned int input = rand();
for (unsigned int x = UINT_MAX / ((unsigned int) RAND_MAX + 1); x > 1; x--) {
input *= (unsigned int) RAND_MAX + 1;
input += rand();
}
int result = /* Handle positives: */ input <= INT_MAX ? input
: /* Handle negatives: */ input >= (unsigned int) INT_MIN ? INT_MIN + (int)(input % (unsigned int) INT_MIN)
: /* Handle errors: */ 0;
Если смещение находится в представлении дополнения 2, то
преобразовать это
uint8_t argument = 0xFF; //-1
int16_t difference = argument << 1;
*ip += difference;
в это:
uint8_t argument = 0xFF; //-1
int8_t signed_argument;
signed_argument = argument; // this relies on implementation-defined
// conversion of unsigned to signed, usually it's
// just a bit-wise copy on 2's complement systems
// OR
// memcpy(&signed_argument, &argument, sizeof argument);
*ip += signed_argument + signed_argument;
Чтобы уточнить: вы беспокоитесь, что сдвиг влево отрицательного 8-битного числа сделает его похожим на положительное 9-битное число? Просто добавьте верхние 9 бит со знаковым битом начального числа перед сдвигом влево:
diff = 0xFF;
int16 diff16=(diff + (diff & 0x80)*0x01FE) << 1;
Теперь ваш diff16
подписано 2*diff
Как отметил Ричард Дж. Росс III, вы можете избежать умножения (если это дорого для вашей платформы) с условным переходом:
int16 diff16 = (diff + ((diff & 0x80)?0xFF00:0))<<1;
Если вы беспокоитесь о вещах, находящихся в пределах досягаемости и т. Д. ("Неопределенное поведение"), вы можете сделать
int16 diff16 = diff;
diff16 = (diff16 | ((diff16 & 0x80)?0x7F00:0))<<1;
Ни в коем случае это не производит числа, которые выходят за пределы диапазона.
Однако самое чистое решение, похоже, "бросить и сдвинуть":
diff16 = (signed char)diff; // recognizes and preserves the sign of diff
diff16 = (short int)((unsigned short)diff16)<<1; // left shift, preserving sign
Это дает ожидаемый результат, потому что компилятор автоматически заботится о знаковом бите (поэтому нет необходимости в маске) в первой строке; а во второй строке выполняется сдвиг влево на целое число без знака (для которого переполнение четко определено в соответствии со стандартом); окончательное приведение к short int
гарантирует, что число правильно интерпретируется как отрицательное. Я считаю, что в этой форме конструкция никогда не является "неопределенной".