跨平台是Google的Protocol Buffer在实践中处理浮点类型的方式吗?

时间:2011-08-30 19:49:39

标签: c++ cross-platform floating-point protocol-buffers ieee-754

Google的Protocol Buffers允许您在邮件中存储浮点数和双精度数。我查看了实现源代码,想知道他们是如何以跨平台的方式设法做到这一点的,我偶然发现的是:

inline uint32 WireFormatLite::EncodeFloat(float value) {
  union {float f; uint32 i;};
  f = value;
  return i;
}

inline float WireFormatLite::DecodeFloat(uint32 value) {
  union {float f; uint32 i;};
  i = value;
  return f;
}

inline uint64 WireFormatLite::EncodeDouble(double value) {
  union {double f; uint64 i;};
  f = value;
  return i;
}

inline double WireFormatLite::DecodeDouble(uint64 value) {
  union {double f; uint64 i;};
  i = value;
  return f;
}

现在,一个重要的附加信息是这些例程不是过程的结束,而是对它们的结果进行后处理,以便以小端顺序放置字节:

inline void WireFormatLite::WriteFloatNoTag(float value,
                                        io::CodedOutputStream* output) {
  output->WriteLittleEndian32(EncodeFloat(value));
}

inline void WireFormatLite::WriteDoubleNoTag(double value,
                                         io::CodedOutputStream* output) {
  output->WriteLittleEndian64(EncodeDouble(value));
}

template <>
inline bool WireFormatLite::ReadPrimitive<float, WireFormatLite::TYPE_FLOAT>(
    io::CodedInputStream* input,
    float* value) {
  uint32 temp;
  if (!input->ReadLittleEndian32(&temp)) return false;
  *value = DecodeFloat(temp);
  return true;
}

template <>
inline bool WireFormatLite::ReadPrimitive<double, WireFormatLite::TYPE_DOUBLE>(
    io::CodedInputStream* input,
    double* value) {
  uint64 temp;
  if (!input->ReadLittleEndian64(&temp)) return false;
  *value = DecodeDouble(temp);
  return true;
}

所以我的问题是:这在实践中确实足够好以确保C ++中浮点数和双精度数的序列化可跨平台传输吗?

我在我的问题中明确插入“实践中”这个词,因为我知道在理论上我不能对浮点数和双打在C ++中实际格式化有任何假设,但我不知道我知道这种理论上的危险是否实际上是我在实践中应该非常担心的事情。

更新

现在我认为PB可能会破坏SPARC上的方法。如果我正确理解this page by Oracle describing the format used for number on SPARC,则SPARC使用相反的endian作为整数的x86,但使用与x86相同的endian作为浮点数和双精度。但是,PB首先将它们直接转换为适当大小的整数类型(通过联合;请参阅上面我的问题中引用的代码片段),然后在平台上反转字节的顺序,从而对浮点数/双精度数进行编码。 big-endian整数:

void CodedOutputStream::WriteLittleEndian64(uint64 value) {
  uint8 bytes[sizeof(value)];

  bool use_fast = buffer_size_ >= sizeof(value);
  uint8* ptr = use_fast ? buffer_ : bytes;

  WriteLittleEndian64ToArray(value, ptr);

  if (use_fast) {
    Advance(sizeof(value));
  } else {
    WriteRaw(bytes, sizeof(value));
  }
}

inline uint8* CodedOutputStream::WriteLittleEndian64ToArray(uint64 value,
                                                            uint8* target) {
#if defined(PROTOBUF_LITTLE_ENDIAN)
  memcpy(target, &value, sizeof(value));
#else
  uint32 part0 = static_cast<uint32>(value);
  uint32 part1 = static_cast<uint32>(value >> 32);

  target[0] = static_cast<uint8>(part0);
  target[1] = static_cast<uint8>(part0 >>  8);
  target[2] = static_cast<uint8>(part0 >> 16);
  target[3] = static_cast<uint8>(part0 >> 24);
  target[4] = static_cast<uint8>(part1);
  target[5] = static_cast<uint8>(part1 >>  8);
  target[6] = static_cast<uint8>(part1 >> 16);
  target[7] = static_cast<uint8>(part1 >> 24);
#endif
  return target + sizeof(value);
}

然而,对于SPARC上浮点/双精度的情况,这是完全错误的,因为字节已经是“正确”的顺序。

总而言之,如果我的理解是正确的,则使用PB在SPARC和x86之间使用浮点数 可传输,因为基本上PB假设所有数字都以相同的endianess存储(相对于其他平台)作为给定平台上的整数,这是在SPARC上做出的错误假设。

更新2

正如Lyke所指出的那样,与x86相比,IEEE 64位浮点 以大端顺序存储在SPARC上。但是,只有两个32位字的顺序相反,而不是所有8个字节,特别是IEEE 32位浮点看起来它们的存储顺序与x86上的顺序相同。

2 个答案:

答案 0 :(得分:10)

我认为只要您的目标C ++平台使用IEEE-754并且该库正确处理字节序,它应该没问题。基本上你所展示的代码假设如果你有正确的顺序和IEEE-754实现,你将获得正确的价值。字节序由协议缓冲区处理,并假设IEEE-754-ness - 但非常普遍。

答案 1 :(得分:4)

在实践中,他们正在强制执行字节顺序的写作和阅读这一事实足以维持可移植性。考虑到协议缓冲区在许多平台(甚至语言)中的广泛使用,这是相当明显的。