C Function to Convert float to byte array
Asked Answered
G

9

29

I'm trying to make a function that will accept a float variable and convert it into a byte array. I found a snippet of code that works, but would like to reuse it in a function if possible.

I'm also working with the Arduino environment, but I understand that it accepts most C language.

Currently works:

float_variable = 1.11;
byte bytes_array[4];

*((float *)bytes_array) = float_variable;

What can I change here to make this function work?

float float_test = 1.11;
byte bytes[4];

// Calling the function
float2Bytes(&bytes,float_test);

// Function
void float2Bytes(byte* bytes_temp[4],float float_variable){ 
     *(float*)bytes_temp = float_variable;  
}

I'm not so familiar with pointers and such, but I read that (float) is using casting or something?

Any help would be greatly appreciated!

Cheers

*EDIT: SOLVED

Here's my final function that works in Arduino for anyone who finds this. There are more efficient solutions in the answers below, however I think this is okay to understand.

Function: converts input float variable to byte array

void float2Bytes(float val,byte* bytes_array){
  // Create union of shared memory space
  union {
    float float_variable;
    byte temp_array[4];
  } u;
  // Overite bytes of union with float variable
  u.float_variable = val;
  // Assign bytes to input array
  memcpy(bytes_array, u.temp_array, 4);
}

Calling the function

float float_example = 1.11;
byte bytes[4];

float2Bytes(float_example,&bytes[0]);

Thanks for everyone's help, I've learnt so much about pointers and referencing in the past 20 minutes, Cheers Stack Overflow!

Geese answered 25/6, 2014 at 23:30 Comment(1)
Worrying about the execution time of copying 4 bytes, while at the same time using software floating point of AVR doesn't make sense. Focus on removing obvious massive bottlenecks if you have performance problems.Isatin
I
30

Easiest is to make a union:

#include <stdio.h>

int main(void) {
  int ii;
  union {
    float a;
    unsigned char bytes[4];
  } thing;

  thing.a = 1.234;
  for (ii=0; ii<4; ii++) 
    printf ("byte %d is %02x\n", ii, thing.bytes[ii]);
  return 0;
}

Output:

byte 0 is b6
byte 1 is f3
byte 2 is 9d
byte 3 is 3f

Note - there is no guarantee about the byte order… it depends on your machine architecture.

To get your function to work, do this:

void float2Bytes(byte bytes_temp[4],float float_variable){ 
  union {
    float a;
    unsigned char bytes[4];
  } thing;
  thing.a = float_variable;
  memcpy(bytes_temp, thing.bytes, 4);
}

Or to really hack it:

void float2Bytes(byte bytes_temp[4],float float_variable){ 
  memcpy(bytes_temp, (unsigned char*) (&float_variable), 4);
}

Note - in either case I make sure to copy the data to the location given as the input parameter. This is crucial, as local variables will not exist after you return (although you could declare them static, but let's not teach you bad habits. What if the function gets called again…)

Inefficacy answered 25/6, 2014 at 23:34 Comment(11)
@haccks - the union of two data elements of four bytes in size makes these two occupy the same physical memory - so when I write to thing.a I write into the byte array as well. You will find that &thing.a == &thing.bytes ...Inefficacy
Thank you so much! I'm finally understanding the concept of unionsGeese
@haccks - I was a bit surprised at your question, and given the quality of your inputs around here, actually questioned myself ("did I do something really dumb? Should this not have compiled?" I even turned on -Wstrict-aliasing looking for problems…). Thanks for the +.Inefficacy
+1 for the remark about byte order -- this can explode if you use it on the wrong architecture. There's probably a way to do what you want that doesn't involve relying on endianness...Paratuberculosis
@Floris; Hahaha.... Sorry for that. I never hesitate to ask something if I do not understand. At first look I thought, how is this possible? Didn't strike the logic at that time. After looking on 30K+, I compiled your program to test whether it is working or not :). Then looked at 4 bytes of float and 4 bytes of array at same memory location and got the point.Oklahoma
@haccks: I added in a solution that doesn't depend on endianness, it's worth being aware of even if you go with the (arguably more elegant) union.Paratuberculosis
@Inefficacy Is this correct? memcpy(bytes_temp, bytes, 4); Where does bytes come from in the first function? thing.bytes?Lechner
@PatrickCollins; Yes, I have seen that, quite good and voted up already :).Oklahoma
@DavidC.Rankin - you are right, my mistake. It should have been thing.bytes. I have fixed it.Inefficacy
byte* bytes_temp[4], is one level of indirection too much. Should have been: byte bytes_temp[4],)Lempira
No, the correct place. Your first argument is an array of four pointers. Should be: four characters. (and: you dont need the cast, memcpy() takes two void pointers as its first two arguments)Lempira
P
13

