#c #embedded #c99 #twos-complement
#c #встроенный #c99 #двойки-дополнение
Вопрос:
У меня есть несколько битов (количество битов может меняться) в беззнаковом int (uint32_t). Например (12 бит в примере):
uint32_t a = 0xF9C;
Биты представляют собой значение int со знаком такой длины.
В этом случае число в десятичной системе счисления должно быть -100.
Я хочу сохранить переменную в переменной со знаком и получить фактическое значение.
Если я просто использую:
int32_t b = (int32_t)a;
это будет просто значение 3996, поскольку оно преобразуется в (0x00000F9C), но на самом деле оно должно быть (0xFFFFFF9C)
Я знаю один способ сделать это:
union test
{
signed temp :12;
};
union test x;
x.temp = a;
int32_t result = (int32_t) x.temp;
теперь я получаю правильное значение -100
Но есть ли лучший способ сделать это? Мое решение не очень гибкое, как я уже упоминал, количество битов может варьироваться (что угодно от 1 до 64 бит).
Комментарии:
1. Сколько именно бит?
2. Вам действительно нужно знать, сколько бит, чтобы вы могли узнать знак.
3. в этом примере 12 бит. Но это может измениться
4. Ну, я всегда знаю, сколько битов имеет переменная со знаком. Я внедряю протокол связи. Длина каждой переменной определена. Итак, я знаю, что эта переменная имеет 12 бит, но другие переменные могут иметь разную длину. Итак, я могу использовать решение, о котором я упоминал, но тогда мне нужно объединение для всех размеров от 1-64
5. Как насчет проверки, установлен ли бит со знаком в зависимости от количества битов (сдвиньте свою маску в зависимости от количества битов), затем инвертируйте эту маску, чтобы выбрать значение a. Затем приведите значение к b . и установите бит b со знаком на основе того, что вы извлекли ранее.
Ответ №1:
Но есть ли лучший способ сделать это?
Ну, зависит от того, что вы подразумеваете под «лучше». Приведенный ниже пример демонстрирует более гибкий способ сделать это, поскольку размер битового поля не является фиксированным. Если для вашего варианта использования требуются другие размеры битов, вы могли бы рассмотреть это как «лучший» способ.
unsigned sign_extend(unsigned x, unsigned num_bits)
{
unsigned f = ~((1 << (num_bits-1)) - 1);
if (x amp; f) x = x | f;
return x;
}
int main(void)
{
int x = sign_extend(0xf9c, 12);
printf("%dn", x);
int y = sign_extend(0x79c, 12);
printf("%dn", y);
}
Вывод:
-100
1948
Комментарии:
1. Без перехода / теста:
unsigned sign_extend(unsigned x, unsigned num_bits){unsigned high = 1 << (num_bits-1);x amp;= (1 << num_bits)-1;return (x ^ high) - high;}
Ответ №2:
Способ расширения битового поля без использования ветвей для подписи (Генри С. Уоррен-младший, CACM v20 n6 июня 1977) заключается в следующем:
// value i of bit-length len is a bitfield to sign extend
// i is right aligned and zero-filled to the left
sext = 1 << (len - 1);
i = (i ^ sext) - sext;
ОБНОВЛЕНИЕ на основе комментария @Lundin
Вот протестированный код (выводит -100):
#include <stdio.h>
#include <stdint.h>
int32_t sign_extend (uint32_t x, int32_t len)
{
int32_t i = (x amp; ((1u << len) - 1)); // or just x if you know there are no extraneous bits
int32_t sext = 1 << (len - 1);
return (i ^ sext) - sext;
}
int main(void)
{
printf("%dn", sign_extend(0xF9C, 12));
return 0;
}
Комментарии:
1. Я понятия не имею, кто такой Генри С. Уоррен-младший, но это не работает и дает неверный результат… Какая логика за этим стоит
1 << (len - 1)
? Ты на самом деле имел в виду(1 << len)-1
?2. Генри С. Уоррен-младший, помимо прочего, является автором книги «Хакерский восторг». Это отлично работает, я использовал это много раз. Возможно, мне следовало указать, что
i
иsext
должны иметь подписанные типы.1 << (len - 1)
является маской для знакового бита.3. Да, этот код действительно полностью отличается от того, что вы опубликовали в первый раз. Но этот код также вызывает неопределенное поведение в случае, если len равен 31 или 32, поскольку можно ожидать, что это допустимый ввод для 32-разрядных чисел без знака. Кроме того, оно не читается. Он также выполняется неоправданно медленно, сравните его, например, с кодом, который я только что создал на ровном месте: godbolt.org/z/v9-8o5 .
4. Кажется, вы пропустили мой комментарий к ответу @ 4386427 :).
5. @Lundin обе подпрограммы выполняют разные функции, зачем их сравнивать?
Ответ №3:
Это зависит от поведения расширения знака, определенного реализацией, при сдвиге вправо целых отрицательных чисел со знаком. Сначала вы сдвигаете целое число без знака полностью влево, пока знаковый бит не станет MSB, затем преобразуете его в целое число со знаком и сдвигаете обратно:
#include <stdio.h>
#include <stdint.h>
#define NUMBER_OF_BITS 12
int main(void) {
uint32_t x = 0xF9C;
int32_t y = (int32_t)(x << (32-NUMBER_OF_BITS)) >> (32-NUMBER_OF_BITS);
printf("%dn", y);
return 0;
}
Комментарии:
1. определенная реализация означает, что она не будет работать со всеми компиляторами?
2. ДА. Расширение sign является одним из вариантов, оставленных для реализации компилятора стандартом C. Но вы также должны отметить, что представление отрицательных чисел также является таким выбором, поэтому
0xFFFFFF9C
не будет-100
с каждым компилятором.3. @EugeneSh. Однако типы фиксированной длины, такие как
int32_t
, гарантированно дополняют друг друга. Нет гарантии, что они будут существовать, но если они будут, они будут дополнять друг друга.4. @ChristianGibbons Вы правы, спасибо, что указали. Я всегда забываю об этом.
5. Вам не нужно беспокоиться, доступны ли типы stdint.h, но писать код, который полагается на поведение, определяемое реализацией, просто плохо. По моему опыту, сдвиги вправо, в частности, непереносимы, примерно на 50% / 50% между арифметическими или логическими сдвигами между реализациями.
Ответ №4:
Это решение вашей проблемы:
int32_t sign_extend(uint32_t x, uint32_t bit_size)
{
// The expression (0xffffffff << bit_size) will fill the upper bits to sign extend the number.
// The expression (-(x >> (bit_size-1))) is a mask that will zero the previous expression in case the number was positive (to avoid having an if statemet).
return (0xffffffff << bit_size) amp; (-(x >> (bit_size-1))) | x;
}
int main()
{
printf("%dn", sign_extend(0xf9c, 12)); // -100
printf("%dn", sign_extend(0x7ff, 12)); // 2047
return 0;
}
Ответ №5:
Разумный, переносимый и эффективный способ сделать это — просто замаскировать часть данных, затем заполнить все остальное 0xFF … чтобы получить правильное представление дополнения 2. Вам нужно знать, сколько битов является частью данных.
- Мы можем замаскировать данные с помощью
(1u << data_length) - 1
. - В этом случае с помощью
data_length = 8
маска данных становится0xFF
. Давайте назовем этоdata_mask
. - Таким образом, часть данных числа является
a amp; data_mask
. - Остальную часть числа необходимо заполнить нулями. То есть все, что не является частью маски данных. Просто сделайте
~data_mask
для достижения этого. - C-код:
a = (a amp; data_mask) | ~data_mask
. Теперьa
является правильным дополнением 32-битного 2.
Пример:
#include <stdio.h>
#include <inttypes.h>
int main(void)
{
const uint32_t data_length = 8;
const uint32_t data_mask = (1u << data_length) - 1;
uint32_t a = 0xF9C;
a = (a amp; data_mask) | ~data_mask;
printf("%"PRIX32 "t%"PRIi32, a, (int32_t)a);
}
Вывод:
FFFFFF9C -100
Это зависит от того, int
что это 32-битное дополнение 2, но в остальном оно полностью переносимо.
Комментарии:
1. Этот код не использует расширение знака, он слепо делает ввод отрицательным. И
a amp; data_mask
перед вводом~data_mask
бесполезно.2. @Nipo Чтобы весь этот вопрос имел смысл, вы уже должны знать, что 0xF9C является дополнением signed 2. Вы не можете знать, подписано ли какое-либо случайное число или нет.
3. @Nipo Чтобы пояснить это для вас, вы не можете подписывать расширение числа, которого еще нет в переменной типа со знаком, и установить его отрицательным. Вы не можете взять какой-то случайный фрагмент двоичного файла и «подписать расширение» его. Преобразование данных в знаковые биты целого числа со знаком вызывает неопределенное поведение, и программа может аварийно завершать работу.
4. Вы, кажется, забываете, что число со знаком на самом деле может быть положительным. Если мы расширим 0x2a с 7 бит до 32, я на самом деле ожидаю получить 0x0000002a, а не 0xffffffaa. Последнее — это то, что мы получим из вашего кода.
5. @Lundin, поищи в Википедии «расширение знака».’ Второй абзац таков: например, если для представления числа «00 1010» используются шесть битов (десятичное положительное значение 10), а операция расширения знака увеличивает длину слова до 16 бит, то новое представление будет просто «0000 0000 0000 1010». Таким образом, сохраняется как значение, так и тот факт, что значение было положительным.