【问题标题】:How to change byte order of a float in C如何在C中更改浮点数的字节顺序
【发布时间】:2021-01-15 13:24:59
【问题描述】:

我有一个必须实施到第二部分的系统。问题是我收到了一个混合字节的浮点数。在下面的示例中,输入是 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;
}

【问题讨论】:

标签: c encoding floating-point


【解决方案1】:

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

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

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,但如果其他人应该阅读它,请不要使用类似的东西。

【讨论】:

  • 我发现你的版本已经更干净了!谢谢。
  • @Jona TBH,我更喜欢你的版本;)
  • 老实说,我不能 100% 确定这不会违反 C 中一些非常奇怪的规则。 return *(float*) &amp;tmp2; 是严格别名违规,以及潜在的对齐违规。您的第一个代码示例可能很快,非常容易阅读,并且严格符合 IMO。
【解决方案2】:

无需额外变量即可:

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;

【讨论】:

  • 嗯,你回答了这个问题,但是啊!
【解决方案3】:

如果您使用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

【讨论】:

    【解决方案4】:

    你可以通过联合来做到这一点:

    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 是为了清楚起见,可以删除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-28
      • 2011-02-26
      • 1970-01-01
      • 2013-08-18
      相关资源
      最近更新 更多