闲扯原码,补码和反码

2023-05-16

                                          闲扯原码,补码和反码

始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处

 

人类习惯使用十进制数进行数值计算,而计算机则采用二进制,所以为了让计算机帮助人类计算,首先要把十进制数转换为二进制数。本文以最简单的8位定点整数为例,分析了计算机存储和计算数值的方法。

地球人都知道,整数有正负之分,但计算机却只认得“0”“1”,不知道符号“+”和“-”,所以有必要用“0”“1”来表示“+”“-”。人们规定用“0”表示“+”,用“1”表示“-”。

       这样,我们就可以表示出计算机能识别的整数了,我们把符号数值化后的二进制数称为机器数,相对应的,符号没有数值化(即仍用“+”“-”号表示)的二进制数称为真值。计算机只能处理机器数,不认识真值,真值是给人类看的。

       机器数有三种编码形式,分别称为:原码,补码和反码。为什么要搞得这么复杂,那些计算机科学家真的是吃饱了没事干吗?且听我慢慢道来:

       其实篇头已经介绍了机器码的一种形式——原码,它的特点是有效数值部分照抄真值,符号“+”“-”分别用“0”“1”表示。

例如,十进制数+6,它的真值是+000 0110(注意:8位二进制数最高位是符号位,所以其真值只有7位),对应的原码就是0000 0110

又如,十进制数-6,它的真值是-000 0110,对应的原码就是1000 0110

原码表示法比较直观,它的数值部分就是该数的绝对值,而且与真值的转换十分方便。但是它的加减法运算较复杂,当两数相加时,机器要首先判断两数的符号是否相同,如果相同则两数相加,若符号不同,则两数相减。在做减法前,还要判断两数绝对值的大小,然后用大数减去小数,最后再确定差的符号,换言之,用这样一种直接的形式进行加运算时,负数的符号位不能与其数值部分一道参加运算,而必须利用单独的线路确定和的符号位。要实现这些操作,电路就很复杂,这显然是不经济实用的。为了减少设备,解决机器内负数的符号位参加运算的问题,总是将减法运算变成加法运算,也就引进了反码和补码这两种机器数。

那如何将减法运算转化为加法运算呢?

首先引入 “模”的概念,是指一个计量系统的计数范围。以我们每天用来算时间的时钟为例,时钟的计量范围是011,所以它的模就等于12。计算机也可以看成一个计量机器,它也有一个计量范围,即存在一个 机器字长为n位的计算机的计量范围是02^n-1,模=2^n

  实质上是计量器产生溢出的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。例如,虽然时钟的模=12,但是在时钟的指针并不能真正指向“12点”,“12点”的位置和“0点”是重合的!用C语言表示就是12%12 == 0

任何有模的计量器,均可化减法为加法运算。这是为什么呢?

仍然以时钟为例,假设当前时针指向10点,而准确时间是6点,调整时间可有以下两种拨法:

一种是倒拨4小时,即:10-4=6

另一种是顺拨8小时:10+8=12+6=6

在以12为模的系统中,加8和减4效果是一样的,因此凡是减4运算,都可以用加8来代替。

”12而言,84互为补数。插一句,所谓“补数”,实际上是模拟了数学中“补角”的概念,如果两个角的度数之和为180度,我们就称这两个角互为补角。同样的,如果在某个计量系统中,两个数之和刚好等于模,则它们互为“补数”,例如,在以12为模的系统中,111102937566都互为补数。

对于计算机,其概念和方法完全一样。机器字长为n位的计算机,设n=8 所能表示的最大数是11111111,若再加1称为100000000(9),但因只有8位,最高位1自然丢失,又回了00000000,所以8位二进制系统的模为2^8 在这样的系统中减法问题可以化成加法问题,只需把减数用相应的补数表示就行了。

把补数用到计算机对数据的处理上,就是补码。补码也是一种机器码,它克服了原码的一些缺陷,一方面使符号位能与有效值部分一起参加运算,从而简化运算规则;另一方面使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计。现代的计算机都是用补码的形式来存储数据和进行算术运算的。

那补码是如何编码的,即我们如何将一个整数的真值转换为一个8位补码呢?

回到最初的例子,十进制数+6。我们已经知道了它真值是+000 0110,原码是0000 0110。并且用自然语言介绍了如何实现真值和原码的转换。但是,一个众所周知的事实是:“自然语言”不如“数学语言”严谨!我们希望能够用数学表达式来表示真值和原码的关系,这就是:

