Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong conversion from double to QString in Qt on ARM

Tags:

c++

qt

arm

I have Qt 4.4.3 built for ARMv5TE. I try to convert a double to a QString:

#include <QtCore/QtCore>
#include <cmath>

int main(int argc, char** argv)
{
  const double pi = M_PI;
  qDebug() << "Pi is : " << pi << "\n but pi is : " << QString::number(pi, 'f', 6);
  printf("printf: %f\n",pi);

  return 0;
}  

but get strange output:

Pi is :  8.6192e+97 
but pi is :  "86191995128153827662389718947289094511677209256133209964237318700300913082475855805240843511529472.0000000000000000"
printf: 3.141593

How do I get the proper string?

like image 377
Renat Zaripov Avatar asked Apr 13 '12 13:04

Renat Zaripov


2 Answers

This looks to be a sort of endianess issue, but not your plain-vanilla big-endian vs little-endian problem. ARM sometimes uses an unusual byte ordering for double. From "Handbook of Floating-Point Arithmetic" by Jean-Michel Muller, et al.:

... the double-precision number that is closest to -7.0868766365730135 x 10^-268 is encoded by the sequence of bytes 11 22 33 44 55 66 77 88 in memory (from the lowest to the highest one) on x86 and Linux/IA-64 platforms (they are said to be little-endian) and by 88 77 66 55 44 33 22 11 on most PowerPC platforms (they are said to be big-endian). Some architectures, such as IA-64, ARM, and PowerPC are said to be bi-endian. i.e., they may be either little-endian or big-endian depending on their configuration.

There exists an exception: some ARM-based platforms. ARM processors have traditionally used the floating-point accelerator (FPA) architecture, where the double-precision numbers are decomposed into two 32-bit words in the big-endian order and stored according to the endianess of the machine, i.e., little-endian in general, which means that the above number is encoded by the sequence 55 66 77 88 11 22 33 44. ARM has recently introduced a new architecture for floating-point arithmetic: vector floating-point (VFP), where the words are stored in the processor's native byte order.

When looked at in a big-endian byte order, M_PI will have a representation that looks like:

0x400921fb54442d18

The large number approximated by 8.6192e+97 wil have a representation that looks like:

0x54442d18400921fb

If you look closely, the two 32-bit words are swapped, but the byte order within the 32-bit words is the same. So apparently, the ARM 'traditional' double point format seems to be confusing the Qt library (or the Qt library is misconfigured).

I'm not sure if the processor is using the traditional format and Qt expects it to be in VFP format, or if things are the other way around. But it seems to be one of those two situations.

I'm also not sure exactly how to fix the problem - I'd guess there's some option for building Qt to handle this correctly.

the following snippet will at least tell you what format for double the compiler is using, which may help you narrow down what needs to change in Qt:

unsigned char* b;
unsigned char* e;

double x = -7.0868766365730135e-268;

b = (unsigned char*) &x;
e = b + sizeof(x);

for (; b != e; ++b) {
    printf( "%02x ", *b);
}

    puts("");

A plain little-endian machine will display:

11 22 33 44 55 66 77 88

Update with a bit more analysis:

At the moment, I'm unable to perform any real debugging of this (I don't even have access to my workstation at the moment), but by looking at the Qt source available on http://qt.gitorious.org here's additional analysis:

It looks like Qt calls in to the QLocalePrivate::doubleToString() function in qlocale.cpp to convert a double to an alphanumeric form.

If Qt is compiled with QT_QLOCALE_USES_FCVT defined, then QLocalePrivate::doubleToString() will use the platform's fcvt() function to perform the conversion. If QT_QLOCALE_USES_FCVT is not defined, then QLocalePrivate::doubleToString() ends up calling _qdtoa() to perform the conversion. That function examines the various fields of the double directly and appears to assume that the double is in a strict big-endian or little-endian form (for example, using the getWord0() and getWord1() functions to get the low and high word of the double respectively).

See http://qt.gitorious.org/qt/qt/blobs/HEAD/src/corelib/tools/qlocale.cpp and http://qt.gitorious.org/qt/qt/blobs/HEAD/src/corelib/tools/qlocale_tools.cpp or your own copy of the files for details.

Assuming that your platform is using the traditional ARM FPA representation for double (where the 32-bit halves of the double are stored in big-endian order regardless of whether the overall system being little-endian), I think you'll need to build Qt with the QT_QLOCALE_USES_FCVT defined. I believe that all you'll need to do is pass the -DQT_QLOCALE_USES_FCVT option to the configure script when building Qt.

like image 188
Michael Burr Avatar answered Oct 13 '22 22:10

Michael Burr


The same code produces proper output on an x86 machine (running Windows XP) with Qt 4.7.0.

I see the following possibilities for the source of the problem:

  • Some bug which is maybe fixed in a newer version of Qt
  • Something went wrong when compiling for ARM

I found this forum post on a similar problem which supposes it could be a big/little-endian conversion problem.

I can't tell how to fix this as I am not experienced with ARM at all but maybe this information helps you anyway.

like image 30
Tim Meyer Avatar answered Oct 13 '22 22:10

Tim Meyer