将 32 位数字转换为四个 8 位数字
Converting 32 bit number to four 8bit numbers
我正在尝试将来自设备的输入(始终为 1 到 600000 之间的整数)转换为四个 8 位整数。
例如,
如果输入是32700,我要188 127 00 00
。
我通过使用实现了这一点:
32700 % 256
32700 / 256
以上方法一直有效到 32700。从 32800 开始,我开始得到不正确的转换。
我对此完全陌生,希望得到一些帮助以了解如何正确完成此操作。
主要编辑以下说明:
鉴于有人已经提到了 shift-and-mask 方法(这无疑是正确的方法),我将提供另一种方法,为了迂腐,它不可移植,依赖于机器,并且可能表现出未定义的行为。尽管如此,这仍然是一个很好的学习练习,IMO。
出于各种原因,您的计算机将整数表示为一组 8 位值(称为 字节);请注意,虽然非常常见,但这并非总是情况(参见CHAR_BIT
)。因此,使用超过 8 位表示的值使用多个字节(因此使用多个位的值是 8 的倍数)。对于 32 位值,您使用 4 个字节,并且在内存中,这些字节总是一个接一个。
我们称一个指针一个包含另一个值在内存中的地址的值。在这种情况下,byte 被定义为指针可以引用的最小值(就位计数而言)。例如,您的 32 位值,包含 4 个字节,将有 4 个“可寻址”单元(每个字节一个),其地址定义为这些地址中的第一个:
|==================|
| MEMORY | ADDRESS |
|========|=========|
| ... | x-1 | <== Pointer to byte before
|--------|---------|
| BYTE 0 | x | <== Pointer to first byte (also pointer to 32-bit value)
|--------|---------|
| BYTE 1 | x+1 | <== Pointer to second byte
|--------|---------|
| BYTE 2 | x+2 | <== Pointer to third byte
|--------|---------|
| BYTE 3 | x+3 | <== Pointer to fourth byte
|--------|---------|
| ... | x+4 | <== Pointer to byte after
|===================
所以你想做的事情(将 32 位字拆分为 8 位字)已经由你的计算机完成了,因为它是由它的处理器 and/or 内存架构强加给它的。为了获得这种几乎巧合的好处,我们将找到您的 32 位值的存储位置并读取其内存 byte-by-byte(而不是一次读取 32 位).
由于所有严肃的 SO 答案似乎都这样做,所以让我引用标准(ISO/IEC 9899:2018,6.2.5-20)来定义我需要的最后一件事(强调我的):
Any number of derived types can be constructed from the object and function types, as follows:
- An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. [...] Array types are characterized by their element type and by the number of elements in the array. [...]
- [...]
因此,由于数组中的元素被定义为连续的,内存中的 32 位值在具有 8 位字节的机器上实际上仅是其机器表示中的 4 个数组字节!
给定一个 32 位有符号值:
int32_t value;
其地址由&value
给出。同时,一个4个8位字节的数组可以表示为:
uint8_t arr[4];
请注意,我使用了无符号变体,因为这些字节本身并不真正代表数字,因此将它们解释为“有符号”是没有意义的。现在,指向数组 4-uint8_t
的指针定义为:
uint8_t (*ptr)[4];
如果我将 32 位值的地址分配给这样的数组,我将能够单独索引每个字节,这意味着我将直接读取字节,避免任何讨厌的移位和-屏蔽操作!
uint8_t (*bytes)[4] = (void *) &value;
我需要转换指针 ("(void *)
") 因为 我无法忍受抱怨的编译器 &value
的类型是 "pointer -to-int32_t
”,而我将它分配给“指向数组的指针 4-uint8_t
”,这种类型不匹配被编译器捕获并被标准;这是我们正在做的事情并不理想的第一个警告!
最后,我们可以通过索引直接从内存读取每个字节来单独访问每个字节:(*bytes)[n]
读取[=的第n
个字节27=]!
把它们放在一起,给定一个 send_can(uint8_t)
函数:
for (size_t i = 0; i < sizeof(*bytes); i++)
send_can((*bytes)[i]);
并且,为了测试目的,我们定义:
void send_can(uint8_t b)
{
printf("%hhu\n", b);
}
当 value
为 32700
:
时,在我的机器上打印
188
127
0
0
最后,这说明了此方法依赖于平台的另一个原因:32 位字的字节存储顺序并不总是期望从二进制表示的理论讨论 i.e:
- 字节 0 包含位 31-24
- 字节 1 包含位 23-16
- 字节 2 包含位 15-8
- 字节 3 包含位 7-0
实际上,据我所知,C 语言允许 任何 24 种可能性来对这 4 个字节进行排序(这称为 endianness)。同时,移动和屏蔽总是会让你得到第 n
个“逻辑”字节。
你可以做一些位掩码。
600000 是 0x927C0
600000 / (256 * 256)
得到 9,还没有屏蔽。
((600000 / 256) & (255 * 256)) >> 8
得到 0x27 == 39。使用 8 个设置位的 8 位移位掩码 (256 * 255)
和右移 8 位,>> 8
,这也可以作为另一个/ 256
.
600000 % 256
为您提供 0xC0 == 192。屏蔽将是 600000 & 255
.
您可以在 16 位有符号整数中存储的最大正值是 32767。如果您强制使用一个大于该值的数字,您将得到一个 负数 数字作为结果,因此 %
和 /
.
返回了意外的值
使用 unsigned 16 位整数(最大范围为 65535)或 32 位整数类型。
这实际上取决于您的体系结构如何存储 int。例如
- 8或16位系统short=16,int=16,long=32
- 32位系统,short=16,int=32,long=32
- 64位系统,short=16,int=32,long=64
这不是硬性规定 - 您需要先检查您的架构。还有一个long long但是有些编译器不认识它而且大小因架构而异。
有些编译器定义了 uint8_t 等,因此您可以实际指定数字的位数,而不必担心整数和长整数。
话虽如此,您希望将一个数字转换为 4 个 8 位整数。你可以有类似
的东西
unsigned long x = 600000UL; // you need UL to indicate it is unsigned long
unsigned int b1 = (unsigned int)(x & 0xff);
unsigned int b2 = (unsigned int)(x >> 8) & 0xff;
unsigned int b3 = (unsigned int)(x >> 16) & 0xff;
unsigned int b4 = (unsigned int)(x >> 24);
使用移位比乘法、除法或 mod 快得多。这取决于您希望实现的字节顺序。您可以使用 b1 和 b4 等的公式来反转分配。
我最终这样做了:
unsigned char bytes[4];
unsigned long n;
n = (unsigned long) sensore1 * 100;
bytes[0] = n & 0xFF;
bytes[1] = (n >> 8) & 0xFF;
bytes[2] = (n >> 16) & 0xFF;
bytes[3] = (n >> 24) & 0xFF;
CAN_WRITE(0x7FD,8,01,sizeof(n),bytes[0],bytes[1],bytes[2],bytes[3],07,255);
我在打包和解包大量自定义数据包时遇到过类似情况 transmitted/received,我建议您尝试以下方法:
typedef union
{
uint32_t u4_input;
uint8_t u1_byte_arr[4];
}UN_COMMON_32BIT_TO_4X8BIT_CONVERTER;
UN_COMMON_32BIT_TO_4X8BIT_CONVERTER un_t_mode_reg;
un_t_mode_reg.u4_input = input;/*your 32 bit input*/
// 1st byte = un_t_mode_reg.u1_byte_arr[0];
// 2nd byte = un_t_mode_reg.u1_byte_arr[1];
// 3rd byte = un_t_mode_reg.u1_byte_arr[2];
// 4th byte = un_t_mode_reg.u1_byte_arr[3];
我正在尝试将来自设备的输入(始终为 1 到 600000 之间的整数)转换为四个 8 位整数。
例如,
如果输入是32700,我要188 127 00 00
。
我通过使用实现了这一点:
32700 % 256
32700 / 256
以上方法一直有效到 32700。从 32800 开始,我开始得到不正确的转换。
我对此完全陌生,希望得到一些帮助以了解如何正确完成此操作。
主要编辑以下说明:
鉴于有人已经提到了 shift-and-mask 方法(这无疑是正确的方法),我将提供另一种方法,为了迂腐,它不可移植,依赖于机器,并且可能表现出未定义的行为。尽管如此,这仍然是一个很好的学习练习,IMO。
出于各种原因,您的计算机将整数表示为一组 8 位值(称为 字节);请注意,虽然非常常见,但这并非总是情况(参见CHAR_BIT
)。因此,使用超过 8 位表示的值使用多个字节(因此使用多个位的值是 8 的倍数)。对于 32 位值,您使用 4 个字节,并且在内存中,这些字节总是一个接一个。
我们称一个指针一个包含另一个值在内存中的地址的值。在这种情况下,byte 被定义为指针可以引用的最小值(就位计数而言)。例如,您的 32 位值,包含 4 个字节,将有 4 个“可寻址”单元(每个字节一个),其地址定义为这些地址中的第一个:
|==================|
| MEMORY | ADDRESS |
|========|=========|
| ... | x-1 | <== Pointer to byte before
|--------|---------|
| BYTE 0 | x | <== Pointer to first byte (also pointer to 32-bit value)
|--------|---------|
| BYTE 1 | x+1 | <== Pointer to second byte
|--------|---------|
| BYTE 2 | x+2 | <== Pointer to third byte
|--------|---------|
| BYTE 3 | x+3 | <== Pointer to fourth byte
|--------|---------|
| ... | x+4 | <== Pointer to byte after
|===================
所以你想做的事情(将 32 位字拆分为 8 位字)已经由你的计算机完成了,因为它是由它的处理器 and/or 内存架构强加给它的。为了获得这种几乎巧合的好处,我们将找到您的 32 位值的存储位置并读取其内存 byte-by-byte(而不是一次读取 32 位).
由于所有严肃的 SO 答案似乎都这样做,所以让我引用标准(ISO/IEC 9899:2018,6.2.5-20)来定义我需要的最后一件事(强调我的):
Any number of derived types can be constructed from the object and function types, as follows:
- An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. [...] Array types are characterized by their element type and by the number of elements in the array. [...]
- [...]
因此,由于数组中的元素被定义为连续的,内存中的 32 位值在具有 8 位字节的机器上实际上仅是其机器表示中的 4 个数组字节!
给定一个 32 位有符号值:
int32_t value;
其地址由&value
给出。同时,一个4个8位字节的数组可以表示为:
uint8_t arr[4];
请注意,我使用了无符号变体,因为这些字节本身并不真正代表数字,因此将它们解释为“有符号”是没有意义的。现在,指向数组 4-uint8_t
的指针定义为:
uint8_t (*ptr)[4];
如果我将 32 位值的地址分配给这样的数组,我将能够单独索引每个字节,这意味着我将直接读取字节,避免任何讨厌的移位和-屏蔽操作!
uint8_t (*bytes)[4] = (void *) &value;
我需要转换指针 ("(void *)
") 因为 我无法忍受抱怨的编译器 &value
的类型是 "pointer -to-int32_t
”,而我将它分配给“指向数组的指针 4-uint8_t
”,这种类型不匹配被编译器捕获并被标准;这是我们正在做的事情并不理想的第一个警告!
最后,我们可以通过索引直接从内存读取每个字节来单独访问每个字节:(*bytes)[n]
读取[=的第n
个字节27=]!
把它们放在一起,给定一个 send_can(uint8_t)
函数:
for (size_t i = 0; i < sizeof(*bytes); i++)
send_can((*bytes)[i]);
并且,为了测试目的,我们定义:
void send_can(uint8_t b)
{
printf("%hhu\n", b);
}
当 value
为 32700
:
188
127
0
0
最后,这说明了此方法依赖于平台的另一个原因:32 位字的字节存储顺序并不总是期望从二进制表示的理论讨论 i.e:
- 字节 0 包含位 31-24
- 字节 1 包含位 23-16
- 字节 2 包含位 15-8
- 字节 3 包含位 7-0
实际上,据我所知,C 语言允许 任何 24 种可能性来对这 4 个字节进行排序(这称为 endianness)。同时,移动和屏蔽总是会让你得到第 n
个“逻辑”字节。
你可以做一些位掩码。
600000 是 0x927C0
600000 / (256 * 256)
得到 9,还没有屏蔽。
((600000 / 256) & (255 * 256)) >> 8
得到 0x27 == 39。使用 8 个设置位的 8 位移位掩码 (256 * 255)
和右移 8 位,>> 8
,这也可以作为另一个/ 256
.
600000 % 256
为您提供 0xC0 == 192。屏蔽将是 600000 & 255
.
您可以在 16 位有符号整数中存储的最大正值是 32767。如果您强制使用一个大于该值的数字,您将得到一个 负数 数字作为结果,因此 %
和 /
.
使用 unsigned 16 位整数(最大范围为 65535)或 32 位整数类型。
这实际上取决于您的体系结构如何存储 int。例如
- 8或16位系统short=16,int=16,long=32
- 32位系统,short=16,int=32,long=32
- 64位系统,short=16,int=32,long=64
这不是硬性规定 - 您需要先检查您的架构。还有一个long long但是有些编译器不认识它而且大小因架构而异。
有些编译器定义了 uint8_t 等,因此您可以实际指定数字的位数,而不必担心整数和长整数。
话虽如此,您希望将一个数字转换为 4 个 8 位整数。你可以有类似
的东西unsigned long x = 600000UL; // you need UL to indicate it is unsigned long
unsigned int b1 = (unsigned int)(x & 0xff);
unsigned int b2 = (unsigned int)(x >> 8) & 0xff;
unsigned int b3 = (unsigned int)(x >> 16) & 0xff;
unsigned int b4 = (unsigned int)(x >> 24);
使用移位比乘法、除法或 mod 快得多。这取决于您希望实现的字节顺序。您可以使用 b1 和 b4 等的公式来反转分配。
我最终这样做了:
unsigned char bytes[4];
unsigned long n;
n = (unsigned long) sensore1 * 100;
bytes[0] = n & 0xFF;
bytes[1] = (n >> 8) & 0xFF;
bytes[2] = (n >> 16) & 0xFF;
bytes[3] = (n >> 24) & 0xFF;
CAN_WRITE(0x7FD,8,01,sizeof(n),bytes[0],bytes[1],bytes[2],bytes[3],07,255);
我在打包和解包大量自定义数据包时遇到过类似情况 transmitted/received,我建议您尝试以下方法:
typedef union
{
uint32_t u4_input;
uint8_t u1_byte_arr[4];
}UN_COMMON_32BIT_TO_4X8BIT_CONVERTER;
UN_COMMON_32BIT_TO_4X8BIT_CONVERTER un_t_mode_reg;
un_t_mode_reg.u4_input = input;/*your 32 bit input*/
// 1st byte = un_t_mode_reg.u1_byte_arr[0];
// 2nd byte = un_t_mode_reg.u1_byte_arr[1];
// 3rd byte = un_t_mode_reg.u1_byte_arr[2];
// 4th byte = un_t_mode_reg.u1_byte_arr[3];