Here's a way to do what you want that won't break if you're on a system with a different endianness from the one you're on now:

byte* floatToByteArray(float f) {
    byte* ret = malloc(4 * sizeof(byte));
    unsigned int asInt = *((int*)&f);

    int i;
    for (i = 0; i < 4; i++) {
        ret[i] = (asInt >> 8 * i) & 0xFF;
    }

    return ret;
}

You can see it in action here: http://ideone.com/umY1bB

The issue with the above answers is that they rely on the underlying representation of floats: C makes no guarantee that the most significant byte will be "first" in memory. The standard allows the underlying system to implement floats however it feels like -- so if you test your code on a system with a particular kind of endianness (byte order for numeric types in memory), it will stop working depending on the kind of processor you're running it on.

That's a really nasty, hard-to-fix bug and you should avoid it if at all possible.

Paratuberculosis answered 26/6, 2014 at 0:25 Comment(9)
Thanks for your contribution, it does concern me that other system could misinterpret a float variable. How does your sample code determine between systems though? Forgive my ignorance, I'm learning still...Geese
@TylerDurden It works across systems because it doesn't "break" the notion of a float that the system is working with. Using a union interacts directly with the values in memory, so it depends on whether the system stores the most significant byte in the highest-order byte of the float. Using this strategy only goes through other abstractions -- int and bitshifting -- so it will be consistent no matter how the underlying system orders its bytes.Paratuberculosis
The most significant bit of a float and an int will be in the same place regardless of endianness -- so bitshifting and masking will pull it out correctly. However, the system makes no guarantee that the most significant bit of a float is the highest-order bit, so that can break if you use a union on a system of different endianness than the one you developed on.Paratuberculosis
It doesn't have to do with "misinterpreting a float," rather, some systems represent numbers as [MOST_SIGNIFICANT_BYTE, 2ND_MOST_SIGNIFICANT, 2ND_LEAST_SIGNIFICANT, LEAST_SIGNIFICANT] and others represent them as [LEAST_SIGNIFICANT, 2ND_LEAST_SIGNIFICANT, 2ND_MOST_SIGNIFICANT, MOST_SIGNIFICANT].Paratuberculosis
Ahh I think I see, so it compares the first significant bits using bit masking, to a another int or float to make sure the bytes are in the correct order. Is this kind of right?Geese
& 0xFF grabs the least significant byte, not the first one in memory, so it avoids depending on the byte order of the underlying system.Paratuberculosis
Exactly! This is the advantage of bit-shift operations in certain circumstances. Regardless of endianness, for bit-shift operations on multi-byte numbers, the machine still arranges the LSB as bits 0-7 and MSB as bits 24-31 (or as far left in the 32-bits as the MSB extends)Lechner
In general I don't like functions that malloc - they become terrific sources of memory leaks. But +1 for a method that works regardless of endianness.Inefficacy
I down-voted because teaching people to use malloc on an 8 bit AVR is a very bad idea.Isatin
E
4

I would recommend trying a "union".

Look at this post:

http://forum.arduino.cc/index.php?topic=158911.0

typedef union I2C_Packet_t{
 sensorData_t sensor;
 byte I2CPacket[sizeof(sensorData_t)];
};

In your case, something like:

union {
  float float_variable;
  char bytes_array[4];
} my_union;

my_union.float_variable = 1.11;
Ean answered 25/6, 2014 at 23:35 Comment(0)
O
3

Yet another way, without unions: (Assuming byte = unsigned char)

void floatToByte(byte* bytes, float f){

  int length = sizeof(float);

  for(int i = 0; i < length; i++){
    bytes[i] = ((byte*)&f)[i];
  }

}
Oversight answered 26/1, 2016 at 22:37 Comment(5)
Do you have a source or example for your claim?Oversight
Am I misreading this? According to the link: An object shall have its stored value accessed only by an lvalue expression that has one of the following types which includes a character type.Oversight
Thanks. Was curious, as I found this little hack to be useful in several situations, but hadn't had anything blow up.Oversight
Well, good to know anyway, since I'm probably a bit too liberal with my typecasting :-)Oversight
Likewise - I learned something today. I've deleted my above comments now since they are just noise and don't contribute anything. Have an up-vote...Insupportable
V
3

