Tenho uma situação em que preciso agrupar 16 bits em um número de 64 bits e depois lê-los como um número inteiro assinado no intervalo [ -32768, 32768 ). O método que escolhi para isso é calcular o número como um int assinado de 16 bits, convertê-lo imediatamente em um int não assinado de 16 bits e, em seguida, fazer o upcast para um int não assinado de 64 bits antes de executar a mudança de bit adequada para obter os 16 bits críticos no lugar apropriado.
Aqui está o pseudocódigo para criar o arranjo compactado de bits:
Given int x, y such that x - y >= -32768 and y - x < 32768;
const int MASK_POS = 45;
const unsigned short int u_s = x - y;
unsigned long long int ull_s = u_s;
ull_s <<= MASK_POS;
Aqui está o pseudocódigo para extrair a diferença nos números originais:
Given unsigned long long int ull_s with 16 bits encoding a signed integer in the 46th through 61st bits;
const unsigned short int u_s = ((ulls >> MASK_POS) & 0xffff);
const short int s_s = u_s;
const int difference_x_and_y = s_s;
Esta me parece uma maneira razoável de empacotar um número inteiro assinado e extraí-lo. Desconfio do comportamento específico da plataforma ao realizar mudanças de bits em números inteiros negativos, mas acho que converter para a forma não assinada do mesmo número de bits antes de atualizar o número de bits gerais no número e, ao contrário, extrair o não assinado inteiro com comprimento de bit desejado antes de converter para um inteiro com sinal de tamanho igual, será seguro.
(Caso alguém esteja curioso, haverá MUITO coisa acontecendo nos outros 48 bits desse número inteiro não assinado de 64 bits em que o material acaba - dos três bits mais altos aos 31 mais baixos e aos 14 do meio, tudo foi analisado. Certamente posso escrever alguns testes de unidade para garantir que esse comportamento se mantenha em qualquer arquitetura, mas se alguém puder ver uma falha agora, é melhor saber com antecedência. Obrigado!)
O que você está fazendo está perfeitamente bem. Desde C++ 20, inteiros assinados são obrigados a ter representação em complemento de dois, e todas as conversões assinadas/não assinadas são bem definidas e equivalentes a
std::bit_cast
. Mesmo antes disso, qualquer implementação de seu interesse se comportaria dessa maneira.No entanto, provavelmente seria melhor se você usasse tipos de largura fixa, já
std::uint16_t
que seu código depende muito de uma largura específica.Você pode empacotar números inteiros assim, mas isso levanta a questão de por que você não poderia usar um
struct
likequad
diretamente. Nenhum compilador sensato adicionará preenchimento aquad
, e você pode ter certeza disso comO compilador também não tem permissão para reordenar os membros de
quad
, portanto, para todos os efeitos, você pode simplesmente agrupar números inteirosquad
em vez de agrupá-los em um número inteiro.