Save bytes in float variable

Hello,

I am trying to save Bytes in a float variable.

I am working with the ESP32 with the CAN BUS and I can´t get the right value in float variables..

I´m sendig over CAN the decimal value 35: wich converted to float is : 0x420c0000
but on the float variable i get: 0x40a88400.

If I store the data the same way in a Integer then i get the : 0x420c0000, is maybe any way to convert that integer hex values into the hex values of the float variable?

void loop()
{
  can_message_t rx_frame;
  if(can_receive(&rx_frame,pdMS_TO_TICKS(1000))==ESP_OK)
  {
    printf("from 0x%08X,DLC%d,data", rx_frame.identifier,rx_frame.data_length_code);
    for(int i=0;i<rx_frame.data_length_code;i++)
    {
      printf(" 0x%02X",rx_frame.data[i]);
      
    }
    //Lat_delta_distance is a float variable.

    Lat_delta_distance = (rx_frame.data[0] << 24) + (rx_frame.data[1] << 16) + (rx_frame.data[2] << 8) + (rx_frame.data[3]); 
    printf("; 0x%08X ; ", Lat_delta_distance);
    Serial.println(""); // linefeed

    printf("\n ");
  }
}

OUTPUT:

so you can see the value of 35 on the data bytes( Red), is not the same as in the float variable (blue).

You convert a bit pattern twice into a float pattern. The right hand side is constructed from bytes and thus is treated like an integral value and converted in the assignment to a float value.

A typecast may help (untested)
Lat_delta_distance = (float)(...);

For simplicity I prefer to use a union of both types and write to the integral part, then read from the float part.

1 Like

That is not guaranteed by the C++ standard to produce defined behavior.

Best to use memcpy():

#include "Arduino.h"
template<typename IntType>
void printHex(IntType val, Print *ptr = &Serial) {
  ptr->print("0x ");
  for (int8_t shift = 8 * sizeof(val) - 4; shift >= 0; shift -= 4) {
    uint8_t hexDigit = (val >> shift) & 0xF;
    ptr->print(hexDigit, HEX);
    if (((shift & 0xF) == 0) && (shift > 0)) {
      ptr->print(" ");
    }
  }
}

template<typename IntType>
void printlnHex(IntType val, Print *ptr = &Serial) {
  printHex(val, ptr);
  ptr->println();
}

const size_t floatSize = sizeof(float);

void setup() {
  uint8_t bytes[floatSize];
  float float1, float2;

  Serial.begin(115200);
  delay(1000);

  float1 = 35.0;
  Serial.print("float1 = ");
  Serial.println(float1);
  memcpy(bytes, &float1, floatSize);
  Serial.println("Viewed as Bytes:");
  for (uint8_t b : bytes) {
    printlnHex(b);
  }

  memcpy(&float2, bytes, floatSize);
  Serial.print("float2 = ");
  Serial.println(float2);
}

void loop() {
}

void funct(char *s) {
  Serial.println(s);
}

Output:

float1 = 35.00
Viewed as Bytes:
0x 00
0x 00
0x 0C
0x 42
float2 = 35.00

What exactly is not guaranteed? It's known that every machine can have its own float format (endianness...), so that the decent pattern interpretation is not guaranteed in general. Consequently memcpy() also can not be guaranteed in general.

In the actual (CAN) context the bit-preserving conversion is exact. Eventually a ntohl() conversion should be inserted.

The specification states that if you write in an Union using one member then you can only read back safely using that member too. In practice if you created a union with just the float and the exact count of bytes (4) then it is very likely that it will work, likely but still undefined behavior.

memcpy() will guarantee that the 4 bytes are copied in the right place.

2 Likes

https://en.cppreference.com/w/cpp/language/union : "It's undefined behavior to read from the member of the union that wasn't most recently written.".

I didn't even know that :dizzy_face: Thanks.

Even the Arduino Uno has the single precision float according to the IEEE format. When an Arduino board talks to another device, then only the byte order is important.

The same is guaranteed by a union.

But none guarantees that the bytes are in the right order - see endianness, ntoh(), hton(). Also see "type coercion" vs. "type conversion".

It's most definitely not. Read again Post #6.
Of course you have to get LSB, MSB correct.

1 Like

Give me a concrete example where union and memcpy() yield different results.

That's not necessary. It's simple enough. The language standard states that writing a value of one type to a union and then extracting it as another type invokes undefined behavior. That means that the compiler is not required to create any defined code in response. It may create no code at all. It may create code that "works" in one instance and code that crashes the processor in another.

Continuing to stubbornly insist that it's OK to use a union in this way will not magically change the language standard. On the other hand the standard does require memcpy() used in this way to produce defined results.

1 Like

Yeah, As said above, most of the time is the case is simple enough it will work.
But you miss the point here. It’s not because it works that it’s legit.

I can’t test for the moment but on an ESP32 If you were to take a struct with a byte and an char[3] and a uint32_t and make a union with a 7 byte array, it is possible (due to possible compiler alignment optimization in the struct on 32 bits boundaries or moving members around) that the 7 byte array would not hold the previous bytes, or not in the order you expect.

Using 3 memcpy(). One for each member, into a 7 byte array would create the correct representation

1 Like

That's right in general, but does not apply to 2 variables of the same type size with the same alignment in the same bytes.

Perhaps a quantum computer behaves differently, or computers of different bit count of bytes etc., but on the same controller, in the same 4 bytes of memory, no magic can swap or move around bits or bytes when reading.

The standard is very defensive here but in our case of 4 byte long and float overlay there is no chance for different behaviour. Else the machine were broken.

“The machine” doesn't evaluate your C++ code, the compiler does, and compilers rely on certain preconditions when carrying out optimizations. If you break those preconditions, the code it generates will not do what you intended. That's why the standard describes what's allowed and what's not, and compiler writers generally follow the standard to know what assumptions they can make to optimize your code.
For example, it is perfectly fine for a compiler to do dead store elimination on your union members. If you write to member a and never read from it again, only from member b, it is free to just optimize out the store to a.
Most compilers probably won't, for the sake of backwards compatibility with C, but “it seems to work in the cases I've tested” is not a good argument to justify writing bad code. If the standard explicitly disallows it, don't do it.

Besides, memcpy is shorter and clearer.
In C++20, there's bit_cast which shows the intent even more clearly. You could even write bit_cast yourself today as a wrapper around memcpy (if you don't need it to be constexpr).

I dare to disagree. Optimization never can change program behaviour, except for time and memory requirements.

Do you get a "possibly uninitialized" warning when using a union as described?

Exactly, and the behavior of a C++ program is defined by the C++ standard, which states that the behavior of reading an inactive union member is undefined.

No, because compilers are not required to diagnose undefined behavior. See Undefined behavior - cppreference.com.

1 Like

Not if the code is not optimized.

It is UB regardless of optimization. The standard says so

Memcpy() is the recommended way

Now you can code the way you want (and it will likely work), but it won’t make it OK by the norm

Reading and writing 4 bytes is not at all undefined. If the writing statement is not executed then this only can be due to optimization.

I think that's the key point @ DrDiettrich. You can implement whatever kludgy, hacky code you like for your own projects. But, don't recommend to others techniques that violate the language standard.

I don't see a violation of the standard in this case. But I think that this here is a matter of interpretation. Some people interpret the standard in a shallow (literal only) way, others think a bit more about it and take into account related definitions, context and facts. To me "undefined" does not necessarily mean that the behaviour is undefined under all conditions.

If you look back I wrote that "I prefer", so it's a matter of taste and experience what other people do and suggest.