在C中读取/写入浮点类型时,如何处理字节顺序差异?

在C中读取/写入浮点类型时,如何处理字节顺序差异?,c,floating-point,endianness,C,Floating Point,Endianness,我正在为我的应用程序设计一种文件格式,我显然希望它能在big-endian和little-endian系统上工作。我已经找到了使用htonl和ntohl管理整数类型的有效解决方案,但在尝试使用float和double值执行相同操作时,我有点卡住了 考虑到浮点表示法的工作性质,我假设标准字节顺序函数不能处理这些值。类似地,我甚至不能完全确定传统意义上的endianness是否是控制这些类型字节顺序的因素 我所需要的就是一致性。一种将double写出来,并确保在读回时得到相同值的方法。如何在C语言中

我正在为我的应用程序设计一种文件格式,我显然希望它能在big-endian和little-endian系统上工作。我已经找到了使用
htonl
ntohl
管理整数类型的有效解决方案,但在尝试使用
float
double
值执行相同操作时,我有点卡住了

考虑到浮点表示法的工作性质,我假设标准字节顺序函数不能处理这些值。类似地,我甚至不能完全确定传统意义上的endianness是否是控制这些类型字节顺序的因素


我所需要的就是一致性。一种将
double
写出来,并确保在读回时得到相同值的方法。如何在C语言中实现这一点?

根据应用程序的不同,最好使用纯文本数据格式(可能是XML)。如果不想浪费磁盘空间,可以对其进行压缩。

浮点值使用与整数值imho相同的字节顺序。使用活接头将其与相应的整体对应件重叠,并使用常见的hton功能:

float htonf(float x) {
   union foo {
     float f;
     uint32_t i;
   } foo = { .f = x };

   foo.i = htonl(foo.i);
   return foo.f;
}

XML可能是实现这一点的最可移植的方式

然而,看起来您已经构建了大部分解析器,但是仍然停留在浮点/双精度问题上。我建议把它写成一个字符串(精确到你想要的程度),然后把它读回来


除非您的所有目标平台都使用IEEE-754浮点数(和双精度浮点数),否则字节交换技巧将不适用于您。

如果您保证您的实现始终以指定格式处理序列化的浮点表示,那么您就可以了(IEEE 754很常见)

是的,体系结构对浮点数的排序可能不同(例如,以大端或小端为单位)。因此,您需要以某种方式指定endianness。这可以是格式的规范或变量,并记录在文件的数据中


最后一个主要陷阱是,内置的对齐方式可能会有所不同。硬件/处理器处理对齐错误数据的方式由实现定义。因此,您可能需要交换数据/字节,然后将其移动到目标
float
/
double
另一个选项是使用
double-frexp(双值,int*exp)
(C99)将浮点值分解为标准化分数(在[0.5,1]范围内)和2的整数幂。然后可以将分数乘以
FLT\u基数
DBL\u MANT\u DIG
,得到范围内的整数[
FLT_RADIX
DBL_MANT_DIG
/2,
FLT_RADIX
DBL_MANT_DIG
)。然后保存两个整数,无论是大尾数还是小尾数,以您的格式选择

加载保存的数字时,执行相反的操作,并使用
double ldexp(double x,int exp);
将重建的分数乘以2的幂


FLT_RADIX
=2(我想几乎所有的系统)和
DBL_MANT_DIG
像HDF5甚至NetCDF这样的库可能有点重时,这将是最好的,正如高性能标记所说,除非您还需要这些库中的其他可用功能


一个只处理序列化的较轻的替代方案是(另请参阅)。许多操作系统都提供现成的XDR例程,如果这还不够的话。还存在独立的XDR库。

将其保存为文本不是一个选项?@qPCR4vir可能会降低很多性能。库如HDF5()为您解决所有这些问题。我怀疑HDF5对于您的需求来说可能有点重。与任何序列化问题一样:指定一个wire格式,然后为每个平台编写编写器和读取器。我有一种感觉,对于至少一个平台,这将是微不足道的,而对于其他平台,它最多需要一个字节交换。等等……还有平台现在的表单不使用IEEE 754浮点?我不认为对RAM中IEEE-754浮点/双精度中的位的顺序有任何保证。它可以是任何东西,您不应该直接操作它的内容。这是一篇有趣的帖子,内容是关于如何确保平台的双精度实现符合IEEE-754:@FUZxxl-早于IEEE浮点运算的一个重要平台是IBM大型机。从20世纪60年代开始,它们仍然有自己的格式。但OP可能不关心这些格式。
%a
可能比
%f
/
%e
/
%g
在将浮点值写入文本时更好。可读性较差,但应避免trun小数位数的组合或小数位数太多。在某些平台上,整数和浮点具有不同的endianness。但我们可能不想再支持这些。太好了,谢谢。我想我唯一关心的是溢出。您建议如何处理它们?此外,此方法准确吗?如果浮点值是base-2,则此方法不正确o准确地说。当CPU的格式的指数范围比格式的指数范围大或相反时,您可能会遇到唯一的溢出。您可能需要检查这一点,并使用特殊的无穷大值或最大值(如果不支持无穷大)。您可能在位数方面有类似的问题尾数中的s/位。如果它不适合CPU或文件插槽,则需要对其进行截断或舍入。您可以优化IEEE-754的代码,以便如果CPU支持IEEE-754,则无需进行任何特殊检查。嗯,好的。您介意发布一些(伪)代码吗代码?我试过实现它,但不是在所有情况下都能正常工作。事实上,不管怎样,我成功了,错误就在我的代码中。
#include <limits.h>
#include <float.h>
#include <math.h>
#include <string.h>
#include <stdio.h>

