LINUX.ORG.RU

Превратить беззнаковое число в знаковое

 , ,


2

6

Язык C. Имеем на входе беззнаковое число, например, uint32_t. Но на самом деле в нем находится, например, 25 битовое знаковое число. В 25-м бите признак отрицательного числа, в 26..32 битах нули. Размер числа в битах задаётся параметром. Как бы более правильно сделать функцию типа int32_t to_signed32(uint32_t value, uint8_t bits)? Вижу вариант с кучей юнионов (по одному на каждое количество битов), ну и всякая арифметика.

★★★
Ответ на: комментарий от anonymous

Зависит от формата представления числа: прямой, обратный, или дополнительный код.

anonymous
()

Какие нафиг юнионы? Сдвигом единицы на нужное число бит получаешь маску для знакового бита. Если этот бит установлен, возвращаешь минус (число со сброшенными знаковым битом), иначе просто само число.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

С телефона неудобно. Просто -1 это 0x1ffffff (25 бит). Если сбросить знаковый бит получится 0x0FFFF. Меняем знак - получаем -0xffff а не -1

vromanov ★★★
() автор топика
Ответ на: комментарий от anonymous

Вот это хороший вариант. Но похоже с вичитанием будет проще

vromanov ★★★
() автор топика
Ответ на: комментарий от vromanov

так и пиши что это знаковое число в дополнительном коде.

А так алгоритм @slovazap верный, если это просто число и бит знака отдельно(ака число в прямом коде)

fsb4000 ★★★★★
()
Ответ на: комментарий от mittorn

UB

В с++ вполне defined, потому что знаковые числа в дополнительном коде. В си правый сдвиг знакового - implementation defined.

anonymous
()
Ответ на: комментарий от vromanov

Я понял, знаковое число в дополнительном коде. Ну тогда примерно то же самое, только вместо сброса знакового бита и изменения знака вычитание.

value & (1 << (bits - 1)) ? value - (1 << bits) : value

slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 1)
Ответ на: комментарий от slovazap

Хорошая задача. Важная. Наверняка узкое место в каком-нибудь серьезном проекте :)

Не с проста ж тег hiload

anonymous
()
Ответ на: комментарий от anonymous

Не с проста ж тег hiload

А ты думал! Хайлоад… как много в этом слове… всего такого, как в этой теме :)

anonymous
()
Ответ на: комментарий от anonymous

Если тебе надо разбирать тысячи пакетов с таким полями в секунду на слабом процессоре, то это превращается в hi-load

vromanov ★★★
() автор топика
Ответ на: комментарий от vromanov

Если тебе надо разбирать тысячи пакетов с таким полями в секунду на слабом процессоре, то это превращается в hi-load

Тогда смотрите в сторону векторных регистров.

anonymous
()
Ответ на: комментарий от vromanov

Просто -1 это 0x1ffffff (25 бит). Если сбросить знаковый бит получится 0x0FFFF. Меняем знак - получаем -0xffff а не -1

Ну хорошо, допустим что хранится там в 25 битах в типе uint32_t

0b00000000 00000000 00000000 00000000 это 0
0b00000000 00000000 00000000 00000001 это 1
...
0b00000000 11111111 11111111 11111111 это 65535

0b00000001 11111111 11111111 11111111 это -1
0b00000001 11111111 11111111 11111110 это -2
0b00000001 00000000 00000000 00000000 это -65536
т.е. дополнительный код
Так а что собственно с ним делать? Перевести в честный int32_t? Тогда надо растянуть единичку из
0b00000001 11111111 11111111 11111111 это -1
         ^
этой позиции
на оставшиеся биты. Вроде как-то так:
uint32_t val; // тут хранится значение, которое надо перевести
bool is_signed = !!(val & (1 << SIGN_BIT_POS));
return  val | is_signed ? (0xFFFFFFFF << SIGN_BIT_POS) : 0;

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

ну и можно еще сдвинуть его влево как беззнаковое на такое количество бит, чтоб знаковый бит попал на знаковый бит, т.е

0b00000001 11111111 11111111 11111111 это -1

сдвигаем на 7 влево
0b11111111 11111111 11111111 10000000

потом https://en.wikipedia.org/wiki/Arithmetic_shift на 7 вправо
0b11111111 11111111 11111111 11111111 - получаем -1 для int32

uint32_t val; // тут хранится значение, которое надо перевести
const char shift = 8 * sizeof(uint32_t) - SIGN_BIT_POS;
return  (int32_t)(val << shift) >> shift;
SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 3)
signed_ = 0xff * (x & 0x01000000) | x;
anonymous
()

