Estou lendo este guia sobre programação de rede, do qual estou gostando muito: https://beej.us/guide/bgnet/html/split/slightly-advanced-techniques.html#serialization
Estou confuso sobre algo, no entanto. Nesta seção sobre serialização, ele fala sobre serializar ints por motivos de ordenação de bytes, o que faz sentido para mim, mas ele também inclui essas duas funções pack754 e unpack754 para serializar floats no formato IEEE-754.
uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
long double fnorm;
int shift;
long long sign, exp, significand;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (f == 0.0) return 0; // get this special case out of the way
// check sign and begin normalization
if (f < 0) { sign = 1; fnorm = -f; }
else { sign = 0; fnorm = f; }
// get the normalized form of f and track the exponent
shift = 0;
while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
fnorm = fnorm - 1.0;
// calculate the binary form (non-float) of the significand data
significand = fnorm * ((1LL<<significandbits) + 0.5f);
// get the biased exponent
exp = shift + ((1<<(expbits-1)) - 1); // shift + bias
// return the final answer
return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}
long double unpack754(uint64_t i, unsigned bits, unsigned expbits)
{
long double result;
long long shift;
unsigned bias;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (i == 0) return 0.0;
// pull the significand
result = (i&((1LL<<significandbits)-1)); // mask
result /= (1LL<<significandbits); // convert back to float
result += 1.0f; // add the one back on
// deal with the exponent
bias = (1<<(expbits-1)) - 1;
shift = ((i>>significandbits)&((1LL<<expbits)-1)) - bias;
while(shift > 0) { result *= 2.0; shift--; }
while(shift < 0) { result /= 2.0; shift++; }
// sign it
result *= (i>>(bits-1))&1? -1.0: 1.0;
return result;
}
O que me confunde é que essas funções funcionam olhando para o primeiro bit para o sinal, depois os próximos bits X para o expoente, depois os próximos bits Y para a mantissa. Então isso não significa que o float já tem que estar no formato IEEE-754 na máquina host para que isso funcione?
Isto está aqui apenas para explicar o formato ou é algo que você realmente faria na vida real?
Sim. A codificação FP tem muitas variações entre implementações, incluindo variações de tamanho, endian, precisão, intervalo de expoentes, suporte subnormal (e possivelmente até base).
Não, o empacotamento/desempacotamento "funcionará" (veja os problemas a seguir) mesmo que
long double
não seja IEEE.Parece código de aprendiz. Eu não usaria o código pack/unpack fornecido, dadas suas fraquezas (abaixo) e especialmente os 2 loops muito ineficientes . Loops podem iterar milhares de vezes com binary128 .
while
O código é uma tentativa cheia de buracos de empacotar um arbitrário codificado
long double
em um binário IEEE64 . Ele falha para valores próximos a 0,0, arredondamento, manipula estouro e infinito/NAN também.pack754()
tem pelo menos estas deficiências:if (f == 0.0) return 0;
perde informações durante a serialização, pois retorna 0 para +0,0 e -0,0. Ao testar o bit de sinal FP, não useif (f < 0)
, masif (signbit(f))
para extrair bem o bit de sinal, mesmo quef
seja zero ou NAN.long double
pode ser mais de 64 bits, entãouint64_t pack754(long double f, unsigned bits, unsigned expbits)
perde informação ao tentar compactar em 64 bits. Suponho que o OP esteja tolerando essa perda de informação.1LL<<significandbits
é UB em overflow (significandbits >= 63
).1ULL<<significandbits
tem alguma vantagem, mas overflow (significandbits >= 64
) continua sendo um problema.Usar
float
matemática com along double
matemática posterior é míope.((1LL<<significandbits) + 0.5L)
Faz um pouco mais de sentido.Em vez de
while(fnorm >= 2.0)
gostar de código, uselong double frexpl(long double value, int *p)
para extrair um valor normalizado e expoente. Uselong double ldexpl(long double x, int p)
para recombinar.while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
corre o risco de um loop infinito quandofnorm
é infinito.+ 0.5f
para arredondamento tem muitos problemas de cantos. Melhor usarlround()
e amigos....
Para uma troca simples de valores de FP entre plataformas, eu consideraria
sprintf(buf, "%La", x)
como primeiro passo empacotar estrtold()
descompactar .Empacotar um FP em um apertado
intN_t
e manter fidelidade de precisão/alcance em muitas implementações de computador são objetivos concorrentes.O que é mais importante: conversões fiéis ou tamanho de pacote pequeno?
A maioria dos sistemas com os quais trabalhei preza conversões fiéis em vez de tamanho de pacote pequeno.
Incluir um
long double
, para portabilidade, em um sistema de 64 bits é simplesmente um projeto imprudente.