【问题标题】:How to get float in bytes?如何以字节为单位浮点数?
【发布时间】:2014-01-27 03:43:37
【问题描述】:

我正在使用 HIDAPI 将一些数据发送到 USB 设备。此数据只能作为 byte 数组发送,我需要在此数据数组中发送一些 float 数字。我知道花车有4 字节。所以我认为这可能有效:

float f = 0.6;
char data[4];

data[0] = (int) f >> 24;
data[1] = (int) f >> 16;
data[2] = (int) f >> 8;
data[3] = (int) f;

后来我要做的就是:

g = (float)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]) );

但是测试这表明我像data[0] = (int) f &gt;&gt; 24; 这样的行总是返回0。我的代码有什么问题以及如何正确执行此操作(即,在 4 char 字节中破坏 float 内部数据并稍后重建相同的 float)?


编辑

我能够使用以下代码完成此操作:

float f = 0.1;
unsigned char *pc;
pc = (unsigned char*)&f;

// 0.6 in float
pc[0] = 0x9A;
pc[1] = 0x99;
pc[2] = 0x19;
pc[3] = 0x3F;

std::cout << f << std::endl; // will print 0.6

*(unsigned int*)&f = (0x3F << 24) | (0x19 << 16) | (0x99 << 8) | (0x9A << 0);

我知道memcpy() 是一种“更干净”的方式,但我认为这种方式的性能要好一些。

【问题讨论】:

  • (int)f &gt;&gt; 24 返回0 的原因是int 转换为f 首先等于0:转换将浮动发送到它的地板。这是未定义的行为,但要以这种骇人听闻的方式进行操作,您需要像 *(int*)&amp;f &gt;&gt; 24 这样的东西。

标签: c floating-point


【解决方案1】:

在标准 C 中,保证任何类型都可以作为字节数组访问。 当然,直接的方法是使用联合:

 #include <stdio.h> 

 int main(void)
 {
    float x = 0x1.0p-3; /* 2^(-3) in hexa */

    union float_bytes {
       float val;
       unsigned char bytes[sizeof(float)];
    } data;

    data.val = x;
    for (int i = 0; i < sizeof(float); i++) 
          printf("Byte %d: %.2x\n", i, data.bytes[i]);

    data.val *= 2;   /* Doing something with the float value */
    x = data.val;    /* Retrieving the float value           */
    printf("%.4f\n", data.val);

    getchar();
 }

如您所见,完全没有必要使用 memcpy 或指针...

union 方法易于理解、标准且快速。

编辑。

我将解释为什么这种方法在 C (C99) 中有效。

  • [5.2.4.2.1(1)] 一个字节有CHAR_BIT 位(整数常量>= 8,在几乎情况下是8)。
  • [6.2.6.1(3)] unsigned char 类型使用它的所有位来表示对象的值,它是一个非负整数,以纯二进制表示。这意味着没有填充位或用于任何其他额外目的的位。 (signed charchar 类型不保证同样的事情)。
  • [6.2.6.1(2)] 每个非位域类型在内存中都表示为连续的字节序列。
  • [6.2.6.1(4)](引用)“存储在任何其他对象类型的非位字段对象中的值由 n × CHAR_BIT 位组成,其中 n 是该对象的大小类型,以字节为单位。该值可以复制到 unsigned char [n] 类型的对象中(例如,通过 memcpy);[...]"
  • [6.7.2.1(14)] 一个指向结构对象(特别是联合)的指针,经过适当的转换,指向它的初始成员。 (因此,联合的开头没有填充字节)。
  • [6.5(7)]可以通过字符类型访问对象的内容:

一个对象的存储值只能由具有以下之一的左值表达式访问 以下类型:
— 与对象的有效类型兼容的类型,
— 与对象的有效类型兼容的类型的限定版本,
— 对应于有效类型的有符号或无符号类型 对象,
— 一种类型,它是有符号或无符号类型,对应于 对象的有效类型,
— 聚合或联合类型,其中包括上述类型之一 成员(递归地包括子聚合或包含联合的成员),或
一种字符类型

更多信息:

A discussion in google groups
Type-punning

编辑 2

标准 C99 的另一个细节:

  • [6.5.2.3(3) 脚注 82] 类型双关是允许的:

如果用于访问联合对象内容的成员与上次用于访问的成员不同 在对象中存储一个值,该值的对象表示的适当部分被重新解释 作为 6.2.6 中描述的新类型中的对象表示(有时称为“类型 punning")。这可能是一个陷阱表示。

【讨论】:

  • 我想指出,虽然这在 C 中可能有效(我认为是 C99,但不是 C89?),但这在 C++ 中是未定义的行为。以防万一任何 C++ 用户路过看到这个。
  • @KerrekSB :是的,我的代码在 C99 中是标准的。我不太确定联合技术是否在 C89 中是标准的。 (for 循环内的int 仅在 C99 中有效)。但是,问题有标签c,但没有c++
  • 是的,当然,这只是一个备注。有时人们认为他们可以将一种语言的东西应用到另一种语言上,所以我只是想保证安全。 C 在访问内存方面比 C++ 轻松得多;我没有标准参考表明这是有效的,但我相信你是对的。
  • @KerrekSB 我在this answer 中通过工会矿场走进了类型双关语,并在此链接了一些更好的讨论。 Pascal Cuoq 的解释和他链接的 DR 支持自 C89 以来它一直是合法的。 C++ 案例根本不清楚,我倾向于认为它是未定义的,但它可能不是。
  • @KerrekSB 我指的是Purpose of Unions in C and C++,我也链接了这个Accessing inactive union member - undefined?
【解决方案2】:

你可以这样做:

char data[sizeof(float)];


float f = 0.6f;

memcpy(data, &f, sizeof f);    // send data


float g;

memcpy(&g, data, sizeof g);    // receive data

为了使其工作,两台机器需要使用相同的浮点表示。


正如 cmets 中正确指出的那样,您不一定需要做额外的memcpy;相反,您可以将f 直接 视为一个字符数组(任何符号)。不过,您仍然需要在接收端执行memcpy,因为您可能将任意字符数组视为浮点数!示例:

unsigned char const * const p = (unsigned char const *)&f;
for (size_t i = 0; i != sizeof f; ++i)
{
    printf("Byte %zu is %02X\n", i, p[i]);
    send_over_network(p[i]);
}

【讨论】:

  • 愚蠢的问题,为什么 0.6 之后的 'f'?以前见过,只是没看到原因……
  • 我喜欢这个答案,但我很好奇,没有其他方法可以对浮点数进行字节级访问吗?
  • @mFeinstein 0.6ffloat 类型的常量,0.6double 类型的常量。无论如何,double 类型的常量都会自动转换为float(但天真的编译器可能会为f = 0.6 生成更差的代码,并且某些平台可能会以不同的方式舍入)。还有其他进行字节级访问的方法,但memcpy 是这里最好的方法。
  • @Gilles:是的,您可以将f 直接视为字符数组:char const * data = (char const *)&amp;f;,现在将data[i] 用于i 的范围。
  • 有些地方无法区分 float、double 和 int 常量可能会给您带来麻烦,因此养成始终指定 f 或 @ 的习惯并不是一个坏主意987654339@ 后缀。话虽如此,我必须承认,只有当我需要确保它是正确的类型或需要确保读者理解它是什么类型时,我才会这样做。
【解决方案3】:

C 语言保证任何类型的任何值¹都可以作为字节数组访问。字节类型为unsigned char。这是将浮点数复制到字节数组的低级方法。 sizeof(f) 是用于存储变量f 的值的字节数;你也可以使用sizeof(float)(你可以传递sizeof一个变量或更复杂的表达式,或者它的类型)。

float f = 0.6;
unsigned char data[sizeof(float)];
size_t i;
for (i = 0; i < sizeof(float); i++) {
    data[i] = (unsigned char*)f + i;
}

函数memcpymemmove 正是这样做的(或其优化版本)。

float f = 0.6;
unsigned char data[sizeof(float)];
memcpy(data, f, sizeof(f));

不过,您甚至不需要制作此副本。您可以直接将指向浮点数的指针传递给您的写入 USB 函数,并告诉它要复制多少字节 (sizeof(f))。如果函数采用 void* 以外的指针参数,则需要显式转换。

int write_to_usb(unsigned char *ptr, size_t size);
result = write_to_usb((unsigned char*)f, sizeof(f))

请注意,这只有在设备使用相同的浮点数表示时才有效,这很常见但不是通用的。大多数机器使用IEEE floating point formats,但您可能需要切换字节顺序。


至于您的尝试有什么问题:&gt;&gt; 运算符对整数进行操作。在表达式(int) f &gt;&gt; 24 中,f 被强制转换为int;如果您在没有演员表的情况下编写了f &gt;&gt; 24f 仍将自动转换为int。将浮点值转换为整数可以通过截断或舍入来逼近它(通常接近 0,但规则取决于平台)。 0.6四舍五入为0或1,所以data[0]为0或1,其余均为0。

你需要作用于浮点对象的字节,而不是它的值。

¹ 不包括在 C 中不能真正操作的函数,但包括函数自动衰减到的函数指针。

【讨论】:

  • 没有for 是否可以在一行中完成?使用&lt;&lt;| 分割字节?
  • @mFeinstein &lt;&lt; 和其他整数运算不会帮助你。查看我的编辑。
  • 是的,我明白了,但我认为将浮点数转换为无符号字符数组可能会奏效。我只是想避免不必要的代码,因为这将在中断中运行。但如果它开始变得复杂,那么 memcpy 将几乎相同,我会使用它。
  • @mFeinstein 您不能强制转换为数组类型,但将指向浮点数的指针强制转换为unsigned char * 本质上是相同的。
【解决方案4】:

如果您控制双方,最安全的方法是发送某种标准化表示...这不是最有效的,但对于小数字来说也不错。

hostPort writes char * "34.56\0" byte by byte
client reads char * "34.56\0" 

然后使用库函数atofatof_l 转换为浮点数。

当然这不是最优化的,但它肯定很容易调试。

如果你想获得更多的优化和创意,第一个字节是长度然后是指数,那么每个字节代表 2 个小数位......所以

34.56 变成 char array[] = {4,-2,34,56}; 类似的东西是可移植的...我只是尽量不要传递二进制浮点表示...因为它会很快变得混乱。

【讨论】:

  • 这对于我的需求来说会很麻烦,因为我有一个微控制器来接收数据并且性能不是最好的
【解决方案5】:

联合 float 和 char 数组可能更安全。放入 float 成员,取出 4 个(或任何长度)字节。

【讨论】:

  • 实际上,不,以这种方式滥用工会是不安全的。如果您写信给工会的成员,则不允许您从另一个成员那里读回(这是未定义的行为)。编译器利用此限制进行优化。
  • 公牛。这就是 union... 以一种格式放入 in 数据并将 out 未更改的位作为另一种格式提取的全部目的。跨度>
  • 不,这不是工会的目的。联合的目的是使用相同的内存片将不同的数据存储为不同的时间。在 C89 中,行为是实现定义的。在 C++ 中,GCC 确实利用这一点进行优化;我认为某些版本也在 C 中执行此操作,但在查找之后我错了:这对于 GCC 是安全的。 C11 has also changed to make this defined,因此在当前编译器上实践中可能是安全的,即使它们不完全符合 C11。
  • 再一次,不,你完全倒退了。在标准的早期版本中,联合中的类型双关语显然不是标准的(尽管得到了广泛的支持)。联合的主要目的是将不同的、不相关的对象存储在内存的同一空间中(通常但不一定,附近有一个枚举或整数对象指示联合的哪个字段当前有效)。
  • @Gilles 您链接到的答案stackoverflow.com/questions/11373203/… 中的脚注已经存在于C99TC3 中。如果您相信标准委员会无误,这意味着它一直在 C99 标准中,尽管没有明确表达。在这种解释下,C89、C99 和 C11 都没有定义使用联合进行类型双关。
【解决方案6】:

假设两个设备对如何表示浮点数有相同的概念,那么为什么不直接使用memcpy。即

unsigned char payload[4];
memcpy(payload, &f, 4);

【讨论】:

  • 因为字节将在微控制器中读回,我不确定微控制器库中是否有 memcpy....但现在我看到了
猜你喜欢
  • 2020-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-24
相关资源
最近更新 更多