0

I am writing a binary I/O for storing data in my application.

For illustration consider I want to store a double array of size 10 to the file.

Now since it is not guaranteed that double uses 8 bytes on all platforms, the reader of the file needs to be be modified a bit. Although I am using Qt I think the problem is mainly in the way data read in char * is translated into double. The data read is almost zero.

For example, 1 is read as 2.08607954259741e-317.

Why is every double being read as zero even thought it is not?

void FileString::SaveBinary()
{
    QFile *file = new QFile(fileName);
    if (!file->open(QFile::WriteOnly))
    {
        QString err = file->errorString();
        QString *msgText = new QString("Could not open the file from disk!\n");
        msgText->append(err);
        QString *msgTitle = new QString("ERROR: Could not open the file!");
        emit errMsg(msgTitle, msgText, "WARNING");
        delete file;
        return;
    }
    QDataStream out(file);
    QString line = "MyApp";
    out << line;
    line.setNum(size);//size = 10
    out << line;
    line.setNum(sizeof(double));
    out << line;

    for(int i = 0; i < size; i++)
    {
        out << array[i];
    }

    if(out.status() != QDataStream::Ok)
    {
        qCritical("error: " + QString::number(out.status()).toAscii());
    }
    file->close();
    delete file;
}

void FileString::ReadBinary()
{
    bool ok = false;
    QString line = "";
    QFile *file = new QFile(fileName);
    if (!file->open(QFile::ReadOnly))
    {
        QString err = file->errorString();
        QString *msgText = new QString("Could not open the file from disk!\n");
        msgText->append(err);
        QString *msgTitle = new QString("ERROR: Could not open the file!");
        emit errMsg(msgTitle, msgText, "WARNING");
        delete file;
        return;
    }

    QDataStream in(file);
    in >> line;
    if(line.simplified().contains("MyApp"))
    {
        in >> line;
        size = line.simplified().toInt();
        if(size == 10)
        {
            int mysize = 0;
            in >> line;
            mysize = line.simplified().toInt();
            if(1)//this block runs perfect
            {
                for(int i = 0; i < size; i++)
                {
                    in >> array[i];
                }

                if(in.status() == QDataStream::Ok)
                    ok = true;
                }
            }
            else if(1)//this block reads only zeros
            {
                char *reader = new char[mysize + 1];
                int read = 0;
                double *dptr = NULL;
                for(int i = 0; i < size; i++)
                {
                    read = in.readRawData(reader, mysize);
                    if(read != mysize)
                    {
                        break;
                    }

                    dptr = reinterpret_cast<double *>(reader);//garbage data stored in dptr, why?
                    if(dptr)
                    {
                        array[i] = *dptr;
                        dptr = NULL;
                    }
                    else
                    {
                        break;
                    }
                }


                if(in.status() == QDataStream::Ok)
                    ok = true;
                delete[] reader;
            }
        }
    }

    if(!ok || (in.status() != QDataStream::Ok))
    {
        qCritical("error : true" + " status = " + QString::number((int) in.status()).toAscii());
    }
    file->close();
    delete file;
}

EDIT:

Contents of the generated file

   & M y A p p   1 . 1 8 . 3 . 0    1 0    8?ð      @       @      @      @      @      @      @       @"      @$      

That is supposed to contain:

MyApp 1.18.3.010812345678910

"MyApp 1.18.3.0" "10" "8" "12345678910"
CoderDojo
  • 65
  • 1
  • 12
Cool_Coder
  • 4,888
  • 16
  • 57
  • 99
  • 1
    That is a statement, not a question. Please make sure you have a question so that we do not need to guess what the question may be. Also, please share your file content, etc. – László Papp Jan 11 '14 at 10:02
  • @Lazlo sorry I did not make it clear. Please see the updated question. – Cool_Coder Jan 11 '14 at 10:05
  • You could have provided a much smaller example to illustrate the problem. – Ixanezis Jan 11 '14 at 10:08
  • @Cool_Coder: as requested, paste the content of "file". – László Papp Jan 11 '14 at 10:42
  • @Lazlo added the contents, not sure whether anything can be deduced from it though.... – Cool_Coder Jan 11 '14 at 10:58
  • @Cool_Coder: why did you think that the double to string conversion would be bad as opposed to trying to write with bits that are changing? I think if you replace "line.setNum(size);" with "line.setNum(sizeof(double));" that would also work in this case as you may already know, but why not use this method? http://doc-snapshot.qt-project.org/qdoc/qstring.html#number-2 – László Papp Jan 11 '14 at 11:06
  • because in actual practise there are millions of double variables which take many minutes for translation to strings. – Cool_Coder Jan 11 '14 at 11:07
  • I am not sure that holds true. Can you send a program with benchmarks for that? – László Papp Jan 11 '14 at 11:07
  • Show the benchmark result and corresponding code, please. Also, have you tried the sizeof(double) suggestion instead of "10"? Oh, and double will be slow anyway on certain embedded platforms. You may want to reconsider the double theory if you are concerned about performance unless you do not care about embedded. – László Papp Jan 11 '14 at 11:22
  • "10" denotes the size of array, "8" denotes the sizeof double. This program is for scientific calculations. So I do not expect it to be used on embedded platforms since it invlolves a serious amount of number crunching. I think there is a lot of confusion here. Maybe I will stick to binary strings file. Thanks for your suggestion. – Cool_Coder Jan 11 '14 at 11:28
  • @Cool_Coder: well, you did not actually accept my suggestion, but you are welcome either way. – László Papp Jan 11 '14 at 11:32

2 Answers2

1

What do you expect to read if sizeof double on read platform differs from sizeof double on write platform?

