如何实现double的序列化和反序列化?

Mar*_*dik 1 c++ floating-point serialization qt gcc

我试图解决相对简单的问题,即能够将double写入文件然后再次将文件读入double.基于这个答案,我决定使用人类可读的格式.

根据这个问题,我成功地规避了一些编译器对nan[ - ] infinity的问题.对于有限数字,我使用std::stod函数将数字的字符串表示转换为数字本身.但有时解析会因数字接近于零而失败,例如在以下示例中:

#include <cmath>
#include <iostream>
#include <sstream>
#include <limits>

const std::size_t maxPrecision = std::numeric_limits<double>::digits;
const double small = std::exp(-730.0);

int main()
{
    std::stringstream stream;
    stream.precision(maxPrecision);
    stream << small;
    std::cout << "serialized:    " << stream.str() << std::endl;
    double out = std::stod(stream.str());
    std::cout << "de-serialized: " << out << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在我的机器上,结果是:

serialized:     9.2263152681638151025201733115952403273156653201666065e-318
terminate called after throwing an instance of 'std::out_of_range'
  what():  stod
The program has unexpectedly finished.
Run Code Online (Sandbox Code Playgroud)

也就是说,该数字太接近零而无法正确解析.起初我认为问题是这个数字是非正规的,但似乎并非如此,因为尾数以9而不是0开始.

另一方面,Qt对这个数字没有问题:

#include <cmath>
#include <limits>

#include <QString>
#include <QTextStream>

const std::size_t maxPrecision = std::numeric_limits<double>::digits;
const double small = std::exp(-730.0);

int main()
{
    QString string = QString::number(small, 'g', maxPrecision);
    QTextStream stream(stdout);
    stream.setRealNumberPrecision(maxPrecision);
    stream << "serialized:    " << string << '\n';
    bool ok;
    double out = string.toDouble(&ok);
    stream <<  "de-serialized: " << out << '\n' << (ok?"ok":"not ok") << '\n';
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

serialized:    9.2263152681638151025201733115952403273156653201666065e-318
de-serialized: 9.2263152681638151025201733115952403273156653201666065e-318
ok
Run Code Online (Sandbox Code Playgroud)

摘要:

  1. 这是标准库的gcc实现中的错误吗?
  2. 我可以优雅地规避这个吗?
  3. 我应该只使用Qt吗?

bar*_*nos 5

回答问题#2:

这可能是我的"C-way"类型的思考,但你可以将其复制doubleuint64_t(mem-copying,而不是类型转换),序列化uint64_t而不是反序列化.

这是一个例子(甚至不需要复制doubleuint64_t反之亦然):

uint64_t* pi = (uint64_t*)&small;
stringstream stream;
stream.precision(maxPrecision);
stream << *pi;
cout << "serialized:    " << stream.str() << endl;
uint64_t out = stoull(stream.str());
double* pf = (double*)&out;
cout << "de-serialized: " << *pf << endl;
Run Code Online (Sandbox Code Playgroud)

请注意,为了避免打破严格走样规则,你其实需要先复制它,因为标准没有规定的分配double,并uint64_t以相同的地址,比对:

uint64_t ismall;
memcpy((void*)&ismall,(void*)&small,sizeof(small));
stringstream stream;
stream.precision(maxPrecision);
stream << ismall;
cout << "serialized:    " << stream.str() << endl;
ismall = stoull(stream.str());
double fsmall;
memcpy((void*)&fsmall,(void*)&ismall,sizeof(small));
cout << "de-serialized: " << fsmall << endl;
Run Code Online (Sandbox Code Playgroud)