Вижу вариант с кучей юнионов (по одному на каждое количество битов),

Так и делай, будет громоздко зато явно, логично и понятно

ну и всякая арифметика.

Для случая выше нахер не нужна, значения бит от битового поля явно передавай.

anonymous
()

Если исходный формат - усечённый до 25 бит дополнительный код, и 0x01ffffff соответствует -1, то:


#include <stdint.h>
#include <stdio.h>

#define NBITS 25

int32_t expandSign(uint32_t x, int nbits)
{
	return (((x >> nbits - 1) ^ 1) - 1 << nbits) | x;
}

int main ()
{
	uint32_t x[] = { 0x01ffffff, 0x01fffffe, 0x00000001 };
	
	for (unsigned i = 0; i < sizeof x / sizeof x[0]; i++)
		printf ("x=%#010x y=%d\n", x[i], expandSign (x[i], NBITS));

	return 0;
}

x=0x01ffffff y=-1
x=0x01fffffe y=-2
x=0x00000001 y=1


loki231
()

Почему не посоветовали int с определённой битностью? Из-за нюансов упаковки?

anonymous
()
Ответ на: комментарий от anonymous

та патамушо затупили, какие уж тут нюансы упаковки :)

тс, внатуре анон чётко советует:

typedef struct { int val: 25; } int25;
int32_t int25to32(uint32_t x) { return (int25){x}.val; }
anonymous
()
Ответ на: комментарий от LongLiveUbuntu

Дайте денег.

Дэнги давай, дэнги говорю давай …
… Сам туда иди.

anonymous
()
Ответ на: комментарий от anonymous

Ток я бы написал

typedef struct { signed int val: 25; } int25;
int32_t int25to32(uint32_t x) { return (int25){x}.val; }
anonymous
()
Ответ на: комментарий от anonymous

Видеокарту к микроконтролеру типа STM32 (точнее SPC58)?

vromanov ★★★
() автор топика
Ответ на: комментарий от loki231

Нам так Исплам делать запрещает. Конкретно MISRA

vromanov ★★★
() автор топика
Ответ на: комментарий от SZT

return val | is_signed ? (0xFFFFFFFF << SIGN_BIT_POS) : 0;

Процы обычно очень плохо реагируют на условный переход. Лучше выполнить больше кода, но без гаданий на бранч предикторе.

mv ★★★★★
()
Ответ на: комментарий от mv

Ну это уже от самого проца зависит, да и компилятор может это оптимизировать без условного перехода. Можно перебирать разные варианты, сравнивать скорость и выбрать самый быстрый. Или вообще на асме написать, если это очень критично.

SZT ★★★★★
()
Ответ на: комментарий от anonymous

После выяснения всех необходимых подробностей, без которых задача не имеет смысла: числа в дополнительном коде и для PowerPC, то думаю самое лучшее решение предложено в первом сообщении.

anonymous
()

В результате сделал так. Код получился еще и MISRA 2012 совместимым. Также он проходит проверку на переполнения во время исполнения (для этого сделана проверка на bits < 32U):

int32_t to_signed32 (uint32_t value, uint8_t bits) {
    uint32_t res = value;
    if (bits < 32U) {
        uint32_t sign_bit = (uint32_t) 1U << (bits - 1U);
        bool is_negative = (value & sign_bit) != 0U;
        if (is_negative) {
            res |= (0xFFFFFFFFU << bits);
        }
    }
    return (int32_t) res;
}

Test:

TEST(can_parse_utils, to_signed32) {
    EXPECT_EQ(-1, to_signed32(0x01ffffff, 25U));
    EXPECT_EQ(-2, to_signed32(0x01fffffe, 25U));
    EXPECT_EQ(1,to_signed32(0x00000001, 25U));

    EXPECT_EQ(-1, to_signed32(0xFFFFFFFF, 32U));
    EXPECT_EQ(0x7FFFFFFF, to_signed32(0x7FFFFFFF, 32U));
    EXPECT_EQ(0, to_signed32(0, 32U));

    EXPECT_EQ(-1, to_signed32(0x3, 2U));
    EXPECT_EQ(-2, to_signed32(0x2, 2U));
    EXPECT_EQ(1, to_signed32(0x1, 2U));
    EXPECT_EQ(0, to_signed32(0, 2U));
}

Code:

