如何更改C中浮点数的字节顺序

时间:2021-01-15 13:24:59

标签: c encoding floating-point

我有一个系统,我必须在其中实施到第二部分。问题是我收到了一个混合字节的浮点数。在下面的示例中,输入是 1E9 (f_orig) 而接收的是这个 2.034699e+26 (f_recv)。我做了函数 ABCD_to_CDAB_float 但我觉得它很难看。有没有更好的方法来写ABCD_to_CDAB_float(没有两个临时变量已经很好了)?

#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;
}

4 个答案:

答案 0 :(得分:3)

首先,请注意删除变量并不意味着它会使用更少的内存。编译器很可能会删除不必要的变量,但如果需要,它也可能会添加变量。

如果你绝对想摆脱它们,你可以这样做:

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;
}

但老实说,我不明白这一点。另外,我建议不要这样做,因为很容易出错并导致很难追踪的错误。我想你也可以做这样的事情:(不,你不能。见下面的编辑。)

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
}

但又一次。如果您真的不需要它们,请避免使用这些魔术。在某些情况下,它可能会稍微影响性能,但不会使代码更易于阅读。

老实说,我不能 100% 确定这不会违反 C 中一些非常奇怪的规则。这可能会导致未定义的行为。所以不要完全相信我。在处理这样的指针转换时,很容易违反 strict aliasing rule

编辑:

第二个例子确实违反了严格的别名规则,这证明了我的观点,他可能很棘手。感谢 Andrew Henle 指出。

如果你想绝对没有额外的变量,甚至没有指针,看看 Eric Postpischil's answer 但不要使用类似的东西,如果其他人应该阅读它。

答案 1 :(得分:1)

在没有额外变量的情况下做到这一点:

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;
}

删除一些分散注意力的括号:

    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;

答案 2 :(得分:1)

如果您使用 gcc 系列编译器,我会:

#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;
}

它会生成最高效的代码:

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

答案 3 :(得分:0)

您可以使用联合来做到这一点:

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;
}

为了清楚起见,两个变量 cd 和 ab 可以删除。