1

We cast a data from char* array do double as in the following function:

double getDouble(const char* szData, const size_t dataLength)
{
    double res = 0;
    if(dataLength == 8)
    {
        ub8 doubleData = *(ub8*)(szData);
        doubleData = ntohll(doubleData);
        double* pDoubleData = (double*)(&doubleData);
        res = *pDoubleData;
    }
    return res;
}

ub8 has size as 8 byte, unsigned long long. And the double value -1.1512299550195975 is converted as 3.6975400899608046. But input and output should be equal(equal to -1.1512299550195975). This case is only happened in AIX. In the another platforms we get correct result.

We use optimization level O2 for our project. If I used optimization level as O1 , then data is converted correctly. Can you help me, please, what do you think, why conversion is correct for optimization level O1 and why it is incorrect for optimization level O2? May be I should to turn off or on some flags for aix in the compilation? Thank you. Aix compiler version is 13.1.3.

We get double value in the char array format from binary file. We parsing data from transaction logs of oracle database. And oracle writes , for example, double value as '40 0d 94 8f e6 10 3e 93' and we should convert is as '-1,1512299550195975' in the double type value. But in the only aix we get '3,6975400899608046' incorrect value

  • I seriously doubt this works correctly in *any* optimization setting... what is `ub8`? If that's some 8-bit value, you have just massacred your floating point value down to one byte. Are you aware that `ntohll()` works on *integers*? Can you provide a [mcve]? – DevSolar Aug 01 '18 at 13:42
  • ub8 has size as 8 byte, unsigned long long – Эльфия Валиева Aug 01 '18 at 13:45
  • The code is so nasty that you'll probably need to turn off strict aliasing, at the very least. – Paul R Aug 01 '18 at 13:45
  • @ЭльфияВалиева: Again, why are you storing a floating point value in an integer, *process* that integer with an integer function intended to correct *byte order*, then expect the mash you made of all those bits to still be a valid floating point value? – DevSolar Aug 01 '18 at 13:49
  • And why is floating point data passed around as `char *` in the first place? The mind boggles... – DevSolar Aug 01 '18 at 13:52
  • Works for me (with gcc, I don't have xlc). Mind you, in PowerPC (big endian) `ntohll` should be identical. – Lorinczy Zsigmond Aug 01 '18 at 13:56
  • @DevSolar We get double value in the char array format from binary file. We parsing data from transaction logs of oracle database. And oracle writes , for example, double value as '40 0d 94 8f e6 10 3e 93' and we should convert is as '-1,1512299550195975' in the double type value. But in the only aix we get '3,6975400899608046' incorrect value – Эльфия Валиева Aug 01 '18 at 13:58
  • 3
    This is very strange, these values are consistent with *complementing* the raw value before reinterpreting it as double, instead of rearranging its bytes. – harold Aug 01 '18 at 14:08
  • indeed, the binary values of these numbers are `-1.15123: bff26b7019efc16c` and `3.69754: 400d948fe6103e93`, their sum is `ffffffffffffffff` – Lorinczy Zsigmond Aug 01 '18 at 14:14
  • Maybe a simpler code wouldn't provoke the optimizer's bug: `ub8 tmp= ntohll(*(ub8 *)szData); return *(double *)&tmp;` – Lorinczy Zsigmond Aug 01 '18 at 15:40
  • thank you, I used -qnoansialias to turn off aling strict, and it is help – Эльфия Валиева Aug 02 '18 at 11:40

1 Answers1

0

Your implementation is relying on Undefined Behaviour, particularly violating the Strict Aliasing rule, as somebody has commented.

That is why sometimes it may work (without optimization) and sometimes no.

To avoid violating the Strict Aliasing, you can use one of the following:

Only in C (not C++, More info here):

typedef union swap64{
    unsigned long long u64;
    double d64;
}swap64;
swap64 s;
memcpy(s.u64, szData, sizeof(swap64));
s.u64=ntohll(s.u64);

//Following line is UB in C++, but not in C:
return s.d64;

Or without UB (code valid in both C++ and C):

unsigned long long u64;
double d64;

//Most compilers will remove the memcpy calls and replace them by simpler instructions
memcpy(&u64, szData, sizeof(u64));
u64=ntohll(u64);
memcpy(&d64, &u64, sizeof(u64));

return d64;
LoPiTaL
  • 2,495
  • 16
  • 23
  • @ЭльфияВалиева If it solved your issue, please, accept the answer. – LoPiTaL Aug 02 '18 at 09:54
  • no, we decided to use -qnoansialias flag, and It is help. We think that using union can be occurred some errors, because this union data can be stored differently for different platforms and we supported several platforms of them – Эльфия Валиева Aug 02 '18 at 11:38
  • The union version also causes UB, it would be better not to mention it. The memcpy version is best. – M.M Aug 02 '18 at 11:54
  • Yes, that is why I have also posted the memcpy version, which does not rely on UB. Adding the -qnoansialias flag will disable some optimizations based on the strict aliasing, which may impact the performance of your system. Also, it is non portable across compilers. – LoPiTaL Aug 02 '18 at 15:23
  • @M.M I have added an edit, in order to state that the UB is only in C++, not in C, which is why I have added it as an option – LoPiTaL Aug 02 '18 at 15:42