28

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!

Ben Winding
  • 10,208
  • 4
  • 80
  • 67
  • 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. – Lundin Jun 09 '22 at 07:00

9 Answers9

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…)

Floris
  • 45,857
  • 6
  • 70
  • 122
  • 2
    @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` ... – Floris Jun 25 '14 at 23:50
  • 2
    Thank you so much! I'm finally understanding the concept of unions – Ben Winding Jun 25 '14 at 23:58
  • 1
    @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 +. – Floris Jun 25 '14 at 23:59
  • +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... – Patrick Collins Jun 26 '14 at 00:10
  • @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. – haccks Jun 26 '14 at 00:34
  • @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. – Patrick Collins Jun 26 '14 at 00:41
  • @Floris Is this correct? `memcpy(bytes_temp, bytes, 4);` Where does bytes come from in the first function? `thing.bytes`? – David C. Rankin Jun 26 '14 at 00:47
  • @PatrickCollins; Yes, I have seen that, quite good and voted up already :). – haccks Jun 26 '14 at 00:50
  • @DavidC.Rankin - you are right, my mistake. It should have been `thing.bytes`. I have fixed it. – Floris Jun 26 '14 at 00:59
  • `byte* bytes_temp[4],` is one level of indirection too much. Should have been: `byte bytes_temp[4],)` – wildplasser Jul 12 '19 at 10:33
  • 1
    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) – wildplasser Jul 12 '19 at 11:07
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.

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • 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... – Ben Winding Jun 26 '14 at 00:55
  • 2
    @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. – Patrick Collins Jun 26 '14 at 01:00
  • 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. – Patrick Collins Jun 26 '14 at 01:01
  • 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]. – Patrick Collins Jun 26 '14 at 01:04
  • 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? – Ben Winding Jun 26 '14 at 01:16
  • 2
    `& 0xFF` grabs the least significant byte, not the first one in memory, so it avoids depending on the byte order of the underlying system. – Patrick Collins Jun 26 '14 at 02:21
  • 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) – David C. Rankin Jun 26 '14 at 05:37
  • 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. – Floris Oct 29 '14 at 21:15
  • I down-voted because teaching people to use malloc on an 8 bit AVR is a very bad idea. – Lundin Jun 09 '22 at 06:11
5

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;
FoggyDay
  • 11,962
  • 4
  • 34
  • 48
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];
  }

}
Christopher Schneider
  • 3,745
  • 2
  • 24
  • 38
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);
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);
}
mclaassen
  • 5,018
  • 4
  • 30
  • 52
  • 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));`. – haccks Jun 25 '14 at 23:54
  • 1
    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. – Floris Jun 26 '14 at 00:03
  • Working from phone. Was too lazy to find the > character. Fixed now. – mclaassen Jun 26 '14 at 00:07
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));
Lundin
  • 195,001
  • 40
  • 254
  • 396
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.
Lundin
  • 195,001
  • 40
  • 254
  • 396
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/

J Parr
  • 1
  • 1