Suppose sizeof double on your write platform was 10. Then you stored a sequence of 10 bytes in a file that represents a 10-byte double. Then, if sizeof double on your read platform is 8, you would try to parse bits of an 10-byte double into an 8-byte and that would obviously end up with garbage.

Here's a more intuitive example with ints: If you a have a 2-byte integer number, say 5. If you store it in binary file, you'll get a sequence of 2 bytes: 00000000 00000101. Then, if you try to read the same number as a 1-byte int, you'll manage to read only the first byte, which is 00000000 and get just zero as a result.

Consider using strings to save doubles for portability https://stackoverflow.com/a/6790009/817441

Community
  • 1
  • 1
Ixanezis
  • 1,631
  • 13
  • 20
  • I address that his doubles are read as garbage. The fact that doubles 'are nearly zero' is just a special case of that. The OP is not only aware of 8-10 bit differences, but it is really so in his code. I believe the answer explains exactly what OP wants, because if the question was really about 'why is what I've read is close to zero?', then it would have been formulated as 'How a doubles represented in memory?'. AFAICT, you haven't devoted much time in OP's question and code investigation. – Ixanezis Jan 11 '14 at 10:52
  • @Lazlo that is exactly what I am talking about! I am reading/writing currently on my laptop. So atleast on the same machine the code should work. Lxanezis do you agree? – Cool_Coder Jan 11 '14 at 11:06
  • If that is true, then `if(mysize == sizeof(double))` should had been evaluated `true`. As I can learn from the code, the problem appears in another branch of the code, when `mysize != sizeof(double)`. This is why I was pretty sure sizes do not match and it leads to incorrect double reading. – Ixanezis Jan 11 '14 at 11:06
  • it does evaluate to true. And the code in that block runs perfectly. But for debugging purpose if I set it to false so that the next bock runs then I getting the zero problem. So the created binary file is definitely correct. – Cool_Coder Jan 11 '14 at 11:08
1

Note that in your original code sizeof(double) could work instead of the hard-coded string, but it will not as long as to migrate to a different architecture with a different double size on it.

As a side note if you are worried about the performance of the double to string conversion, you may have more problems when your users or you would like to move to embedded later. I have just run some conversions in a loop, and it is not that bad on my old laptop either. Here is my very poor benchmark result:

time ./main 

real    0m1.244s
user    0m1.240s
sys     0m0.000s

I would like to point it out again that it is an old laptop.

for the code:

#include <QString>

int main()
{
    for (int i = 0; i < 1000000; ++i)
        QString::number(5.123456789012345, 'g', 15);
    return 0;
}

So, instead of the non-portable direct write, I would suggest to use the following method:

QString QString::number(double n, char format = 'g', int precision = 6) [static]

Returns a string equivalent of the number n, formatted according to the specified format and precision. See Argument Formats for details.

Unlike QLocale::toString(), this function does not honor the user's locale settings.

http://doc-snapshot.qt-project.org/qdoc/qstring.html#number-2

Having discussed all this theretically, I would be writing something like this if I were you:

void FileString::SaveBinary()
{
    QFile *file = new QFile(fileName);
    if (!file->open(QFile::WriteOnly))
    {
        QString err = file->errorString();
        QString *msgText = new QString("Could not open the file from disk!\n");
        msgText->append(err);
        QString *msgTitle = new QString("ERROR: Could not open the file!");
        emit errMsg(msgTitle, msgText, "WARNING");
        delete file;
        return;
    }
    QDataStream out(file);
    QString line = QString::number(myDouble);
    out << line;

    for(int i = 0; i < size; i++)
    {
        out << array[i];
    }

    if(out.status() != QDataStream::Ok)
    {
        qCritical("error: " + QString::number(out.status()).toAscii());
    }
    file->close();
    delete file;
}

One portable option could be to use long double, but of course that would increase the computation at other places, so depending on the scenario, it may or may not be an option.

Community
  • 1
  • 1
László Papp
  • 51,870
  • 39
  • 111
  • 135
  • For my application I am already using text files. The text is generated using QString::number(). However when there are many million double variables involved the time for translation is large. Also the text file size becomes large. That is why I wanted to add another file format for binary I/O. It looks like using raw binary files consisting of double variables is not portable at all. So I will add a warning message when the binary file is opened in an incompatible PC. – Cool_Coder Jan 11 '14 at 11:35
  • You benchmarked by converting the double to a QString only upto 6 (default) decimal digits. Try for 15 digits then you will understand the bottleneck. – Cool_Coder Jan 11 '14 at 12:02
  • Well you are benchmarking incorrectly still! By passing 15 digit to QString::number() do you expect it to print to the file with precision of 15 digits? Instead you should use QString::number(5.123, 'g', 15); This requires 1270ms on my laptop. That is why I downvoted. – Cool_Coder Jan 11 '14 at 12:13
  • @Lazlo I definitely appreciate you spending time for me. But just look where you diverted our discussion to? Don't you see why I am not converting double to QString now? I told this already to you, but you wouldn't listen. My question was pretty simple, "how to read/write double variables in a portable manor". Ixanezis answered exactly what I wanted to know. But still as an appreciation for helping me I will give you an upvote, please don't get me wrong here... – Cool_Coder Jan 11 '14 at 12:24
  • @Cool_Coder: my name is "Laszlo". :) Yes. the other explain the high-level concept as well. I just wished to provide a bit more concrete answer with benchmark and code included, as well as alternative approaches I believe progammers.stackexchange.com is better for only high-level dicussions. Have you seen the "long double" at the end added in a subsequent edit? Also, fwiw, sizeof(double) write works here, and IMO should. I hope the benchmark is also useful. :) Not requesting to select my answer, just explaining. – László Papp Jan 11 '14 at 14:16