#if CHAR_BIT != 8
#error currently supported only CHAR_BIT = 8
#endif

#if FLT_RADIX != 2
#error currently supported only FLT_RADIX = 2
#endif

#ifndef M_PI
#define M_PI 3.14159265358979324
#endif

typedef unsigned char uint8;

/*
  10-byte little-endian serialized format for double:
  - normalized mantissa stored as 64-bit (8-byte) signed integer:
      negative range: (-2^53, -2^52]
      zero: 0
      positive range: [+2^52, +2^53)
  - 16-bit (2-byte) signed exponent:
      range: [-0x7FFE, +0x7FFE]

  Represented value = mantissa * 2^(exponent - 53)

  Special cases:
  - +infinity: mantissa = 0x7FFFFFFFFFFFFFFF, exp = 0x7FFF
  - -infinity: mantissa = 0x8000000000000000, exp = 0x7FFF
  - NaN:       mantissa = 0x0000000000000000, exp = 0x7FFF
  - +/-0:      only one zero supported
*/

void Double2Bytes(uint8 buf[10], double x)
{
  double m;
  long long im; // at least 64 bits
  int ie;
  int i;

  if (isnan(x))
  {
    // NaN
    memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\xFF\x7F", 10);
    return;
  }
  else if (isinf(x))
  {
    if (signbit(x))
      // -inf
      memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10);
    else
      // +inf
      memcpy(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10);
    return;
  }

  // Split double into normalized mantissa (range: (-1, -0.5], 0, [+0.5, +1))
  // and base-2 exponent
  m = frexp(x, &ie); // x = m * 2^ie exactly for FLT_RADIX=2
                     // frexp() can't fail
  // Extract most significant 53 bits of mantissa as integer
  m = ldexp(m, 53); // can't overflow because
                    // DBL_MAX_10_EXP >= 37 equivalent to DBL_MAX_2_EXP >= 122
  im = trunc(m); // exact unless DBL_MANT_DIG > 53

  // If the exponent is too small or too big, reduce the number to 0 or
  // +/- infinity
  if (ie > 0x7FFE)
  {
    if (im < 0)
      // -inf
      memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10);
    else
      // +inf
      memcpy(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10);
    return;
  }
  else if (ie < -0x7FFE)
  {
    // 0
    memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00", 10);
    return;
  }

  // Store im as signed 64-bit little-endian integer
  for (i = 0; i < 8; i++, im >>= 8)
    buf[i] = (uint8)im;

  // Store ie as signed 16-bit little-endian integer
  for (i = 8; i < 10; i++, ie >>= 8)
    buf[i] = (uint8)ie;
}

void Bytes2Double(double* x, const uint8 buf[10])
{
  unsigned long long uim; // at least 64 bits
  long long im; // ditto
  unsigned uie;
  int ie;
  double m;
  int i;
  int negative = 0;
  int maxe;

  if (!memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\xFF\x7F", 10))
  {
#ifdef NAN
    *x = NAN;
#else
    *x = 0; // NaN is not supported, use 0 instead (we could return an error)
#endif
    return;
  }

  if (!memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10))
  {
    *x = -INFINITY;
    return;
  }
  else if (!memcmp(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10))
  {
    *x = INFINITY;
    return;
  }

  // Load im as signed 64-bit little-endian integer
  uim = 0;
  for (i = 0; i < 8; i++)
  {
    uim >>= 8;
    uim |= (unsigned long long)buf[i] << (64 - 8);
  }
  if (uim <= 0x7FFFFFFFFFFFFFFFLL)
    im = uim;
  else
    im = (long long)(uim - 0x7FFFFFFFFFFFFFFFLL - 1) - 0x7FFFFFFFFFFFFFFFLL - 1;

  // Obtain the absolute value of the mantissa, make sure it's
  // normalized and fits into 53 bits, else the input is invalid
  if (im > 0)
  {
    if (im < (1LL << 52) || im >= (1LL << 53))
    {
#ifdef NAN
      *x = NAN;
#else
      *x = 0; // NaN is not supported, use 0 instead (we could return an error)
#endif
      return;
    }
  }
  else if (im < 0)
  {
    if (im > -(1LL << 52) || im <= -(1LL << 53))
    {
#ifdef NAN
      *x = NAN;
#else
      *x = 0; // NaN is not supported, use 0 instead (we could return an error)
#endif
      return;
    }
    negative = 1;
    im = -im;
  }

  // Load ie as signed 16-bit little-endian integer
  uie = 0;
  for (i = 8; i < 10; i++)
  {
    uie >>= 8;
    uie |= (unsigned)buf[i] << (16 - 8);
  }
  if (uie <= 0x7FFF)
    ie = uie;
  else
    ie = (int)(uie - 0x7FFF - 1) - 0x7FFF - 1;

  // If DBL_MANT_DIG < 53, truncate the mantissa
  im >>= (53 > DBL_MANT_DIG) ? (53 - DBL_MANT_DIG) : 0;

  m = im;
  m = ldexp(m, (53 > DBL_MANT_DIG) ? -DBL_MANT_DIG : -53); // can't overflow
           // because DBL_MAX_10_EXP >= 37 equivalent to DBL_MAX_2_EXP >= 122

  // Find out the maximum base-2 exponent and
  // if ours is greater, return +/- infinity
  frexp(DBL_MAX, &maxe);
  if (ie > maxe)
    m = INFINITY;
  else
    m = ldexp(m, ie); // underflow may cause a floating-point exception

  *x = negative ? -m : m;
}

