2

I have a system where I have to implement to second part. The issue is that I received a float where the bytes are mixed. In the example below, the input is 1E9 (f_orig) and what is receive is this 2.034699e+26 (f_recv). I did the function ABCD_to_CDAB_float but I find it ugly. Is there a better way to to write ABCD_to_CDAB_float(without two temporary variables will already be nice) ?

#include <stdio.h>
#include <float.h>
#include <string.h>
#include <stdint.h>

/* Change float byte order */
float ABCD_to_CDAB_float(float infloat) {
    float outfloat;
    uint8_t tmp[4];
    memcpy(&tmp, &infloat, 4);
    uint8_t tmp2[] = {tmp[2], tmp[3], tmp[0], tmp[1]};
    memcpy(&outfloat, &tmp2, 4);
    return outfloat;
}

int main() {
    float f_orig = 1E9; // This is the value sent
    printf("f_orig\t%e\n", f_orig);

    char str_orig[4];
    memcpy(&str_orig, &f_orig, 4);
    printf("str_orig\t%x %x %x %x\n", str_orig[0], str_orig[1], str_orig[2], str_orig[3]);
    
    float f_built; // same as f_orig
    char str_built[] = {0x28, 0x6B, 0x6E, 0x4E};
    memcpy(&f_built, &str_built, 4);
    printf("f_built\t%e\n", f_built); // it prints "1E9"

    float f_recv; // received float
    char str_recv[] = {0x6E, 0x4E, 0x28, 0x6B};
    memcpy(&f_recv, &str_recv, 4);
    printf("f_recv\t%e\n", f_recv); // converesion was wrong somewhere

    
    char str6[] = {str_recv[2], str_recv[3], str_recv[0], str_recv[1]};
    float f_res;
    memcpy(&f_res, &str6, 4);
    printf("f_res\t%e\n", f_res); // result is fine
    
    printf("%e\n",ABCD_to_CDAB_float(f_recv)); // result is right

    return 0;
}
Jona
  • 1,218
  • 1
  • 10
  • 20
  • 1
    What is the goal to not use variable, you can do some stuff with `XOR` but we need to know what you are looking for exactly. – Ôrel Jan 15 '21 at 13:35
  • You can avoid the `memcpy` by casting the address of the `float` to `char *`. [Converting float values from big endian to little endian](https://stackoverflow.com/a/2782742) – 001 Jan 15 '21 at 13:36

4 Answers4

3

Firstly, please note that removing variables does not automatically imply that it will use less memory. It's quite likely that the compiler will remove unnecessary variables, but it might also add variables if it wants to.

If you absolutely want to get rid of them, you can do this:

float ABCD_to_CDAB_float(float infloat) {
    float outfloat;
    char *tmp1 = (char*) &infloat;
    char *tmp2 = (char*) &outfloat;
    tmp2[0] = tmp1[2];
    tmp2[1] = tmp1[3];
    tmp2[2] = tmp1[0];
    tmp2[3] = tmp1[1];
    return outfloat;
}

But to be honest, I don't see the point. Also, I'd advice against things like this, because it's very easy to get it wrong and cause very hard traced bugs. I guess you also could do something like this: (No, you cannot. See edit below.)

float ABCD_to_CDAB_float(float infloat) {
    char *tmp1 = (char*) &infloat;
    char tmp2[] = {tmp[2], tmp[3], tmp[0], tmp[1]};
    return *(float*) &tmp2; // This is not ok! It's violating the
                            // strict aliasing rule
}

But again. Avoid these magic tricks if you don't really need them. In some cases, it might impact performance a little bit, but it does not make the code easier to read.

And to be perfectly honest, I'm not 100% sure that this does not violate some of those really strange rules in C. It is a possibility that this leads to undefined behavior. So don't trust me completely on this one. When dealing with pointer casting like this, it's easy to violate the strict aliasing rule

EDIT:

The second example DOES violate the strict aliasing rule, which proves my point that his might be tricky. Thanks to Andrew Henle for pointing it out.

If you want to have absolutely no extra variables, not even pointers, take a look at Eric Postpischil's answer but don't use stuff like that if anyone else is supposed to read it.

klutt
  • 30,332
  • 17
  • 55
  • 95
  • I find your version already cleaner ! Thank you. – Jona Jan 15 '21 at 14:00
  • @Jona TBH, I prefer your version ;) – klutt Jan 15 '21 at 14:14
  • 2
    *And to be perfectly honest, I'm not 100% sure that this does not violate some of those really strange rules in C.* `return *(float*) &tmp2;` is a strict-aliasing violation, and also a potential alignment violation. Your first code example is probably every bit as fast, is really easy to read, and IMO strictly-conforming. – Andrew Henle Jan 15 '21 at 14:54
1

To do it with no extra variables:

float ABCD_to_CDAB_float(float x)
{
    return (union { uint8_t u[4]; float f; }) {{
        ((uint8_t *) &x)[2], ((uint8_t *) &x)[3],
        ((uint8_t *) &x)[0], ((uint8_t *) &x)[1] }} .f;
}

To remove some distracting parentheses:

    return (union { uint8_t u[4]; float f; }) {{
        2[(uint8_t *) &x], 3[(uint8_t *) &x],
        0[(uint8_t *) &x], 1[(uint8_t *) &x] }} .f;
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

If you use gcc family compiler I would:

#define SWAP(a,b,type) do{type c = a; (a) = b; (b) = c;} while(0)

float ABCD_to_CDAB_float(float x)
{
    union
    {
        float f;
        uint16_t u16[2];
    }z = {.f = x};

    z.u16[0] = __builtin_bswap16(z.u16[0]);
    z.u16[1] = __builtin_bswap16(z.u16[1]);

    SWAP(z.u16[0], z.u16[1], uint16_t);

    return z.f;
}

float ABCD_to_DCBA_float(float x)
{
    uint32_t u32;

    memcpy(&u32,&x, 4);
    u32 = __builtin_bswap32(u32);
    memcpy(&x, &u32, 4);
    return x;
}

and it will generate the most efficient code:

ABCD_to_CDAB_float:
        movd    eax, xmm0
        mov     ecx, eax
        shr     eax, 16
        mov     edx, eax
        rol     cx, 8
        rol     dx, 8
        sal     ecx, 16
        movzx   eax, dx
        or      eax, ecx
        movd    xmm0, eax
        ret
ABCD_to_DCBA_float:
        movd    eax, xmm0
        bswap   eax
        movd    xmm0, eax
        ret
0___________
  • 60,014
  • 4
  • 34
  • 74
0

You can do this with unions:

union f2l {
    float f;
    uint32_t l;
};

float ABCD_to_CDAB(float input) {
    f2l t1;
    t1.f = input;
    uint16_t cd = t1.l >> 16;
    uint16_t ab = t1.l & 0x0000FFFF;
    t1.l = (ab << 16) | cd; 
    return t1.f;
}

the 2 variables cd and ab are for sake of clarity and can be removed.

Dominique Lorre
  • 1,168
  • 1
  • 10
  • 19