做了这么多年软件开发,我发现一直没有搞懂有符号数,不知道你懂不懂?
问题是这样的,下位机程序往上位机发数据,发的是有符号数,上位机这边用字节流接收之后就按每两个字节转化为一个double类型的数据处理了,没有考虑符号位,也就是直接按无符号数处理了,导致发的和收的数据不一样。
趁此问题,肯定要好好研究一下有符号数和无符号数,以后再遇到此类问题就能避免不知不觉掉进坑里。
基本概念
想理解有符号数、无符号数就需要先了解机器数、真值、原码、补码、反码这几个概念:
- 机器数:一个数在计算机的存储形式是二进制数,我们称这些二进制数为机器数,机器数是有符号的,在计算机中用机器数的最高位存放符号位,0表示正数,1表示负数。
- 真值:因为机器数带有符号位,所以机器数的形式值不一定等于其真值,以机器数
1000 0011
为例,其真正表示的值为-3,而形式值为131。 - 原码:原码的表示与机器数真值一样,用第一位表示符号,其余位表示数值。例如十进制的的正负3,用8位二进制的原码表示分别为
0000 0011
和1000 0011
。 - 反码:
- 正数的反码是其原码本身。
- 负数的反码是在其原码的基础上,符号位不变,其余各位取反。
例如正负3的补码分别为
0000 0011
和1111 1100
- 补码:
- 正数的补码是其原码本身。
- 负数的补码是在其原码的基础上,符号位不变,其余各位取反后加1(即在反码的基础上加1)。
例如正负3的补码分别为
0000 0011
和1111 1101
从以上基本概念可以看出,正数的机器数、真值、原码、反码、补码都是一样的,而负数是不同的。
计算机中实际只存储补码。
所以如果是一个负数,就像我遇到的这个场景,下位机程序把一个负数发给上位机,上位机程序收到的实际是这个负数的补码,要得到其真实值就需要按有符号数来处理。我们就具体分析一下。
案例分析
在我的案例中,下位机发送的数据是从-8192 ~ 8192范围内的整数,每个数用两个字节表示。
我们知道,两个字节,如果是无符号数,可以表示的范围是0 ~ 65535,如果是有符号数,可以表示的范围是-32768 ~ 32767
假设现在上位机收到的数据中有这样两个字节F7 AB
,对应的十进制是63403,显然是错误的,因为我不应该收到大于8192的数,那怎么把这个63403还原成实际的数值呢?
这里我们先写出一段可以实现这一还原过程的代码:
代码语言:c复制// 假设变量temp就是上面提到的63403,a是要得到的实际的的数值
if (temp > 32768)
{
int a = ((int)(temp - 1) ^ 0xFFFF) * -1;
}
解释如下:
首先过滤出那些实际是负数的数
既然这个数是个负数,那它的最高位一定是1,一个最高位是1的双字节数最小是1000 0000 0000 0000
,按无符号数去理解的话,对应的十进制数是32768,所以只要是大于32768的数就是要处理的数。
然后还原
根据上面刚刚提到的基本概念,负数的补码是在其原码的基础上,符号位不变,其余各位取反后加1
,针对这句话做一个逆向操作:
- 它加1,那我们减1,
temp - 1
- 它取反,那我们再反过来,通过与
FFFF
进行异或(^)操作,就反了过来。顺便把最高位的1符号位也干掉了,变成了0,这样它就不能参与真值的计算了 - 最后再乘-1,变成负数就可以了
经过计算,得到了实际值-2133。
以上只是一个思路,能实现的方法肯定不止这一种,还要根据实际情况具体分析。
他们说,生物的尽头是化学,化学的尽头是物理,物理的尽头是数学