int test(double x, const char* name)
{
  uint8 buf[10], buf2[10];
  double x2;
  int error1, error2;

  Double2Bytes(buf, x);
  Bytes2Double(&x2, buf);
  Double2Bytes(buf2, x2);

  printf("%+.15E '%s' -> %02X %02X %02X %02X %02X %02X %02X %02X  %02X %02X\n",
         x,
         name,
         buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9]);

  if ((error1 = memcmp(&x, &x2, sizeof(x))) != 0)
    puts("Bytes2Double(Double2Bytes(x)) != x");

  if ((error2 = memcmp(buf, buf2, sizeof(buf))) != 0)
    puts("Double2Bytes(Bytes2Double(Double2Bytes(x))) != Double2Bytes(x)");

  puts("");

  return error1 || error2;
}

int testInf(void)
{
  uint8 buf[10];
  double x, x2;
  int error;

  x = DBL_MAX;
  Double2Bytes(buf, x);
  if (!++buf[8])
    ++buf[9]; // increment the exponent beyond the maximum
  Bytes2Double(&x2, buf);

  printf("%02X %02X %02X %02X %02X %02X %02X %02X  %02X %02X -> %+.15E\n",
         buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9],
         x2);

  if ((error = !isinf(x2)) != 0)
    puts("Bytes2Double(Double2Bytes(DBL_MAX) * 2) != INF");

  puts("");

  return error;
}

#define VALUE_AND_NAME(V) { V, #V }

const struct
{
  double value;
  const char* name;
} testData[] =
{
#ifdef NAN
  VALUE_AND_NAME(NAN),
#endif
  VALUE_AND_NAME(0.0),
  VALUE_AND_NAME(+DBL_MIN),
  VALUE_AND_NAME(-DBL_MIN),
  VALUE_AND_NAME(+1.0),
  VALUE_AND_NAME(-1.0),
  VALUE_AND_NAME(+M_PI),
  VALUE_AND_NAME(-M_PI),
  VALUE_AND_NAME(+DBL_MAX),
  VALUE_AND_NAME(-DBL_MAX),
  VALUE_AND_NAME(+INFINITY),
  VALUE_AND_NAME(-INFINITY),
};

int main(void)
{
  unsigned i;
  int errors = 0;

  for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
    errors += test(testData[i].value, testData[i].name);

  errors += testInf();

  // Test subnormal values. A floating-point exception may be raised.
  errors += test(+DBL_MIN / 2, "+DBL_MIN / 2");
  errors += test(-DBL_MIN / 2, "-DBL_MIN / 2");

  printf("%d error(s)\n", errors);

  return 0;
}
+NAN 'NAN' -> 00 00 00 00 00 00 00 00  FF 7F

+0.000000000000000E+00 '0.0' -> 00 00 00 00 00 00 00 00  00 00

+2.225073858507201E-308 '+DBL_MIN' -> 00 00 00 00 00 00 10 00  03 FC

-2.225073858507201E-308 '-DBL_MIN' -> 00 00 00 00 00 00 F0 FF  03 FC

+1.000000000000000E+00 '+1.0' -> 00 00 00 00 00 00 10 00  01 00

-1.000000000000000E+00 '-1.0' -> 00 00 00 00 00 00 F0 FF  01 00

+3.141592653589793E+00 '+M_PI' -> 18 2D 44 54 FB 21 19 00  02 00

-3.141592653589793E+00 '-M_PI' -> E8 D2 BB AB 04 DE E6 FF  02 00

+1.797693134862316E+308 '+DBL_MAX' -> FF FF FF FF FF FF 1F 00  00 04

-1.797693134862316E+308 '-DBL_MAX' -> 01 00 00 00 00 00 E0 FF  00 04

+INF '+INFINITY' -> FF FF FF FF FF FF FF 7F  FF 7F

-INF '-INFINITY' -> 00 00 00 00 00 00 00 80  FF 7F

FF FF FF FF FF FF 1F 00  01 04 -> +INF

+1.112536929253601E-308 '+DBL_MIN / 2' -> 00 00 00 00 00 00 10 00  02 FC

-1.112536929253601E-308 '-DBL_MIN / 2' -> 00 00 00 00 00 00 F0 FF  02 FC

0 error(s)