Буферы протокола 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
Как указал Лайк, 64-битные числа с плавающей запятой IEEE хранятся в SPARC в обратном порядке, в отличие от x86. Однако только два 32-битных слова находятся в обратном порядке, а не все 8 байтов, и в частности 32-битные числа с плавающей запятой IEEE выглядят так, как будто они хранятся в том же порядке, что и на x86.