Bộ đệm giao thức của Google cho phép bạn lưu trữ các phần nổi và phần nhân đôi trong tin nhắn. Tôi đã xem qua mã nguồn triển khai và tự hỏi làm thế nào họ quản lý để thực hiện điều này theo cách đa nền tảng và điều tôi gặp phải là:
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;
}
Bây giờ, một thông tin bổ sung quan trọng là những quy trình này không phải là kết thúc của quá trình mà là kết quả của chúng được xử lý sau để đặt các byte theo thứ tự nhỏ nhất:
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;
}
Vì vậy, câu hỏi của tôi là: điều này có thực sự đủ tốt trong thực tế để đảm bảo rằng việc tuần tự hóa các float và double trong C ++ sẽ có thể vận chuyển qua các nền tảng không?
Tôi đang chèn rõ ràng các từ "trong thực tế" trong câu hỏi của mình vì tôi biết rằng trên lý thuyết, người ta không thể đưa ra bất kỳ giả định nào về cách float và double thực sự được định dạng trong C ++, nhưng tôi không biết liệu mối nguy hiểm về mặt lý thuyết này có thực sự là điều mà tôi nên rất lo lắng trong thực tế.
CẬP NHẬT
Với tôi, bây giờ có vẻ như cách tiếp cận mà PB áp dụng có thể bị phá vỡ trên SPARC. Nếu tôi hiểu trang này bởi Oracle mô tả chính xác định dạng được sử dụng cho số trên SPARC , thì SPARC sử dụng giá trị cuối đối diện là x86 cho số nguyên nhưng giá trị cuối giống như x86 cho số nổi và số đôi . Tuy nhiên, PB mã hóa float / double bằng cách đầu tiên truyền trực tiếp chúng sang kiểu số nguyên có kích thước thích hợp (thông qua phương tiện liên hợp; xem các đoạn mã được trích dẫn trong câu hỏi của tôi ở trên), sau đó đảo ngược thứ tự của các byte trên nền tảng với số nguyên 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);
}
Tuy nhiên, điều này chính xác là điều sai khi nó đang làm trong trường hợp float / double trên SPARC vì các byte đã có thứ tự "đúng".
Vì vậy, kết luận lại, nếu sự hiểu biết của tôi là đúng thì số dấu phẩy động không thể vận chuyển giữa SPARC và x86 bằng cách sử dụng PB, vì về cơ bản PB giả định rằng tất cả các số được lưu trữ với cùng một nội dung (so với các nền tảng khác) như các số nguyên trên một nền tảng nhất định đây là một giả định không chính xác để thực hiện trên SPARC.
CẬP NHẬT 2
Như Lyke đã chỉ ra, các dấu chấm động 64-bit IEEE được lưu trữ theo thứ tự big-endian trên SPARC, trái ngược với x86. Tuy nhiên, chỉ có hai từ 32 bit là theo thứ tự ngược lại, không phải tất cả 8 byte và cụ thể là các dấu chấm động 32 bit của IEEE trông giống như chúng được lưu trữ theo thứ tự như trên x86.