Хранить подписанный 32-битный в неподписанном 64-битном int - PullRequest
0 голосов
/ 06 января 2020

По сути, я хочу "хранить" 32-разрядное целое число со знаком внутри (в 32 крайних правых битах) 64-разрядное целое число без знака - поскольку я хочу использовать самые левые 32-разрядные для других целей.

То, что я сейчас делаю, - это простое приведение и маска:

#define packInt32(X) ((uint64_t)X | INT_MASK)

Но у этого подхода есть очевидная проблема: если X - положительное целое число (первый бит не установлен) все идет хорошо. Если оно отрицательное, оно становится грязным.


Вопрос в следующем:

Как добиться вышеизложенного, также поддерживая отрицательные числа, в самом быстром и самый эффективный способ ?

Ответы [ 5 ]

9 голосов
/ 06 января 2020

"Беспорядок", о котором вы упоминаете, происходит потому, что вы приводите маленький тип со знаком к большому типу без знака. Во время этого преобразования размер корректируется сначала с применением расширения знака. Это то, что вызывает у вас проблемы.

Вы можете сначала просто привести (целое) целое число к типу без знака того же размера. Тогда приведение к 64 битам не вызовет расширение знака:

#define packInt32(X) ((uint64_t)(uint32_t)(X) | INT_MASK)
5 голосов
/ 06 января 2020

Вам необходимо замаскировать любые биты, кроме младших 32 бит. Вы можете сделать это с помощью побитового И:

#define packInt32(X) (((uint64_t)(X) & 0xFFFFFFFF) | INT_MASK)
1 голос
/ 06 января 2020

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

Другой вариант - создать объединение с битовым словом. Это затем переносит проблему на компилятор для оптимизации:

union {
  int64_t merged;
  struct {
     int64_t field1:32,
             field2:32;
  };
};

Третий вариант - иметь дело со знаковым битом самостоятельно. Сохраните 15-битное абсолютное значение и 1-битный знак. Не суперэффективно, но более вероятно, будет законно, если вы когда-нибудь столкнетесь с процессором, не являющимся дополнением к 2, где отрицательные знаковые значения не могут быть безопасно преобразованы в неподписанные. Они редки, как куриные зубы, поэтому я бы сам об этом не беспокоился.

1 голос
/ 06 января 2020

Отрицательное 32-разрядное целое число будет расширено до 64-разрядного знака.

#include <stdint.h>
uint64_t movsx(int32_t X) { return X; }

movsx на x86-64:

movsx:
        movsx   rax, edi
        ret

Маскировка старших 32-разрядных удалит, потому что это будет только расширение нуля:

#include <stdint.h>
uint64_t mov(int32_t X) { return (uint64_t)X & 0xFFFFFFFF; }
//or uint64_t mov(int32_t X) { return (uint64_t)(uint32_t)X; }

mov на x86-64:

mov:
        mov     eax, edi
        ret

https://gcc.godbolt.org/z/fihCmt

Ни один из методов не теряет никакой информации из младших 32-битных данных, поэтому любой из методов является допустимым способом сохранения 32-битного целого в 64-битном.

Код x86-64 для простого mov на один байт короче (3 байта против 4). Я не думаю, что должна быть большая разница в скорости, но если она есть, я бы ожидал, что равнина mov выиграет чуть-чуть.

0 голосов
/ 06 января 2020

При условии, что единственной операцией над 64-битным значением будет преобразование его обратно в 32 (и, возможно, его сохранение / отображение), нет необходимости применять маску. Компилятор подпишет расширение 32-битных атрибутов при приведении его к 64-битному, и выберет самое низкое 32-битное при преобразовании 64-битного значения обратно в 32-битное.

#define packInt32(X) ((uint64_t)(X))
#define unpackInt32(X) ((int)(X))

Или лучше, используя (встроенный) функции:

inline uint64_t packInt32(int x) { return ((uint64_t) x) ; }
inline int unpackInt32(uint64_t x) { return ((int) x) ; }
...