Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a structure containing floats into something more network friendly

I am attempting to send a structure containing floating point data over a network, which comprises two different hardware architectures, written in C.

The client is running on x86_64 architecture, and the server is running on PPC (32bit) architecture.

It seems the only options I have for converting data to and from network friendly formats is:

htons/ntohs(2 byte) and htonl/ntohl(4 byte)

There appears to be no version which deals with floating point numbers (which makes sense, as differing architectures/endianness has different representations of floating point numbers).

So, I attempted splitting the floating point number into integer and exponent format in the following way:

void FtoME(float num, int32_t* mantissa, int32_t* exponent)
{
    int32_t sig = (int32_t)num;
    int32_t exp = 0;
    double frac = num-sig;
    double temp = num;
    while (frac > 0 && exp > -20)
    {
        temp = temp * 10; //left shift the whole number
        sig = (int32_t)temp; //get the integer part
        frac = temp - sig; //get the fractional part
        exp--;
    }
    printf("Scientific note: %dx10^%d\n",sig, exp);
    *mantissa = sig;
    *exponent = exp;
}

Now, whilst this does work in theory, in practice, I run into overflows a LOT, so clearly, this is not the correct way to handle this.

Are there other approaches I might try to be able to avoid overflows, and convert the float to a network friendly format (and importantly, back again), whilst not losing any data?

like image 667
Ian Young Avatar asked Mar 03 '23 16:03

Ian Young


2 Answers

The IEEE 754 standard should be enough for most architectures - and for only those that are not compliant, you just need to worry about the conversion to IEEE 754 and back again. For the byteorder stuff, given a 32-bit float, you can use uint32_t tmp; memcpy(&tmp, &f, sizeof(f)); with htonl and ntohl.

like image 111

There appears to be no version which deals with floating point numbers (which makes sense, as differing architectures/endianness has different representations of floating point numbers).

No it does not "make sense": every data type larger than one byte is going to be affected by the endianness of the system. This includes short and long integers.

The htonl and ntohl functions are exactly what you are looking for and can also be used to send floating point numbers, which are 32 bits (NOT doubles though, which are 64 bits... that's still doable, but a little more complicated).

The htonl function converts a 32 bit value from the host endianness to network endianness (which is big endian), while the ntohl function converts a 32 bit value from network endianness to host endianness. All you have to do is convert your float into a uint32_t appropriately when sending and when receiving, and you'll be good to go.

Server:

float f = 10e-9;
uint32_t tmp;

memcpy(&tmp, &f, 4);
uint32_t data = htonl(tmp);

send_to_client(data);

// Or if you are sending as raw bytes:
send_to_client((char*)&data, 4);

Client:

uint32_t data;

data = receive_from_server();

// Or if you are receiving as raw bytes:
char *bytes = receive_from_server(4);
memcpy(&data, bytes, 4);

float f = (float)ntohl(data);

As an example of dealing with a struct, assume you want to send this struct over a network:

struct data {
    char name[10];
    uint32_t something1;
    uint16_t something2;
    float value1;
    float value2;
};

Then you could do the following.

Server:

struct data x;
// Do something to initialize the fields of x.

char buffer[24]; // 10 + 4 + 2 + 4 + 4
uint32_t tmpl;
uint16_t tmps;

memcpy(buffer, x.name, 10);

tmpl = htonl(x.something1);
memcpy(buffer + 10, &tmpl, 4);

tmps = htons(x.something2);
memcpy(buffer + 14, &tmps, 2);

memcpy(&tmpl, &x.value1, 4)
tmpl = htonl(tmpl);
memcpy(buffer + 16, &tmpl, 4);

memcpy(&tmpl, &x.value, 4)
tmpl = htonl(tmpl);
memcpy(buffer + 20, &tmpl, 4);

send_to_client(buffer, 24);

Client:

char *buffer = receive_from_server(24);

struct data x;
uint32_t tmpl;
uint16_t tmps;

memcpy(x.name, buffer, 10);

memcpy(&tmpl, buffer + 10, 4);
x.something1 = ntohl(tmpl);

memcpy(&tmps, buffer + 14, 2);
x.something2 = ntohl(tmps);

memcpy(&tmpl, buffer + 16, 4);
tmpl = ntohl(tmpl);
memcpy(&x.value1, &tmpl, 4);

memcpy(&tmpl, buffer + 20, 4);
tmpl = ntohl(tmpl);
memcpy(&x.value2, &tmpl, 4);
like image 24
Marco Bonelli Avatar answered Apr 25 '23 04:04

Marco Bonelli