设机器字长为N位,真值为X,则:

[X]  = X         0 <= X < 2^(n-1)

[X]  = 2^(n-1) - X -2^(n-1) < X <= 0   

如何来理解这个公式呢?

仍以十进制数+6-6为例:

[+6]  = 6,把6转换为8二进制数,就得到原码0000 0110。(本文的最后将会提供一个把十进制数转换为机器码的C++算法实现)。

[-6]  = 2^(8-1) – (-6) = 256 + 6 = 262,把262转换为8二进制数,就得到原码1000 0110。即最高位本来是0,加了一个2^(8-1)后,最高位就变成1了。

同样我们给出补码的数学表达式:

[X]  = X         0 <= X < 2^(n-1)

[X]  = 2^n + X   -2^(n-1) <= X < 0

和原码一样,正数的补码就等于真值,那如何理解负数的补码呢?

例如,[-6]  = 2^8 + (-6) = 512 – 6 = 506

且慢,这个506怎么这么熟悉!它不正是以2^8为模的6的“补数”吗?原来负数的补码就等于它的绝对值的补数啊!

506转换为8二进制数,就得到-6的补码1111 1010

得到某个数的补码后,我们就可以把减法运算转化为加法运算了。

补码加法的运算法则为:[X +Y] = [X] + [Y]

1X =+011 0011Y=+010 1001,求[X+Y]

解:[X +Y] = [X] + [Y] = 0011 0011 + 0010 1001 = 0101 1100

2X =+011 0011Y=-010 1001,求[X+Y]

解:[X +Y] = [X] + [Y] = 0011 0011 + 1101 0111 = 0000 1010 (进位溢出)

注:因为计算机中运算器的位长是固定的(本例中只有8位),上述运算中产生的最高位进位将丢掉,所以结果不是1 0000 1010,而是0000 1010

补码减法公式[X - Y] = [X] -  [Y] = [X] + [-Y]

其中:[-Y]补称为负补,求负补的办法是:对补码的每一位(包括符合位)求反,且未位加1

3X =+011 0011Y=+010 1001,求[X-Y]

解:[X - Y] =  [X] + [-Y] = 0011 0011 + 1101 0111 = 0000 1010 (进位溢出)

2X =+011 0011Y=-010 1001,求[X-Y]

解:[X - Y] =  [X] + [-Y] = 0011 0011 + 0010 1001 = 0101 1100

根据补码加减运算得到的结果仍然是补码,若要将补码转换成原码,只要对其再求一次补码就行了。

再来说说反码。当初引入反码是为了解决原码运算所遇到的困难,但由于反码自身也存在一定的缺陷,加之补码在机器运算中的优越表现,完全掩盖了反码的光芒,以至于现在人们之所以提到反码,只是因为在用笔算将真值转换为补码的时候,可以快一些——先将原码转换为反码,然后反码加1,就得到了补码——但是对于计算机来说,反码这个中介完全是没有必要的。

反码和原码的关系很紧密,反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。

同样我们给出反码的数学表达式:

[X]  = X         0 <= X < 2^(n-1)

[X]  = 2^n – 1 + X   -2^(n-1) < X <= 0

 从数学表达式中我们可以发现,整数的三种机器码都是相同的,而负数的则不同。负数的反码是对原码按位求反(符合位除外),而补码则等于反码加1。这样有了反码这个中介,我们即使是用笔算也能够很快地将原码转换为补码了。例如:

[+6]  = 6 [-6]  = 2^(8-1) – 1 + (-6) = 255 + 6 = 261,把261转换为8二进制数,就得到反码1111 1001

由于-6的原码为1000 0110,我们稍作观察,就可找到反码和原码的关系。

再将反码加1,就得到-6的补码1111 1010

现在明白了吧?

 

附录:把十进制数转换为机器码的C++程序代码

 

#include <iostream>

using namespace std;

 

const int MAX = 32;

 

void Binary(char b[], int x); //x转换为二进制数

void TrueForm(char b[], int x); //获取原码

void RadixMinus(char b[], int x); //获取反码

void Complement(char b[], int x); //获取补码

void TruthValue(char b[], int x);//获取真值

 

int main()

{

      int x = 1;

      char b[MAX+1]={0};

     

      cout << "十进制数:" << x << endl;

      TruthValue(b, x);//获取真值

      cout << "真值:" << b << endl;

     

      TrueForm(b, x); //获取原码

      cout << "原码:" << b << endl;

     

      RadixMinus(b, x);//获取反码 

      cout << "反码:" << b << endl;

     

      Complement(b, x);//获取补码

    cout << "补码:" << b << endl;

   

    cout << "十进制数:" << -x << endl;

      TruthValue(b, -x);//获取真值

      cout << "真值:" << b << endl;

     

      TrueForm(b, -x); //获取原码

      cout << "原码:" << b << endl;

     

      RadixMinus(b, -x);//获取反码 

      cout << "反码:" << b << endl;

     

      Complement(b, -x);//获取补码

    cout << "补码:" << b << endl;

     

    system("pause");

    return 0;

}

 

void Binary(char b[], int x)//x转换为二进制数

{

    for (int i=MAX-1; i>=0; i--)

    {

           b[i] = (x & 1) + '0';

           x >>= 1;

      }

      b[MAX] = '/0';

}

 

void TrueForm(char b[], int x) //获取原码:根据数学表达式求得

{

    if (x >= 0)

          Binary(b, x);

      else

          Binary(b, (1<<(MAX-1)) - x);

}

 

void RadixMinus(char b[], int x) //获取反码:正数的反码=补码;负数的反码=补码-1

{

    if (x >= 0)

          Binary(b, x);

      else

          Binary(b, x - 1);

}

 

void Complement(char b[], int x) //获取补:数据在计算机中以补码形式存储,直接转换即可

{

    Binary(b, x);

}

 

void TruthValue(char b[], int x)//获取真值:根据原码获得真值

{

    TrueForm(b, x);

      b[0] = (b[0] == '0') ? '+' : '-';  

}

 

参考文献:

1Boater的博客:《反码和补码技术是怎样被提出的?》

http://blog.tianya.cn/blogger/post_show.asp?BlogID=227218&PostID=7046448

2北半球的孤独发帖:《关于机器数的几点注记》

http://forum.noi.cn/thread-29319-1-1.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

闲扯原码,补码和反码 的相关文章

  • android FAILED Binder Transaction 问题的原因

    今天在做一个widget的时候需要填充一个ImageView xff0c 图片来自与网络 发现都会出现FAILED Binder Transaction这个问题 通过google得知 xff0c 原来图片的size不能超过40k Activ
  • 加sudo后执行.sh报错command not found

    今天执行一个 sh文件时 xff0c 刚开始没加sudo xff0c 报了错 xff1a jetson1 64 jetson1 span class token operator span desktop span class token
  • windows 安装zabbix客户端安装

    1 下载安装zabbix agent 打开zabbix的官网下载地址 xff0c 根据自己的zabbix server的版本选择对应的agent版本 zabbix 官网下载地址 由于前文我使用的是zabbix 5 4 xff0c 所以此处我
  • Qt编译arm linux版本-qt everywhere 5.14.0

    本教程仅适用于qt everywhere 5 14 0 迄2021 07 14本人发布为止 xff0c 这是互联网上能找到的最新的交叉编译版本教程 真实 详细 半可用 xff08 后续未测试是否能运行 xff09 1 首先下载qt http
  • 内置googletts并且内置中文语音包

    先上补丁 diff git a frameworks base core java android speech tts TextToSpeech java b frameworks base core java android speec
  • rk3128 7.1修复插入键盘后软键盘仍然弹出的bug

    有客户反馈说 xff0c 3128插入物理键盘后 xff0c 输入时软键盘仍然弹出来 测试测了一下果然是这样 xff0c 一开始还以为前任码农改出来的问题 xff0c 试了一下最新sdk xff0c 仍然如此 卧槽 然后上网搜 xff0c
  • RK3128 7.1新SDK开otg

    dts xff1a usb0 usb 64 10180000 0 Normal 1 Force Host 2 Force Device rockchip usb mode 61 lt 2 gt 这个是重点 device mk PRODUCT
  • 给logcat添加额外的输出

    打logcat的时候 xff0c 通常是需要排bug的时候 我们希望知道这是什么固件 xff0c 系统已运行多久当前的内存情况等信息 可以这么改 system core logcat logcat cpp void dumpSysInfo
  • Chromium源码获取与编译--附加一个简单例子的编译

    从工作到现在 xff0c 一直弄界面相关的东西 xff0c 弄了好多年 xff0c 包括前公司也买了一套UI xff0c 但是用下来以后发现不是这个问题就是那个问题 现在新的公司还在用MFC MFC其实没什么不好 xff0c 就是不能改源码
  • windows下使用cmake编译zlib与libpng 留此备份

    首先准备工具 cmake http www cmake org cmake resources software html zlib http www zlib net libpng http www libpng org pub png
  • opengl下png图片的加载与显示

    首先说明 部分代码来自http www cnblogs com IamEasy Man archive 2009 12 14 1624023 html 至于里面的源地址什么的我不管 然后吐槽 xff0c 国内的资料各种坑爹 xff0c 竟没
  • opengl透明mask图片做蒙版效果的实现

    hello xff0c 朋友们 xff0c 我又来了 xff0c 还记得上篇文章提到的事情否 xff1f 没错 xff0c 加载png只是第一步 xff0c 接下来要实现用mask图扣掉png中不需要的部分 xff08 也就是cocos2d
  • Unity3d 5.3.5使用sqlite3

    国内讲的乱七八糟 xff0c 更有故作神秘提供Mono Data Sqlite dll System Data dll的 xff0c 就是不告诉你这两文件在哪里 我很无语 看国外的 xff0c 多靠谱 http answers unity3
  • Zabbix 监控主机是否在线

    最近有点忙 xff0c 很久没有更新博客了 今天这篇文章主要记录如何通过zabbix监控一台主机是否在线 好啦开干 1 新建一个模板 新建模板的目的是在新模板上添加 监控项 和 触发器 的 xff0c 如果你要监控的主机数量非常少的话 xf
  • VSCODE 使用SSH远程连接(Windows10)

    开启Windows10的SSH功能 点击开始菜单 gt 设置 gt 应用 gt 应用和功能 gt 可选功能 gt 添加功能 xff1b 添加OpenSSH 客户端 和 OpenSSH 服务器 然后cmd 输入ssh xff0c 一般会出现下
  • Jetson NX emmc版本系统转移到SSD

    因emmc版本的NX自带内存不够大 xff0c 只有16GB xff08 手上的是这个型号 xff09 xff0c 安装系统大概需要除去4G多内存 xff0c 再安装CUDA cuDNN TensorRT等内存直接爆满 无法继续使用 所以需
  • unix 修改文件类型

    Unix及类Unix系统里 xff0c 每行结尾只有换行 n xff1b Windows系统里面 xff0c 每行结尾是换行 43 回车 n r 第一种 xff1a 利用Linux下的vim编辑器 xff0c 可以方便的在dos文件 uni
  • DIR=$(cd `dirname $0`; pwd)

    dirname 0 xff0c 获取当前脚本所在绝对目录 cd 96 dirname 0 96 xff0c 进入这个目录 xff08 切换当前工作目录 xff09 pwd xff0c 显示切换后脚本所在的工作目录 echo xff0c 打印
  • ulimit命令的使用

    ulimint a 用来显示当前的各种用户进程限制 Linux对于每个用户 xff0c 系统限制其最大进程数 xff0c 为提高性能 xff0c 可以根据设备资源情况 xff0c 设置个Linux用户的最大进程数 xff0c 一些需要设置为

随机推荐

  • error while loading shared libraries: libavformat.so.58: cannot open shared object file: 解决

    sutpc icvtsn dfa error while loading shared libraries libavformat so 58 cannot open shared object file No such file or d
  • Git 检测不到文件目录下的文件变化信息

    Git在上传代码的时候发现有的只能检测出某个目录 xff0c 然而检测不到那个目录下的C文件和h文件 并且git 提交之后 xff0c 在git上某个文件有后缀 64 b4c4u7之类的 这种文件没办法点击 没办法查看 xff0c 根本无法
  • Linux 搭建MQTT服务器并使用

    环境 xff1a ubuntu 18 04 1 安装必备软件 span style background color f6f6f6 span style color 121212 yum install gcc c 43 43 cmake
  • 批量ping多个IP地址

    for L D in 90 1 95 do ping 10 10 70 D gt gt ping log 代码的意思如下 xff1a 代码中的这个 90 1 95 就是网段起与始 xff0c 就是检测网段10 10 70 90到10 10
  • Linux驱动开发一

    一 开发模块框架 1 xff09 编写源代码 include lt linux init h gt include lt linux module h gt 加载函数 static int init hello init void prin
  • ”此网站尚未经过身份验证“问题的解决办法

    在使用 https 访问我的路由器时出现无法访问的现象 xff0c 下图是浏览器的告警信息 xff0c 目前通过百度 此网站尚未经过身份验证 并没有找到解决方案 而我的另一台笔记本电脑却可以通过 https 打开路由器的配置管理页面 xff
  • linux的yum更新方式update和upgrade

    Linux升级命令有两个分别是yum upgrade和yum update 这个两个命令是有区别的 代码如下 yum y update 升级所有包同时也升级软件和系统内核 代码如下 yum y upgrade 只升级所有包 xff0c 不升
  • Linux驱动开发二

    2 xff09 创建设备节点 gt 手动创建设备节点 mknod 设备名 设备类型 主设备号 次设备号 mknod dev hello c 254 0 gt 自动创建设备节点 创建类 class create owner name 参数1
  • SO_BINDTODEVICE 使用

    就绑定到了接口 34 lmi40 34 上 所有数据报的收发都只经过这个网卡 对于SOL BINDTODEVICE的总结如下 xff08 1 xff09 对于TCP套接口 UDP套接口 RAW套接口 xff0c 可以通过SO BINDTOD
  • json交叉编译并移植到嵌入式开发板

    1 解压 xff1a tar xvf json c 0 9 tar gz 默认解压在当前目录 2 进入解压后的目录 xff1a cd cd json c 0 9 3 执行 xff1a sudo configure CC 61 aarch64
  • strftime()函数的使用方法

    strftime strftime是C语言标准库中用来格式化输出时间的的函数 Ubuntu自带的manual手册说法如下 xff0c 截图没有截全 xff0c 需要更详细信息的自行查看相关的内容 下面是strftime的用法 示例代码如下
  • tcpdump 抓包

    一 Tcpdump抓包 抓取端口为2008的数据包 抓包文件内容 抓取到的内容保存在文件中 xff0c 可以通过wireshark分析 二 tcpdump的一些命令 tcpdump和ethereal可以用来获取和分析网络通讯活动 xff0c
  • wireshark抓包工具的使用

    前言 wireshark是非常流行的网络封包分析软件 xff0c 功能十分强大 可以截取各种网络封包 xff0c 显示网络封包的详细信息 使用wireshark的人必须了解网络协议 xff0c 否则就看不懂wireshark 为了安全考虑
  • Docker 在Ubuntu的安装

    系统要求 Docker 支持以下版本的 Ubuntu 操作系统 xff1a Ubuntu Hirsute 21 04Ubuntu Groovy 20 10Ubuntu Focal 20 04 LTS Ubuntu Bionic 18 04
  • Notepad++ 如何筛选内容

    在程序调试的过程中 xff0c 通常会通过抓取日志来分析 xff0c 但实际情况抓取的日志会很多 xff0c 如何在众多的日志中筛选出自己想要的内容呢 xff0c 我们可以通过notepad打开日志 xff0c 然后进行筛选 1 通过ctr
  • Jetson Nano emmc版本系统镜像备份和烧录

    一 镜像备份 1 xff0e 将待复制的jetson设备进入恢复模式 xff0c 用数据线连接jetson设备和主机 对于原厂开发板将FC REC引脚与GND短接 xff0c 通过micro usb到usb数据线连接到电脑 在电脑的ubun
  • Ubuntu系统永久挂载硬盘、U盘等存储设备

    在日常开发工作中 xff0c 经常会遇见需要借助外接存储设备来存放一些数据的情况 xff0c 于是我们会使用如下命令来挂载 xff1a sudo mount dev sda2 mnt 这样挂载的弊端就是设备掉电之后 xff0c 之前挂载的设
  • ubuntu20.04 + kiosk + chrome打造一体机系统

    kiosk 的英文直译为凉亭 公用电话亭 报摊或者一体机的意思 目前主流的浏览器都具有kiosk模式 我们可以使用浏览器的kiosk模式加ubuntu操作系统在不增加任何软件的情况将我们的B S程序打造成一个一体机系统 1 前置条件 ubu
  • Ubuntu 增加swap交换内存

    一 创建虚拟内存 在实际开发中发现swap交换分区不够用了 xff0c 于是需要创建虚拟内存来增加交换分区的大小 在系统空闲空间位置创建swap虚拟内存专用文件夹 cd data 切到你想要创建交换分区的目录 mkdir swap 新建文件
  • 闲扯原码,补码和反码

    闲扯原码 xff0c 补码和反码 始发于 goal00001111 的专栏 xff1b 允许自由转载 xff0c 但必须注明作者和出处 人类习惯使用十进制数进行数值计算 xff0c 而计算机则采用二进制 xff0c 所以为了让计算机帮助人类