Sérialisation portable des valeurs à virgule flottante IEEE754

J’ai récemment travaillé sur un système qui doit stocker et charger de grandes quantités de données, y compris des valeurs à virgule flottante simple précision. J’ai décidé de normaliser l’ordre des octets du réseau pour les entiers et de stocker les valeurs à virgule flottante au format big-endian, c’est-à-dire:

|-- Byte 0 --| |-- Byte 1 -| Byte 2 Byte 3 # ####### # ####### ######## ######## Sign Exponent Mantissa 1b 8b, MSB first 23b, MSB first 

Idéalement, je souhaite fournir des fonctions telles que htonl() et ntohl() , car je les utilisais déjà pour le swabbing d’entiers, et je souhaitais également le mettre en œuvre de manière à offrir autant d’indépendance de plate-forme que possible (en supposant que le type float correspond aux valeurs à virgule flottante IEEE754 32 bits). Existe-t-il un moyen, en utilisant éventuellement ieee754.h , de le faire?

J’ai une réponse qui semble fonctionner et que je posterai ci-dessous, mais elle semble assez lente et inefficace et j’apprécierais toutes les suggestions sur la manière de la rendre plus rapide et / ou plus fiable.

Beaucoup plus simple, et selon la même hypothèse que la votre (ce qui veut dire que les types float et integer ont le même ordre d’octet et sont presque universellement valables – de manière réaliste, vous ne rencontrerez jamais un système où ce n’est pas vrai):

 #include  float htonf(float val) { uint32_t rep; memcpy(&rep, &val, sizeof rep); rep = htonl(rep); memcpy(&val, &rep, sizeof rep); return val; } 

Tout compilateur raisonnablement bon optimisera les deux appels memcpy ; ils sont présents pour déjouer les optimisations trop htonl en htonl aliasing, ce qui en fait un système aussi efficace que htonl plus la surcharge d’un simple appel de fonction.

Comme mentionné dans la question ci-dessus, j’ai une solution à mon problème, mais je n’y suis pas particulièrement attaché et j’apprécie les autres réponses. Je la poste donc ici plutôt que dans la question. En particulier, cela semble être lent, et je ne suis pas sûr que ça casse le pseudonyme ssortingct, entre autres problèmes potentiels.

 #include  float htonf (float val) { union ieee754_float u; float v; uint8_t *un = (uint8_t *) &v; uf = val; un[0] = (u.ieee.negative << 7) + ((u.ieee.exponent & 0xfe) >> 1); un[1] = ((u.ieee.exponent & 0x01) << 7) + ((u.ieee.mantissa & 0x7f0000) >> 16); un[2] = (u.ieee.mantissa & 0xff00) >> 8; un[3] = (u.ieee.mantissa & 0xff); return v; } float ntohf (float val) { union ieee754_float u; uint8_t *un = (uint8_t *) &val; u.ieee.negative = (un[0] & 0x80) >> 7; u.ieee.exponent = (un[0] & 0x7f) << 1; u.ieee.exponent += (un[1] & 0x80) >> 7; u.ieee.mantissa = (un[1] & 0x7f) << 16; u.ieee.mantissa += un[2] << 8; u.ieee.mantissa += un[3]; return uf; } 

Voici une routine d’écriture portable IEEE 754. Il écrira un double au format IEEE 754, quelle que soit la représentation en virgule flottante sur la machine hôte.

 /* * write a double to a stream in ieee754 format regardless of host * encoding. * x - number to write * fp - the stream * bigendian - set to write big bytes first, elee write litle bytes * first * Returns: 0 or EOF on error * Notes: different NaN types and negative zero not preserved. * if the number is too big to represent it will become infinity * if it is too small to represent it will become zero. */ static int fwriteieee754(double x, FILE *fp, int bigendian) { int shift; unsigned long sign, exp, hibits, hilong, lowlong; double fnorm, significand; int expbits = 11; int significandbits = 52; /* zero (can't handle signed zero) */ if (x == 0) { hilong = 0; lowlong = 0; goto writedata; } /* infinity */ if (x > DBL_MAX) { hilong = 1024 + ((1 << (expbits - 1)) - 1); hilong <<= (31 - expbits); lowlong = 0; goto writedata; } /* -infinity */ if (x < -DBL_MAX) { hilong = 1024 + ((1 << (expbits - 1)) - 1); hilong <<= (31 - expbits); hilong |= (1 << 31); lowlong = 0; goto writedata; } /* NaN - dodgy because many compilers optimise out this test, but *there is no portable isnan() */ if (x != x) { hilong = 1024 + ((1 << (expbits - 1)) - 1); hilong <<= (31 - expbits); lowlong = 1234; goto writedata; } /* get the sign */ if (x < 0) { sign = 1; fnorm = -x; } else { sign = 0; fnorm = x; } /* 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--; } /* check for denormalized numbers */ if (shift < -1022) { while (shift < -1022) { fnorm /= 2.0; shift++; } shift = -1023; } /* out of range. Set to infinity */ else if (shift > 1023) { hilong = 1024 + ((1 << (expbits - 1)) - 1); hilong <<= (31 - expbits); hilong |= (sign << 31); lowlong = 0; goto writedata; } else fnorm = fnorm - 1.0; /* take the significant bit off mantissa */ /* calculate the integer form of the significand */ /* hold it in a double for now */ significand = fnorm * ((1LL << significandbits) + 0.5f); /* get the biased exponent */ exp = shift + ((1 << (expbits - 1)) - 1); /* shift + bias */ /* put the data into two longs (for convenience) */ hibits = (long)(significand / 4294967296); hilong = (sign << 31) | (exp << (31 - expbits)) | hibits; x = significand - hibits * 4294967296; lowlong = (unsigned long)(significand - hibits * 4294967296); writedata: /* write the bytes out to the stream */ if (bigendian) { fputc((hilong >> 24) & 0xFF, fp); fputc((hilong >> 16) & 0xFF, fp); fputc((hilong >> 8) & 0xFF, fp); fputc(hilong & 0xFF, fp); fputc((lowlong >> 24) & 0xFF, fp); fputc((lowlong >> 16) & 0xFF, fp); fputc((lowlong >> 8) & 0xFF, fp); fputc(lowlong & 0xFF, fp); } else { fputc(lowlong & 0xFF, fp); fputc((lowlong >> 8) & 0xFF, fp); fputc((lowlong >> 16) & 0xFF, fp); fputc((lowlong >> 24) & 0xFF, fp); fputc(hilong & 0xFF, fp); fputc((hilong >> 8) & 0xFF, fp); fputc((hilong >> 16) & 0xFF, fp); fputc((hilong >> 24) & 0xFF, fp); } return ferror(fp); }