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