int32_t to_signed32 (uint32_t value, uint8_t bits) {
    uint32_t res = value;
  0x0104FB9C:   23 F4        SE_CMPLI  R4,0x20
    if (bits < 32U) {
  0x0104FB9E:   E0 0D        SE_BGE    0x0104FBB8
        uint32_t sign_bit = (uint32_t) 1U << (bits - 1U);
        bool is_negative = (value & sign_bit) != 0U;
        if (is_negative) {
  0x0104FBA0:   19 64 84 FF  E_ADDI    R11,R4,0xFFFFFFFF
  0x0104FBA4:   71 80 00 01  E_LI      R12,0x1
  0x0104FBA8:   7D 8C 58 30  SLW       R12,R12,R11
  0x0104FBAC:   7C 6A 60 39  AND.      R10,R3,R12
  0x0104FBB0:   E6 04        SE_BEQ    0x0104FBB8
            res |= (0xFFFFFFFFU << bits);
  0x0104FBB2:   2C 00        SE_BMASKI R0,0x0
  0x0104FBB4:   42 40        SE_SLW    R0,R4
  0x0104FBB6:   44 03        SE_OR     R3,R0
        }
    }
    return (int32_t) res;
}
  0x0104FBB8:   00 04        SE_BLR    
vromanov ★★★
() автор топика
Ответ на: комментарий от vromanov

ахахаха, а ещё что-то про хайлоад тут писал.

Под столом.

Анонимы ваши решения лучше, чем выбрал тс. Не парьтесь.

(и со сдвигами и с битовыми полями, компилятор там генерит один и тот же ассемблер)

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

(и со сдвигами и с битовыми полями, компилятор там генерит один и тот же ассемблер)

У битовых полей один существенный недостаток - битность указывается во время компиляции.

anonymous
()
Ответ на: комментарий от SZT

Ну это уже от самого проца зависит, да и компилятор может это оптимизировать без условного перехода. Можно перебирать разные варианты, сравнивать скорость и выбрать самый быстрый.

Очень ограниченные возможности у компилятора обойтись без условного перехода, если архитектура условную загрузку регистров не поддерживает.

Или вообще на асме написать, если это очень критично.

Без разницы, в общем-то. А вот минимизировать условия в числовых алгоритмах - это всегда полезно. Ну, кроме случаев, когда архитектура выполняет любую команду за фиксированное количество тактов.

mv ★★★★★
()
Ответ на: комментарий от mv

У нас еще одно ограничение - MISRA. Она многое запрещает. Например, нельзя свигать знаковое целое. Юнионы тоже не рекомендуются.

vromanov ★★★
() автор топика
Ответ на: комментарий от vromanov

Тебе не приходит в голову, что решения анонимов дадут такой-же код?

Ну это вряд ли. Такой код скорее всего быстрее будет

int32_t to_signed32_2 (uint32_t value, uint8_t bits) {
  if(bits < 32U)
  {
    return  (int32_t)(value << (32-bits)) >> (32-bits);
  }
  return value;
}
Хотя если сдвигать signed нельзя, это конечно проблема.

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от vromanov

С этим кодом еще такая проблема есть:

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdbool.h>

int32_t to_signed32 (uint32_t value, uint8_t bits) {
    uint32_t res = value;
    if (bits < 32U) {
        uint32_t sign_bit = (uint32_t) 1U << (bits - 1U);
        bool is_negative = (value & sign_bit) != 0U;
        if (is_negative) {
            res |= (0xFFFFFFFFU << bits);
        }
    }
    return (int32_t) res;
}

int main(void)
{
  uint32_t val = 0b100000; // шесть бит
  uint8_t bitpos = 5; // а знаковый - пятый бит
  printf("%" PRIi32"\n", to_signed32(val, bitpos)); // выведет 32, хотя должно 0
  return EXIT_SUCCESS;
}
можно ассерт какой-нибудь добавить на это

SZT ★★★★★
()
Ответ на: комментарий от vromanov

У нас еще одно ограничение - MISRA. Она многое запрещает. Например, нельзя свигать знаковое целое. Юнионы тоже не рекомендуются.

А компилятор какой? Может там какие-то интринсики есть для арифметического сдвига?

SZT ★★★★★
()
Ответ на: комментарий от vromanov

MISRA

… adherence to the MISRA standard as a whole would have made the software less reliable.

исследователи говорят, что из 72 протестированных правил только 9 уверенно положительно влияют на надёжность программы, остальные либо никак не влияют, либо ещё хуже. следование же стандарту целиком делает программу менее надёжной.

anonymous
()
Ответ на: комментарий от anonymous

С тех пор вышла новая мисра-2012. Ну и мы ей следуем не потому, что мазохисты, а потому что по проессу нужно

vromanov ★★★
() автор топика
Ответ на: комментарий от anonymous

сдвиги для лохов, кста

Как установить конкретный бит, из таблицы Брадиса брать?

та я ж пошутил :)

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.