Насколько кроссплатформенный Google Protocol Buffer обрабатывает типы с плавающей точкой на практике? - PullRequest
22 голосов
/ 30 августа 2011

Протоколные буферы Google позволяют хранить числа с плавающей запятой и двойники в сообщениях.Я просмотрел исходный код реализации, задаваясь вопросом, как им удалось сделать это кроссплатформенным образом, и на что я наткнулся:

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.Если я правильно понимаю эту страницу в Oracle, описывающую формат, используемый для чисел в SPARC , то SPARC использует противоположный порядковый номер как x86 для целых чисел , но такой же порядковый номер как x86 для чисел с плавающей запятой и удваивает .Тем не менее, PB кодирует числа с плавающей запятой / удваивается, сначала приводя их непосредственно к целочисленному типу соответствующего размера (с помощью объединения; см. Фрагменты кода, приведенные в моем вопросе выше), а затем изменяет порядок байтов на платформах сцелые числа с прямым порядком байтов:

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, поскольку байты уже находятся в «правильном» порядке.

Итак, в заключение, если мое понимание верно, то числа с плавающей запятой не переносимы между SPARC и x86 с использованием PB, потому что по существу PB предполагает, что все числа хранятся с одинаковым порядком байтов (относительнодругие платформы) как целые числа на данной платформе, что является неправильным допущением для SPARC.

ОБНОВЛЕНИЕ 2

Как указал Лайк, IEEE 64-bitплавающие точки хранятся в порядке с прямым порядком байтов в SPARC, в отличие от x86.Однако только два 32-разрядных слова находятся в обратном порядке, а не все 8 байтов, и, в частности, 32-разрядные числа с плавающей запятой IEEE выглядят так, как будто они хранятся в том же порядке, что и в x86.

Ответы [ 2 ]

10 голосов
/ 30 августа 2011

Я думаю, что все должно быть в порядке, если ваша целевая платформа C ++ использует IEEE-754, а библиотека правильно обрабатывает порядок байтов. В основном код, который вы показали, предполагает, что если у вас есть правильные биты в правильном порядке и реализация IEEE-754, вы получите правильное значение. Порядок байтов обрабатывается протокольными буферами, и предполагается, что IEEE-754-ness, но довольно универсален.

4 голосов
/ 30 августа 2011

На практике того факта, что они пишут и читают с принудительным порядком байтов, достаточно для обеспечения переносимости.Это довольно очевидно, учитывая широкое использование протокольных буферов на многих платформах (и даже языках).

...