protocol protobuf google buffers c++ cross-platform floating-point protocol-buffers ieee-754

c++ - buffers - google:: protobuf



¿De qué manera es multiplataforma el manejo de los tipos de punto flotante de Protocol Buffer de Google en la práctica? (2)

Creo que debería estar bien siempre y cuando su plataforma de C ++ de destino use IEEE-754 y la biblioteca maneje el endianness correctamente. Básicamente, el código que has mostrado asume que si tienes los bits correctos en el orden correcto y una implementación IEEE-754, obtendrás el valor correcto. El endianness es manejado por búferes de protocolo, y se asume el IEEE-754-ness, pero bastante universal.

Protocol Buffers de Google le permite almacenar flotantes y dobles en los mensajes. Revisé el código fuente de la implementación preguntándome cómo lograron hacer esto de manera multiplataforma, y ​​lo que encontré fue

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; }

Ahora, una información adicional importante es que estas rutinas no son el final del proceso, sino que su resultado se procesa posteriormente para poner los bytes en orden little-endian:

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; }

Entonces, mi pregunta es: ¿es esto lo suficientemente bueno en la práctica para asegurar que la serialización de flotadores y dobles en C ++ sea transportable a través de plataformas?

Estoy insertando explícitamente las palabras "en la práctica" en mi pregunta porque soy consciente de que en teoría no se pueden hacer suposiciones acerca de cómo los flotadores y los dobles están formateados en C ++, pero no tengo idea de si este peligro teórico es En realidad algo de lo que debería estar muy preocupado en la práctica.

ACTUALIZAR

Ahora me parece que el enfoque PB se puede romper en SPARC. Si entiendo esta página por Oracle que describe correctamente el formato utilizado para el número en SPARC , SPARC usa el endian opuesto como x86 para los enteros, pero el mismo endian como x86 para los flotantes y los dobles . Sin embargo, PB codifica floats / dobles primero convirtiéndolos directamente a un tipo entero del tamaño apropiado (por medio de una unión; vea los fragmentos de código citados en mi pregunta anterior), y luego invierta el orden de los bytes en plataformas con enteros 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); }

Sin embargo, esto es exactamente lo que no debe hacer en el caso de flotantes / dobles en SPARC, ya que los bytes ya están en el orden "correcto".

Entonces, en conclusión, si mi entendimiento es correcto, entonces los números de punto flotante no son transportables entre SPARC y x86 usando PB, porque básicamente PB asume que todos los números se almacenan con la misma endianess (en relación a otras plataformas) que los enteros en una plataforma dada, que es una suposición incorrecta para hacer en SPARC.

ACTUALIZACIÓN 2

Como señaló Lyke, los puntos flotantes IEEE de 64 bits se almacenan en orden big-endian en SPARC, en contraste con x86. Sin embargo, solo las dos palabras de 32 bits están en orden inverso, no todos los 8 bytes, y en particular los puntos flotantes IEEE de 32 bits parecen estar almacenados en el mismo orden que en x86.


En la práctica, el hecho de que estén escribiendo y leyendo con el endianness aplicado es suficiente para mantener la portabilidad. Esto es bastante evidente, considerando el uso generalizado de Protocol Buffers en muchas plataformas (e incluso idiomas).