this seems to work also

#include <stddef.h>
#include <stdint.h>
#include <string.h>

float fval = 1.11;
size_t siz;
siz = sizeof(float);

uint8_t ures[siz];

memcpy (&ures, &fval, siz);

then

float utof;
memcpy (&utof, &ures, siz);

also for double

double dval = 1.11;
siz = sizeof(double);

uint8_t ures[siz];

memcpy (&ures, &dval, siz);

then

double utod;
memcpy (&utod, &ures, siz);
Vagina answered 5/6, 2017 at 13:0 Comment(0)
P
1

Although the other answers show how to accomplish this using a union, you can use this to implement the function you want like this:

byte[] float2Bytes(float val)
{
    my_union *u = malloc(sizeof(my_union));
    u->float_variable = val;
    return u->bytes_array;
}

or

void float2Bytes(byte* bytes_array, float val)
{
    my_union u;
    u.float_variable = val;
    memcpy(bytes_array, u.bytes_array, 4);
}
Plutonium answered 25/6, 2014 at 23:47 Comment(3)
You are returning pointer to local variable. Once the function block ends, u will not exist. You can dynamically allocate union to its pointer: my_union *u = malloc(sizeof(my_union));.Oklahoma
It's a bit more usual to write u->float_variable rather than (*u).float_variable, isn't it? Any particular reason why you chose that way? Note also that in the question, the function signature passed the pointer to the result in as a parameter.Inefficacy
Working from phone. Was too lazy to find the > character. Fixed now.Plutonium
H
0
**conversion without memory reference** \
#define FLOAT_U32(x) ((const union {float f; uint32_t u;}) {.f = (x)}.u) // float->u32
#define U32_FLOAT(x) ((const union {float f; uint32_t u;}) {.u = (x)}.f) // u32->float

**usage example:**
float_t sensorVal = U32_FLOAT(eeprom_read_dword(&sensor));
Handbag answered 9/6, 2022 at 5:54 Comment(1)
Or if you want to make it type safe: {.f = _Generic((x),float:(x))}Isatin
I
0

First of all, some embedded systems 101:

Anyone telling you to use malloc/new on Arduino have no clue what they are talking about. I wrote a fairly detailed explanation regarding why here: Why should I not use dynamic memory allocation in embedded systems?

You should avoid float on 8 bit microcontrollers since it leads to incredibly inefficient code. They do not have a FPU, so the compiler will be forced to load a very resource-heavy software floating point library to make your code work. General advise here.


Regarding pointer conversions:

C allows all manner of wild and crazy pointer casts. However, there are lots of situations where it can lead to undefined behavior if you cast a character byte array's address into a float* and then de-reference it.

  • If the address of the byte array is not aligned, it will lead to undefined behavior on systems that require aligned access. (AVR doesn't care about alignment though.)
  • If the byte array does not contain a valid binary representation of a float number, it could become a trap representation. Similarly you must keep endianess in mind. AVR is an 8-bitter but it's regarded as little endian since it uses little endian format for 16 bit addresses.
  • It leads to undefined behavior because it goes against the C language "effective type" system, also known as a "strict pointer aliasing violation". What is the strict aliasing rule?

Going the other way around is fine though - taking the address of a float variable and converting it to a character pointer, then de-reference that character pointer to access individual bytes. Multiple special rules in C allows this for serialization purposes and hardware-related programming.


Viable solutions:

  • memcpy always works fine and then you won't have to care about alignment and strict aliasing. You still have to care about creating a valid floating point representation though.
  • union "type punning" as demonstrated in other answers. Note that such type punning will assume a certain endianess.
  • Bit shifting individual bytes and concatenating with | or masking with & as needed. The advantage of this is that it's endianess-independent in some scenarios.
Isatin answered 9/6, 2022 at 6:58 Comment(0)
M
0
float f=3.14;
char *c=(char *)&f;

float g=0;
char *d=(char *)&g;

for(int i=0;i<4;i++) d[i]=c[i];

/* Now g=3.14 */

Cast your float as char, and assign the address to the char pointer. Now, c[0] through c[3] contain your float.

http://justinparrtech.com/JustinParr-Tech/c-access-other-data-types-as-byte-array/

Measurable answered 19/6, 2022 at 19:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.