Helpex - Trao đổi & giúp đỡ Đăng nhập
22

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.

22 hữu ích 5 bình luận 5.7k xem chia sẻ
10

Tôi nghĩ nó sẽ ổn miễn là nền tảng C ++ mục tiêu của bạn sử dụng IEEE-754 và thư viện xử lý endianness đúng cách. Về cơ bản, mã bạn đã hiển thị là giả định rằng nếu bạn có đúng các bit theo đúng thứ tự và triển khai IEEE-754, bạn sẽ nhận được giá trị phù hợp. Khả năng kết thúc được xử lý bởi bộ đệm giao thức và IEEE-754-ness được giả định - nhưng khá phổ biến.

10 hữu ích 5 bình luận chia sẻ
4

Trong thực tế, việc họ đang viết và đọc với khả năng thực thi được thực thi là đủ để duy trì tính di động. Điều này là khá rõ ràng, xem xét việc sử dụng rộng rãi Bộ đệm giao thức trên nhiều nền tảng (và thậm chí cả ngôn ngữ).

4 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ c++ cross-platform floating-point protocol-buffers ieee-754 , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading