(16条消息) C# 从补码中获取有符号数的实际数值
原理
计算机存储数据时,默认是存储数据的补码。有符号的数粗存在符号位(最高位)。
这里就会提到原码、反码、补码的概念。
原码:用符号位和数值表示带符号数,正数的符号位用“0”表示,负数的符号位用“1”表示,数值部分用二进制形式表示。
反码:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。
补码:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1.
思路
下位机上传有符号数时,直接上传的是补码。上位机解析时,需要手动转换:
一、当数为负数时。
0.获取该数据的补码,去掉最高位(符号位)并将其合并到一个结构体中(数据通信一般以字节为单位)。
1.空位(不用的位)补全,高位不用的置1。
2.将补码-1
3.然后再取反
4.乘上比例尺与-1
二、当数为正数时。
0.获取该数据的补码,去掉最高位(符号位)并将其合并到一个结构体中(数据通信一般以字节为单位)。
1.乘上比例尺
示例
下位机上传的数据中有8个字节,其中第4(从0开始)字节的低四位和第5字节的高7位组成一个参数,这个参数是有符号的数据,与整数之间的比例尺是0.1。要求解析出该参数。
代码
// 获取数据 byte[] data = new byte[8]; data = GetDataFromUsb(); // 解析数据 // 当上传的数据为负数时 if ((UInt16)(data[4] & 0x08) == 0x08) { // 去掉最高位获取数据 UInt16 s = (UInt16)(((data[4] & 0x07) << 7) + (UInt16)((data[5] >> 1) & 0x7F)); // 空位补全,高位不用的置1 UInt16 s1 = (UInt16)(s | 0xFC00); // 补码-1 UInt16 s2 = (UInt16)(s1 - 1); // 取反 UInt16 s3 = (UInt16)~s2; // 乘上-1和比例尺 _Speed = -(s3 * 0.1f); } // 当待解析的数据为正数时 else { // 去掉最高位获取数据 UInt16 s = (UInt16)(((data[4] & 0x07) << 7) + (UInt16)((data[5] >> 1) & 0x7F)); // 乘上比例尺 _Speed = s * 0.1f; }
图表解释
1._Speed参数的占位情况。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
data[4] | F_Speed | _Speed | _Speed | _Speed | ||||
data[5] | _Speed | _Speed | _Speed | _Speed | _Speed | _Speed | _Speed |
其中data[4]的第3位为我们符号位,也就是F_Speed。当我们做有符号码转换时,可以直接忽略,因为他不影响我们真实值的改变。
2.空位补全缘由
当我们使用更大的空间来做容器填充数据时,负数的空位补全。
UInt16也就等于2个字节
第0步运算之后,我们数据如下图所示。
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
_S | _S | _S | _S | _S | _S | _S | _S | _S | _S |
我们知道,上面的所有_S的数据都是元数据的补码,因为该数是负数,所以我们需要将这个数据的符号位置为1,其它未使用的位(14、13、12、11、10)也置为1。这样我们就可以通过-1再求反码得到负数的值了。
赞 (0)