简体   繁体   中英

How to implement serialization and de-serialization of a double?

I am trying to solve the relatively simple problem of being able to write a double to a file and then to read the file into a double again . Based on this answer I decided to use the human readable format.

I have successfully circumvented the problems some compilers have with nan and [-]infinity according to this question . With finite numbers I use the std::stod function to convert the string representation of a number into the number itself. But from time to time the parsing fails with numbers close to zero, such as in the following example:

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

On my machine the result is:

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

That is, the number is too close to zero to be properly parsed. At first I thougth that the problem is that this number is denormal , but this doesn't seem to be the case, since the mantissa starts with a 9 and not a 0.

Qt on the other hand has no problems with this number:

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

Outputs:

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

Summary:

  1. Is this a bug in the gcc implementation of standard library?
  2. Can I circumvent this elegantly?
  3. Should I just use Qt?

Answering question #2:

This is probably my "C-way" kind of thinking, but you could copy the double into a uint64_t (mem-copying, not type-casting), serialize the uint64_t instead, and do the opposite on de-serialization.

Here is an example (without even having to copy from double into uint64_t and vice-versa):

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;

Please note that in order to avoid breaking strict-aliasing rule , you actually do need to copy it first, because the standard does not impose the allocation of double and uint64_t to the same address-alignment:

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;

If you're open to other recording methods you can use 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM