前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个有符号数引发的大案

一个有符号数引发的大案

原创
作者头像
角落工程师
发布2022-05-18 10:19:32
3990
发布2022-05-18 10:19:32
举报
文章被收录于专栏:工程师养成记

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

问题是这样的,下位机程序往上位机发数据,发的是有符号数,上位机这边用字节流接收之后就按每两个字节转化为一个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。

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本概念
  • 案例分析
    • 首先过滤出那些实际是负数的数
      • 然后还原
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档