4

I'm trying to re-construct a 32-bit floating point value from an eeprom.

The 4 bytes in eeprom memory (0-4) are : B4 A2 91 4D

and the PC (VS Studio) reconstructs it correctly as 3.054199 * 10^8 (the floating point value I know should be there)

Now I'm moving this eeprom to be read from an 8-bit Arduino, so not sure if it's compiler/platform thing, but when I try reading the 4 bytes into a 32-bit dword, and then typecast it to a float, the value I get isn't even close.

Assuming the conversion can't be done automatically with the standard ansi-c compiler, how can the 4 bytes be manually parsed to be a float?

ben
  • 473
  • 2
  • 9
  • 21
  • 5
    Sounds like an [endianness issue](http://stackoverflow.com/q/22567723/1708801) but without code we can not know for sure. In my answer to that question I link to a convertor so you can try flipping the values and see if you can repeat the results you are seeing to confirm. – Shafik Yaghmour Oct 11 '14 at 01:51
  • I agree it is probably endianness. Do you get about -3.0280572E-7 on the Arduino? – Patricia Shanahan Oct 11 '14 at 05:42
  • Interestingly, when the first byte is the treated as the MSB, the compiler (float) conversion yields: 3.03055283E9.. however I do recall seeing -3.02E-7 originally but not sure why I don't anymore. When the first byte is treated as the LSB, the compiler casts to: 1.30138995E9. – ben Oct 11 '14 at 22:57
  • [Here](https://stackoverflow.com/questions/56786533/how-to-convert-byte-array-to-float-in-php) is how to convert byte array into float in PHP. – Syed Azhar Abbas Jun 27 '19 at 10:11

3 Answers3

10

The safest way, and due to compiler optimization also as fast as any other, is to use memcpy:

uint32_t dword = 0x4D91A2B4;
float f;
memcpy(&f, &dw, 4);

Demo: http://ideone.com/riDfFw

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    Thanks, this worked great! Since my program space is almost full, I'll try to add a custom memcpy implementation so that I avoid including stdlib.h... just so that it doesn't add other unused code – ben Oct 11 '14 at 23:03
  • @ben: I would first just copy the memcpy prototype into your code... most compilers recognize the name `memcpy` and won't actually call a function when the size argument is fixed and small. – Ben Voigt Oct 11 '14 at 23:42
1

As Shafik Yaghmour mentioned in his answer - it's probably an endianness issue, since that's the only logical problem you could encounter with such a low-level operation. While Shafiks answer in the question he linked, basically covers the process of handling such an issue, I'll just leave you some information:

As stated on the Anduino forums, Anduino uses Little Endian. If you're not sure about what will be the endianness of the system you'll end up working on, but want to make your code semi-multiplatform, you can check the endianness at runtime with a simple code snippet:

bool isBigEndian(){
   int number = 1;
   return (*(char*)&number != 1);
}

Be advised that - as all things - this consumes some of your procesor time and makes your program run slower, and while that's nearly always a bad thing, you can still use this to see the results in a debug version of your app.

How this works is that it tests the first byte of the int stored at the address pointed by &number. If the first byte is not 1, it means the bytes are Big Endian.

Also - this only will work if sizeof(int) > sizeof(char).

You can also embed this in your code:

float getFromEeprom(int address){
   char bytes[sizeof(float)];
   if(isBigEndian()){
      for(int i=0;i<sizeof(float);i++)
         bytes[sizeof(float)-i] = EEPROM.read(address+i);
   }
   else{
      for(int i=0;i<sizeof(float);i++)
         bytes[i] = EEPROM.read(address+i);
   }
   float result;
   memcpy(&result, bytes, sizeof(float));
   return result;
}
Community
  • 1
  • 1
Paweł Stawarz
  • 3,952
  • 2
  • 17
  • 26
  • Thanks, definitely endiness made a difference, but the float casting itself didn't give the right value regardless of which way I order the bytes. I selected Ben's answer since it was first, but really like your code! – ben Oct 11 '14 at 23:08
0

You need to cast at the pointer level.

int     myFourBytes = /* something */;
float*  myFloat = (float*) &myFourBytes;
cout << *myFloat;

Should work.

If the data is generated on a different platform that stores values in the opposite endianness, you'll need to manually swap the bytes around. E.g.:

unsigned char myFourBytes[4] = { 0xB4, 0xA2, 0x91, 0x4D };
std::swap(myFourBytes[0], myFourBytes[3]);
std::swap(myFourBytes[1], myFourBytes[2]);
StilesCrisis
  • 15,972
  • 4
  • 39
  • 62
  • Thanks, I love the beauty of this! It seems to use the built in compiler conversion without a separate call.. and it works fine! The only reason I wouldn't go with it is because of the strict aliasing warning.. even though I'm not sure what it means I'll take the word of people much more experienced than me in programming that it could cause unexpected problems! – ben Oct 11 '14 at 22:56
  • 2
    It means the optimizer is technically allowed to do awful things here, as a strict reading of the standard calls this undefined behavior. Practically speaking, there are GIANT swaths of existing production code in major systems which would break if the optimizer started flubbing this. – StilesCrisis Oct 11 '14 at 23:18
  • I think you can get around the aliasing issue by using a union of an int and a float, instead of a cast between the two types. – StilesCrisis Oct 11 '14 at 23:19