一个有符号数引发的大案

2022-05-18 10:19:32 浏览数 (1)

做了这么多年软件开发,我发现一直没有搞懂有符号数,不知道你懂不懂?

问题是这样的,下位机程序往上位机发数据,发的是有符号数,上位机这边用字节流接收之后就按每两个字节转化为一个double类型的数据处理了,没有考虑符号位,也就是直接按无符号数处理了,导致发的和收的数据不一样。

趁此问题,肯定要好好研究一下有符号数和无符号数,以后再遇到此类问题就能避免不知不觉掉进坑里。

基本概念

想理解有符号数、无符号数就需要先了解机器数、真值、原码、补码、反码这几个概念:

  1. 机器数:一个数在计算机的存储形式是二进制数,我们称这些二进制数为机器数,机器数是有符号的,在计算机中用机器数的最高位存放符号位,0表示正数,1表示负数。
  2. 真值:因为机器数带有符号位,所以机器数的形式值不一定等于其真值,以机器数1000 0011为例,其真正表示的值为-3,而形式值为131。
  3. 原码:原码的表示与机器数真值一样,用第一位表示符号,其余位表示数值。例如十进制的的正负3,用8位二进制的原码表示分别为0000 00111000 0011
  4. 反码:
    • 正数的反码是其原码本身。
    • 负数的反码是在其原码的基础上,符号位不变,其余各位取反。 例如正负3的补码分别为0000 00111111 1100
  5. 补码:
    • 正数的补码是其原码本身。
    • 负数的补码是在其原码的基础上,符号位不变,其余各位取反后加1(即在反码的基础上加1)。 例如正负3的补码分别为0000 00111111 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,那我们减1,temp - 1
  2. 它取反,那我们再反过来,通过与FFFF进行异或(^)操作,就反了过来。顺便把最高位的1符号位也干掉了,变成了0,这样它就不能参与真值的计算了
  3. 最后再乘-1,变成负数就可以了

经过计算,得到了实际值-2133。

以上只是一个思路,能实现的方法肯定不止这一种,还要根据实际情况具体分析。

他们说,生物的尽头是化学,化学的尽头是物理,物理的尽头是数学

0 人点赞