How to convert four uint16_t into double in ESP8266 Arduino Core

Dear Gentlemen,
I am working in a Modbus project where I have to read modbus Holding and Input registers using ESP8266, Iam using ModbusMaster232 library. It returns a response buffer of uint16_t type. I need to convert the uint16_t to double precision float point value. A quick test in ESP8266 shows double datatype in ESP8266 stores 8 bytes in memory so that would be a good option. Does anybody know how to convert four uint16_t to double floating point value (according to IEEE754) in Arduino. If so please share your knowledge. Thanks in advance

Regards,
Mr.B

Brinth:
If so please share your knowledge.

Start by sharing your code. Use Code Tags.

why won't simply setting a double variable to the value of a uint16_t do what you want?

    uint16_t  u [N] = { 1, 2, 3};
    double    d [N];

    for (int i = 0; i < N; i++)  {
        d [i] = u [i];
        printf ("  %f\n", d [i]);
    }
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][color=#000000]cstdint[/color][color=#434f54]>[/color]   [color=#434f54]// uint16_t[/color]
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][color=#000000]cstring[/color][color=#434f54]>[/color]   [color=#434f54]// memcpy[/color]
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][color=#000000]algorithm[/color][color=#434f54]>[/color] [color=#434f54]// std::transform[/color]
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][b][color=#d35400]iterator[/color][/b][color=#434f54]>[/color]  [color=#434f54]// std::reverse_iterator, std::rbegin[/color]

[color=#5e6d03]#if[/color] [color=#000000]__cplusplus[/color] [color=#434f54]<[/color] [color=#000000]201402L[/color]
[color=#5e6d03]template[/color] [color=#434f54]<[/color][color=#00979c]class[/color] [color=#000000]T[/color][color=#434f54],[/color] [b][color=#d35400]size_t[/color][/b] [color=#000000]N[/color][color=#434f54]>[/color]
[color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][b][color=#d35400]reverse_iterator[/color][/b][color=#434f54]<[/color][color=#000000]T[/color] [color=#434f54]*[/color][color=#434f54]>[/color] [color=#000000]rbegin[/color][color=#000000]([/color][color=#000000]T[/color] [color=#000000]([/color][color=#434f54]&[/color][color=#000000]t[/color][color=#000000])[/color][color=#000000][[/color][color=#000000]N[/color][color=#000000]][/color][color=#000000])[/color] [color=#000000]{[/color] 
  [color=#5e6d03]return[/color] [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][b][color=#d35400]reverse_iterator[/color][/b][color=#434f54]<[/color][color=#000000]T[/color] [color=#434f54]*[/color][color=#434f54]>[/color][color=#000000]([/color][color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#d35400]end[/color][color=#000000]([/color][color=#000000]t[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color] 
[color=#000000]}[/color]
[color=#5e6d03]#else[/color]
[color=#5e6d03]using[/color] [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#000000]rbegin[/color][color=#000000];[/color]
[color=#5e6d03]#endif[/color]

[color=#00979c]double[/color] [color=#000000]uint16s_to_double[/color][color=#000000]([/color][color=#00979c]const[/color] [color=#00979c]uint16_t[/color] [color=#434f54]*[/color][color=#000000]data[/color][color=#000000])[/color] [color=#000000]{[/color]
[color=#5e6d03]#if[/color] [color=#000000]__BYTE_ORDER__[/color] [color=#434f54]==[/color] [color=#000000]__ORDER_LITTLE_ENDIAN__[/color]
  [color=#434f54]// Convert from Big-Endian Modbus data to native Little-Endian[/color]
  [color=#00979c]uint16_t[/color] [color=#000000]native_data[/color][color=#000000][[/color][color=#000000]4[/color][color=#000000]][/color][color=#000000];[/color]
  [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#000000]transform[/color][color=#000000]([/color][color=#000000]data[/color][color=#434f54],[/color] [color=#000000]data[/color] [color=#434f54]+[/color] [color=#000000]4[/color][color=#434f54],[/color] [color=#000000]rbegin[/color][color=#000000]([/color][color=#000000]native_data[/color][color=#000000])[/color][color=#434f54],[/color] [color=#000000]__builtin_bswap16[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#434f54]//             \-- source --/  \-- destination --/  \-- operation --/[/color]
  [color=#434f54]//                                  (reverse)[/color]
[color=#5e6d03]#else[/color]
  [color=#00979c]const[/color] [color=#00979c]uint16_t[/color] [color=#434f54]*[/color][color=#000000]native_data[/color] [color=#434f54]=[/color] [color=#000000]data[/color][color=#000000];[/color]
[color=#5e6d03]#endif[/color]
  [color=#000000]static_assert[/color][color=#000000]([/color][color=#00979c]sizeof[/color][color=#000000]([/color][color=#00979c]double[/color][color=#000000])[/color] [color=#434f54]==[/color] [color=#000000]4[/color] [color=#434f54]*[/color] [color=#00979c]sizeof[/color][color=#000000]([/color][color=#00979c]uint16_t[/color][color=#000000])[/color][color=#434f54],[/color] [color=#005c5f]"double is wrong size"[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#00979c]double[/color] [color=#000000]result[/color][color=#000000];[/color]
  [color=#d35400]memcpy[/color][color=#000000]([/color][color=#434f54]&[/color][color=#000000]result[/color][color=#434f54],[/color] [color=#000000]native_data[/color][color=#434f54],[/color] [color=#00979c]sizeof[/color][color=#000000]([/color][color=#00979c]double[/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#5e6d03]return[/color] [color=#000000]result[/color][color=#000000];[/color]
[color=#000000]}[/color]

[color=#5e6d03]#include[/color] [color=#434f54]<[/color][color=#000000]iostream[/color][color=#434f54]>[/color]
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][color=#000000]iomanip[/color][color=#434f54]>[/color]

[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
 [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000]115200[/color][color=#000000])[/color][color=#000000];[/color]
 [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000])[/color][color=#000000];[/color]
 [color=#434f54]// π as a Big Endian IEEE 754 double is 40,09,21,fb,54,44,2d,18:[/color]
 [color=#00979c]uint16_t[/color] [color=#000000]data[/color][color=#000000][[/color][color=#000000]][/color] [color=#434f54]=[/color] [color=#000000]{[/color][color=#000000]0x0940[/color][color=#434f54],[/color] [color=#000000]0xfb21[/color][color=#434f54],[/color] [color=#000000]0x4454[/color][color=#434f54],[/color] [color=#000000]0x182d[/color][color=#000000]}[/color][color=#000000];[/color]
 [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][b][color=#d35400]cout[/color][/b] [color=#434f54]<<[/color] [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#000000]scientific[/color] [color=#434f54]<<[/color] [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#000000]setprecision[/color][color=#000000]([/color][color=#000000]17[/color][color=#000000])[/color] 
           [color=#434f54]<<[/color] [color=#000000]uint16s_to_double[/color][color=#000000]([/color][color=#000000]data[/color][color=#000000])[/color] [color=#434f54]<<[/color] [color=#000000]std[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#000000]endl[/color][color=#000000];[/color]
[color=#000000]}[/color]

[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color][color=#000000]}[/color]

You might have to reverse the Endianness #if statement, depending on the Endianness of the device you're trying to talk to.

Pieter

Assume your buffer contains the following items for the floating number 11.27 in binary64 standard.

uint16_t myBuff[] = {0xD70A, 0x70A3, 0x8A3D, 0x4026};//low item in low location

Check if the following test sketch is useful to you (tested in Arduino DUE, where double is binary64 size).

uint16_t myBuff[] = {0xD70A, 0x70A3, 0x8A3D, 0x4026};//low item in low location

union
{
  double y;
  uint16_t myData[4];
}data;

void setup()
{
  Serial.begin(9600);
  double x = 11.27;
  long *ptr;
  ptr = (long*) &x;
  long mL = *ptr;
  Serial.println(mL, HEX); //70A3D70A

  ptr++;
  long mH = *ptr;
  Serial.println(mH, HEX); //40268A3D
  Serial.println("===================");
  for(int i=0; i<4; i++)
  {
    data.myData[i] = myBuff[i];
  }
  Serial.println(data.y, 2); //11.27
}

void loop()
{

}

@GolamMostafa, as discussed many times on this forum already, type punning using unions or by reinterpreting pointers to different types* is undefined behavior. The code you posted is invalid.

Until we get std::bit_cast on Arduino (we're still three versions of the standard behind), using memcpy is the only allowed method for type punning.

(*) pointers to character types like char, unsigned char and std::byte are an exception.

@Brinth's poorly-worded question does not make clear what "convert" means. There are different answers depending on whether it means "keep the value but represent it as a double variable" or "copy the exact byte pattern into the memory locations allocated for a double variable".

GolamMostafa:
One is safe as long as he knows that there is a potential hazard?

No, one is absolutely not. You have no guarantee that the compiler generates any meaningful code.

A couple of months back, I worked on a code base with some undefined reinterpreting of pointers like in your example, and simply adding or removing a print statement ten lines below the cast resulted in completely different results.

There is no reason to use your methods over the well-defined memcpy.

Besides, how is OP supposed to know that there is a potential hazard if you just give him ill-formed code without any warnings?

PieterP:
There is no reason to use your methods over the well-defined memcpy.

But, you have posted a highly bureaucratic sketch in Post#3, which is not even compiled in my Arduino IDE/DUE Platform. can you make it elementary, simple and popular so that I can run it on my DUE?

If you ignore the Endianness (you shouldn't), it boils down to

double uint16s_to_double(const uint16_t *data) {
static_assert(sizeof(double) == 4 * sizeof(uint16_t), "double is wrong size");
double result;
memcpy(&result, data, sizeof(double));
return result;
}

On a given processor with given data types, you don't even need that much. Once you know the sizes of each type, that's not going to change. Just do the memcpy() and be done with it.

Of course, you're assuming that OP wants to simply copy the byte pattern. As I pointed out, we really don't know if that's the case.

GolamMostafa:
But, you have posted a highly bureaucratic sketch in Post#3, which is not even compiled in my Arduino IDE/DUE Platform. Can you make it elementary, simple and popular so that I can run it on my DUE?

The Due has an ancient toolchain and a standard library that wasn't ported correctly. To make it compile there, simply remove the #include and , and use Serial.println(uint16s_to_double(data), 17) instead of cout << ...

gfvalvo:
On a given processor with given data types, you don't even need that much. Once you know the sizes of each type, that's not going to change. Just do the memcpy() and be done with it.

That's true, but it's very likely that you'll later want to reuse the code after changing the toolchain or target microcontroller, and it's nice to have the check in place, it'll probably save you a lot of trouble down the line.

gfvalvo:
Of course, you're assuming that OP wants to simply copy the byte pattern. As I pointed out, we really don't know if that's the case.

You're right, he seems to be having a lengthy discussion about that in the Stack Exchange comments/chat as well.

PieterP:
If you ignore the Endianness (you shouldn't), it boils down to

double uint16s_to_double(const uint16_t *data) {
static_assert(sizeof(double) == 4 * sizeof(uint16_t), "double is wrong size");
double result;
memcpy(&result, data, sizeof(double));
return result;
}

gfvalvo:
On a given processor with given data types, you don't even need that much. Once you know the sizes of each type, that's not going to change. Just do the memcpy() and be done with it.

At the courtesy of @PieterP and @gfvalvo, the simple and safe sketch for OP to get double float from binary64 is:

uint16_t data[] = {0xD70A, 0x70A3, 0x8A3D, 0x4026};//low item in low location
double result;

void setup()
{
  Serial.begin(9600);
  memcpy(&result, data, sizeof(double));
  Serial.print(result, 2);  //shows: 11.27
}

void loop()
{

}

gfvalvo:
@Brinth's poorly-worded question does not make clear what "convert" means. There are different answers depending on whether it means "keep the value but represent it as a double variable" or "copy the exact byte pattern into the memory locations allocated for a double variable".

Thanks Gentlemen for your time and effort, I tried the union method and it worked fine. But as @PieterP mentions I would go for the memcpy method instead. And I am extremely sorry @gfvalvo for not being wordy :neutral_face:

Regards,
Mr.B

Brinth:
And I am extremely sorry @gfvalvo for not being wordy :neutral_face:

Doesn't need to be wordy. Does need to be clear, complete, and accurate. That's key to being a good engineer.

Thanks sir will keep in mind