Combine TWO uint16_t in ONE float

I have two values in the format uint16_t and I want to combine them into a float.

How do I do it?

(they come from reading an instrument with modbus)

  float num = (((unsigned long)data[0] << 16) | data[1]);

didn't work. I use Arduino Nano.

Can you give an example of the two integers and what you would expect the corresponding float to be?

data[0]=17256 (stable)
data[1]=55111 (varying, so this is the low part of the 32 bit byte)
The combined value shall be around 232,1 ..... 232,8.
But I get 1130 million ,00.

Ok, what do data[0] and data[1] represent? Do you have any idea how you would combine them using pencil & paper?

You could create an union that has both a float and a uint32_t in it.
union members map on the same memory space

union
{
uint32_t x;
float f;
} u;

u.x = (((unsigned long)data[0] << 16) | data[1]);

float z = u.f;

@ ingdemurtas

you must tell the relational formula by which the given numbers are to be combined so that the end result is 232.1!

You have given :
data[0] = 17256 = 0x4368 (upper part of 32-bit)
data[1] = 55111 = 0xD747 (lower word of 32-bit)

DWORD = 0x4368D747 = 1130944327

How do you want to extract 232.8 from 1130944327? You must know the mechanism of this transformation, and only then the mechanism could be turned into executable program codes!

ingdemurtas:
I have two values in the format uint16_t and I want to combine them into a float.

What does "combine" mean? You want to add them together?

I have a 3phase multimeter and I want to read V1 (phase to neutral voltage of phase 1) via modbus, to show it in a LCD display. The value V1 is float (32 bit, right?), and is put into 16 bit modbus registers.
In the instruction manual (attached, page 2, table 1) I see that register 220 is V1, and 222 is V2.
I think that the value of V1 is the "combination" of the two 16 bit bytes (address 220 and 221), into a 32 bit byte, which shall be printed as a float. Is this what you mean by "relational formula"?

About the combination of two 16 bit bytes: I don't know how is made exactly. I guess they are just put one beside the other one, which explain the <<16 (16 bit shift to the left) and the casting to unsigned long. I don't know how the "floating point position" is handled in the byte of a float type.

EDIT: I've got an email from the manufacturer. He said that for V1 I shall only take the register 220, which is already a float.

Istruzioni_installazione_Femto_D4.pdf (376 KB)

But, can the modbus libray read float? (I read in the comment in the example that it reads 16 bit int).

Here is my code:

Did you try the code in post #4? Additionally you can swap unit32_t x for uint16_t x[2] and skip the bit shift.

Your problem is that you collected the combined bits in an unsigned long and then assigned it to a float. The compiler evaluated the number as an unsigned long and then converted that number to a float. What you need to do is to move the bits into a float or use a union as suggested above. You have bigendian issues too. Try this:

unsigned int data[2]={55111,17256};
void setup()
{
Serial.begin(115200);
float num;
memcpy(&num,data,4);
Serial.println(num);  
}

void loop()
{
}
1 Like

The value V1 is float (32 bit, right?),

Now, you have said the right thing! You are receiving 32-bit data as two 16-bit chunks via variables : data[0] and data[1]. These data are the parts of 32-bit binary32 (IEEE-754) formatted number which is num = data[0] << 16 | data[1] = 0x43680000 + 0xD747 = 0x4368D747, and not integers as you mentioned in your original post.

Now, you can get your expected decimal number with fractional part (aka floating point number) by using one of the following programs:

A: Post#4 due to @robtillaart
B: Post#10 due to @wildbill
C: This Post This one is similar to Post#10; but, a different style!

float x;
unsigned long *p;

p = (unsigned long*)&x;

*p = (unsigned long)data[0]<<16 | data[1];

Serial.println(x, 1); //prints 232.8

Union members share the same memory space and its content; but, the content is mapped/interpreted according to the data type of the member. For example: the bit pattern (information) of 0x4368D747 could be seen as integer value or floating point number or array elements.

void setup() 
{
    Serial.begin(9600);  

    union
    {
        uint32_t x;
        float f;
        uint16_t array[] = {0xD747, 0x4368};  
    } u;

    unsigned long y = u.x;
    Serial.println(y, DEC);   //prints integer value : 1130944327

    float z = u.f;
    Serial.println(z, 1);       //prints floating point value: 232.8

}

I made a sketch where I apply the 3 methods.
One works.

Code:

void setup()
{
  Serial.begin(19200);
}

//unsigned int data[2]={55111,17256};
uint16_t data[2]={55111,17256};

void loop() {
  Serial.print("with memcpy: "); 
  float num;
  memcpy(&num,data,4);
  Serial.println(num); 
//--------------------------------------------------
  Serial.print("with union: "); 
  union 
  {
    uint32_t x;
    float f;
  } u;
  u.x = (((unsigned long)data[0] << 16) | data[1]);
  float z = u.f;
  Serial.println(z); 
//--------------------------------------------------
  Serial.print("with pointers: "); 
  float x;
  unsigned long *p;
  p = (unsigned long*)&x;
  *p = (unsigned long)data[0]<<16 | data[1];
  Serial.println(x);
//--------------------------------------------------
  Serial.println(""); 
  delay(3000);
  
}

I Get:

with memcpy: 232.84
with union: ovf
with pointers: ovf

What is wrong?

1 Like

Endian issue.
Try:

 u.x = (((unsigned long)data[1] << 16) | data[0]);
1 Like

It works!

with memcpy: 232.84
with union: 232.84
with pointers: 232.84

Beautiful! I wasn't aware of endian issue, I read in wikipedia now.
Thanks! :slight_smile:

1 Like

Endian issue.

1. As per original Post#2 (of the OP):
data[0] = 17256 (0x4368)
data[1] = 55111 (0xD747)
Decimal Value of data = 232.8

2. Because, the OP has not mentioned which array element (data[0] or data[1]) is the upper part of the 32-bit data; we are in trouble to figure out the bit pattern of the 32-bit data -- it could be 0x4368D747 or 0xD7474368. Certainly, we have faced the endinaness issue.

3. Endinaness refers to the mandatory declaration that lower-most indexed array element (lower-most memory location) contains the lower-most part of the data (Little-endian, Fig-1) or the lower-most indexed array element (lower-most memory location) contains the upper-most part of the data (Big-endian, Fig-1).


Figure-1: Endianness (wikipedia)

4. It follows from Step-2 that the correct extraction of data from the array elements requires the mentioning of endianness.

5. The OP has not mentioned the endianness of his data; but, (fortunately) he has mentioned the decimal value of data, which is 232.8 (Step-1).

6. From the decimal value of Step-5, it has been quickly recognized that that the bit pattern of the data of Step-1 is to be 0x4368D747 to comply with the binary32 (IEEE-754) standard for the representation of floating point number (232.8).

7. The Complete Codes for Post#11 @GolamMostafa

void setup() 
{
  Serial.begin(9600);
  uint16_t data[2];
  data[0] = 17256; //0x4368
  data[1] = 55111; //0xD747
 
  float x;
  unsigned long *p;

  p = (unsigned long*)&x;
  *p = (unsigned long)data[0]<<16 | data[1];  //Big-endian
  Serial.println(x, 1); //prints 232.8
}

void loop() 
{
 
}

Thank You SOOO much!. I got it working. Thanks :slight_smile:

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.