【问题标题】:How to implement serialization and de-serialization of a double?如何实现double的序列化和反序列化?
【发布时间】:2014-11-26 12:27:42
【问题描述】:

我正在尝试解决相对简单的问题,即能够将双精度写入文件,然后再次将文件读入双精度。基于this answer,我决定使用人类可读的格式。

根据this question,我已经成功地绕过了一些编译器在 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;
}

在我的机器上结果是:

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

也就是说,数字太接近于零而无法正确解析。起初我认为问题在于这个数字是denormal,但似乎并非如此,因为尾数以 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;
}

输出:

serialized:    9.2263152681638151025201733115952403273156653201666065e-318
de-serialized: 9.2263152681638151025201733115952403273156653201666065e-318
ok

总结:

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

【问题讨论】:

  • 十六进制格式用于往返转换。上次我检查(去年?)时,g++ 和 msvc 在支持上都有点不足。但哄着他们合作并不难。
  • 回答问题 #2:这可能是我的“C-way”思维方式,但您可以将 double 复制到 uint64(内存复制,而不是类型转换),序列化uint64,然后在反序列化时执行相反的操作。
  • @barakmanos 谢谢!但这与使用二进制格式不一样吗?我更喜欢使用人类可读的。
  • 您会将其序列化为不同的(但可读的)值。顺便说一句,在实现方面,也许您应该将其序列化为 unsigned char 值的数组,以避免违反严格的别名规则。
  • @MartinDrozdik:可能。在这个功能附近的某个地方有一大堆错误。我发布了一个新问题,stackoverflow.com/questions/27161720/…

标签: c++ qt gcc serialization floating-point


【解决方案1】:

回答问题 #2:

这可能是我的“C-way”思维,但您可以将double 复制到uint64_t(内存复制,而不是类型转换),序列化uint64_t,然后执行反序列化则相反。

这是一个示例(甚至不必从double 复制到uint64_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;

请注意,为了避免破坏strict-aliasing rule,您实际上确实需要先复制它,因为标准并未强制分配doubleuint64_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;

【讨论】:

  • 太棒了!这似乎也可以处理无穷大和 nan。
  • @MartinDrozdik:是的,我正要添加这个概念以回应您对该问题的评论。由于所有可能的uint64_t 值都是“正常”的,因此infNaN 应该没有问题。
  • @MartinDrozdik:同样,至少在理论上,您应该将其内存复制到uint8_t arr[sizeof(small)],因为标准不能保证编译器必须分配uint64_t 和@ 987654336@ 具有相同的(64 位)地址对齐。
  • 为了完整起见,这个答案是否可以跨不同架构的系统移植(例如 ARM 到 x86)?
  • 虽然这个答案有效,但它非常浪费,不应该在空间和性能很重要的系统中使用。 frexp 的另一个答案是好的。
【解决方案2】:

如果您对其他录制方法持开放态度,可以使用frexp

#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);

    int exp;
    double x = frexp(small, &exp);

    //std::cout << x << " * 2 ^ " << exp << std::endl;
    stream << x << " * 2 ^ " << exp;

    int outexp;
    double outx;

    stream.seekg(0);

    stream >> outx;
    stream.ignore(7); // >> " * 2 ^ "
    stream >> outexp;

    //std::cout << outx << " * 2 ^ " << outexp << std::endl;

    std::cout << small << std::endl << outx * pow(2, outexp) << std::endl;

    return 0;
}

【讨论】:

    猜你喜欢
    • 2020-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    • 2023-04-06
    • 2012-03-17
    相关资源
    最近更新 更多