C++ - 将double序列化为小端字节顺序的二进制文件

4

我正在尝试实现一个函数,该函数以小端字节顺序将double写入二进制文件。
目前我已经有了BinaryWriter类的实现:

void BinaryWriter::open_file_stream( const String& path )
{
 // open output stream
  m_fstream.open( path.c_str(), std::ios_base::out | std::ios_base::binary);
  m_fstream.imbue(std::locale::classic());   
}

void BinaryWriter::write( int v )
{
  char data[4];
  data[0] = static_cast<char>(v & 0xFF);
  data[1] = static_cast<char>((v >> 8) & 0xFF);
  data[2] = static_cast<char>((v >> 16) & 0xFF);
  data[3] = static_cast<char>((v >> 24) & 0xFF);
  m_fstream.write(data, 4);
}

void BinaryWriter::write( double v )
{
   // TBD
}
void BinaryWriter::write( int v )是使用Sven answer来实现的,该答案针对如何正确输出十六进制数据到文件?问题提供了解决方案。
我不确定如何实现void BinaryWriter::write( double v )
我尝试简单地跟随void BinaryWriter::write( int v )的实现,但它没有奏效。我猜我没有完全理解这个实现。

谢谢大家


你正在编写这些代码的机器是大端序的吗?如果不是,直接写入数值即可。 - Retired Ninja
1
这是因为计算机不会以相同的方式存储浮点数和整数,因此您无法在其字节上执行二进制运算符。请参阅此答案,了解有关浮点数序列化的讨论:https://dev59.com/tG445IYBdhLWcg3ws8Zp?rq=1 - o_weisman
有没有可能实现一个与机器的字节顺序无关的函数? - idanshmu
3个回答

3

我猜你所运行的机器是大端序,否则写一个double和写一个int是一样的,只不过double占用8个字节。

const int __one__ = 1;
const bool isCpuLittleEndian = 1 == *(char*)(&__one__); // CPU endianness
const bool isFileLittleEndian = false;  // output endianness - you choose :)


void BinaryWriter::write( double v )
{
  if (isCpuLittleEndian ^ isFileLittleEndian) {
    char data[8], *pDouble = (char*)(double*)(&v);
    for (int i = 0; i < 8; ++i) {
      data[i] = pDouble[7-i];
    }
    m_fstream.write(data, 8);
  }
  else
    m_fstream.write((char*)(&v), 8);
}

谢谢。但是,如果我正在编写的机器是小端字节序,那么这个函数会生成不同的结果吗?您是说编译建议的函数会产生不同的结果吗?我认为是(LSB <-> MSB)。如果我想编写一个与我正在运行的机器无关的函数,是否可能? - idanshmu
字节序是在构建时确定的,因为它与目标指令集相关(而不管构建机器体系结构如何)。我将更新代码片段。 - egur

1

但不要忘记,通常int为4个八位字节,double为8个八位字节。

另一个问题是static_cast。看这个例子:

double d = 6.1; char c = static_cast(d); //c == 6

解决方案是使用指针重新解释值:

double d = 6.1;
char* c = reinterpret_cast<char*>(&d);

之后,您可以使用 write(Int_64 *v) 方法,它是从 write(Int_t v) 扩展而来的。

您可以将此方法与以下内容一起使用:

double d =  45612.9874
binary_writer.write64(reinterpret_cast<int_64*>(&d));

不要忘记double的size_of取决于系统。


1
一个将双精度浮点数转换为IEEE小端表示的小程序。 除了在 to_little_endian 测试中,它应该可以在任何机器上运行。
include <cmath>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <limits>
#include <sstream>
#include <random>

bool to_little_endian(double value) {

    enum { zero_exponent = 0x3ff };

    uint8_t  sgn = 0;      //  1 bit
    uint16_t exponent = 0; // 11 bits
    uint64_t fraction = 0; // 52 bits

    double d = value;

    if(std::signbit(d)) {
        sgn = 1;
        d = -d;
    }

    if(std::isinf(d)) {
        exponent = 0x7ff;
    }
    else if(std::isnan(d)) {
        exponent = 0x7ff;
        fraction = 0x8000000000000;
    }
    else if(d) {
        int e;
        double f = frexp(d, &e);

        // A leading one is implicit.
        // Hence one has has a zero fraction and the zero_exponent:
        exponent = uint16_t(e + zero_exponent - 1);

        unsigned bits = 0;
        while(f) {
            f *= 2;
            fraction <<= 1;
            if (1 <= f) {
                fraction |= 1;
                f -= 1;
            }
            ++bits;
        }
        fraction = (fraction << (53 - bits)) & ((uint64_t(1) << 52) - 1);
    }

    // Little endian representation.
    uint8_t data[sizeof(double)];
    for(unsigned i = 0; i < 6; ++i) {
        data[i] = fraction & 0xFF;
        fraction >>= 8;
    }
    data[6] = (exponent << 4) | fraction;
    data[7] = (sgn << 7) | (exponent >> 4);


    // This test works on a little endian machine, only.
    double result = *(double*) &data;
    if(result == value || (std::isnan(result) && std::isnan(value))) return true;
    else {
        struct DoubleLittleEndian {
            uint64_t fraction : 52;
            uint64_t exp : 11;
            uint64_t sgn : 1;
        };

        DoubleLittleEndian little_endian;
        std::memcpy(&little_endian, &data, sizeof(double));
        std::cout << std::hex
            << "  Result: " << result << '\n'
            << "Fraction: " << little_endian.fraction << '\n'
            << "     Exp: " << little_endian.exp << '\n'
            << "     Sgn: " << little_endian.sgn << '\n'
            << std::endl;

        std::memcpy(&little_endian, &value, sizeof(value));
        std::cout << std::hex
            << "   Value: " << value << '\n'
            << "Fraction: " << little_endian.fraction << '\n'
            << "     Exp: " << little_endian.exp << '\n'
            << "     Sgn: " << little_endian.sgn
            << std::endl;

        return false;
    }
}


int main()
{
    to_little_endian(+1.0);
    to_little_endian(+0.0);
    to_little_endian(-0.0);
    to_little_endian(+std::numeric_limits<double>::infinity());
    to_little_endian(-std::numeric_limits<double>::infinity());
    to_little_endian(std::numeric_limits<double>::quiet_NaN());

    std::uniform_real_distribution<double> distribute(-100, +100);
    std::default_random_engine random;
    for (unsigned loop = 0; loop < 10000; ++loop) {
        double value = distribute(random);
        to_little_endian(value);
    }
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接