C语言:深入补码计算原理

C语言:深入补码计算原理

    • 有符号整数存储
      • 原码、反码、补码
        • 转换规则
        • 数据与内存的关系
    • 补码原理


有符号整数存储

原码、反码、补码

有符号整数的2进制表示方法有三种,即原码、反码和补码
三种表示方法均有符号位和数值位两部分,符号位用0表示“正”,用1表示“负”

有符号整数最高位的一位是被当做符号位,剩余的都是数值位。
无符号整数所有的位都是数值位


转换规则

正整数:原、反、补码都相同。

负整数:
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。

正数:

int a = 5;

对于int(整形),内存会开辟4个字节来存放a。由于a是正数,第一位符号位为0,数值为5,转化为二进制就是101,高位补0。正数的原反补三码相同。
故:

a 的原码: 00000000 00000000 00000000 00000101
a 的反码: 00000000 00000000 00000000 00000101
a 的补码: 00000000 00000000 00000000 00000101

负数:

int b = -5;

由于在此b是负数,在原码中,第一位是符号位,存放1,表示负数。
反码:符号位不变,保持为1。其余位按位取反,即0变1,1变0.
补码:在反码的情况下加1。
故:

b 的原码: 10000000 00000000 00000000 00000101
b 的反码: 11111111 11111111 11111111 11111010
b 的补码: 11111111 11111111 11111111 11111011

而补码想要变回原码,也是相同的步骤,即先取反后加一。

数据与内存的关系

首先,我们在内存中存储的数据是以补码的形式存储的。我们用代码定义a,b为5-5,然后观察其在内存中的值:
在这里插入图片描述
由于二进制实在难于分辨,可读性非常差,所以编译器在向程序员呈现计算机存储的值的时候,会转为16进制。我们从上图中可见,a的存储是0x00000005即16进制的5。可b的值却不是-00000005。这个fffffffb其实就是-5的补码 11111111 11111111 11111111 11111011的16进制形式,由此可以证明,内存存储有符号整数就是以补码的形式。

那为什么内存要存补码?

  • 可以把符号与数值统一处理,把数字的正负放在码值中,不用额外区分。
  • 可以使加法减法统一处理(CPU只有加法计算器)。

第一点其实是容易理解的,那为什么用补码可以统一加减法呢?我们以下面的代码为例:

int a = 3;
int b = 5;
int c = a - b;

在以上计算过程中,计算机会把3 - 5当作3 + (-5),然后将两个数字的补码相加。

a 的补码: 00000000 00000000 00000000 00000011
b 的补码: 11111111 11111111 11111111 11111011
--------------------------------------------
两者相加: 11111111 11111111 11111111 11111110
上式取反: 10000000 00000000 00000000 00000001
上式加一: 10000000 00000000 00000000 00000010
最后得到的值即为-2

可以看到我们确实以加法的形式,完成了减法的计算。以上代码中,计算机想要完成3 - 5,于是CPU将其转化为了3 + (-5),然后直接将补码相加,然后转回原码,得到的就是正确答案。

我们再来看到一个案例:

int a = 10;
int b = 5;
int c = a - b;

在以上计算过程中,计算机会把10 - 5当作10 + (-5),然后将两个数字的补码相加。

a 的补码: 00000000 00000000 00000000 00001010
b 的补码: 11111111 11111111 11111111 11111011
--------------------------------------------
两者相加: 1 00000000 00000000 00000000 00000101

此时出现了一个问题,那就是进位导致的位数溢出,由于int类型只能存储32位数据,此时超出的数据就会被丢弃
所以计算后实际值是: 00000000 00000000 00000000 00000101也就是5,我们再次完成了计算。

可见,虽然代码是减法,但是在计算机处理的时候,只做了加法运算。这样可以减少计算机硬件的消耗,只需要在CPU内部做好加法的硬件即可。

本博客还要继续探讨,为什么计算机可以通过补码计算统一加减法。


补码原理

现在假设我们有一个十进制计算机,每个bit位可以存储0 - 9十种数据,现在我们有一个可以存储4位数据内存,接下来我们要完成一个计算:

求出50 - 19的结果:

值得注意的是:由于我们只能存储4位数据,所以如果我们计算中发生了溢出,多出来的位要舍弃。

50 的存储:0050
19的存储:0019

接下来我们进行计算:

50 - 19
= 50 + (-19)
= 50 + (-19) + 9999 + 1 - 10000

到这一步,我暂时停止了,因为我要重复申明刚刚的规则:计算中发生了溢出,多出来的位要舍弃

由于计算机只能存储四位,而计算机是一步一步进行计算的,所以中间的每一个变量都要保存下来,而在保存10000时,我们的位数溢出了,要舍弃高位的1,于是上述计算变为:

= 50 + (9999 - 19) + 1 - 0000
= 50 + (9999 - 19 + 1)
= 50 + 9981
= 10031

由于计算机只能存储四位,而在保存10031时,我们的位数溢出了,舍弃高位的1,于是上述计算变为:

= 0031
= 31

50 - 19结果就是31,整个计算过程看起来非常怪异,为什么还能得到正确结果???

首先,我们要计算的是一个减法 50 - 19,于是我把它转化为了加法50 + (-19),但是这依然改变不了它需要进行相减的本质,我们有没有办法把-19转化为一个正数x,让50 + x == 50 + (-19)

在数学界,这就是异想天开,但是我们的数据是存在计算机中的,其实是有可能实现的,而实现它的本质,就在于丢弃溢出位数的功能。

数学角度,10000 + (-19) + 50等于10031,但是从这个只能存四位的内存的角度,10031就是31。也就是说,我们可以给一个负数加上刚好到溢出位数的数字,故意让其溢出,这样就可以50 + (10000 - 19) == 50 + (-19)了。

但是这涉及到一个问题:根本没有四位的内存可以存下10000这个数据,那就更无法在计算机中完成10000 + (-19)了。于是我们将10000拆分为两个4位以内的数字99991,让10000 + (-19)变成9999 + 1 + (-19),这就是计算机可以执行的了。这样你也许就可以看懂以上的算式了。


接下来,我们要把上面的溢出效果带入到一般的二进制计算机中:

现在我们要完成减法:5 - 3,数据都以int类型存储:

那么我们就要先将上式转化为5 + (-3),接着对-3进行一次溢出的计算:

-3的二进制: -00000000 00000000 00000000 00000011(前面有负号)
加上一个刚好溢出的整数: 1 00000000 00000000 00000000 00000000
拆解出两个int可以存储的数字: 11111111 11111111 11111111 11111111 + 1
先让-3 + 11111111 11111111 11111111 11111111
结果:11111111 11111111 11111111 11111100
再+1:11111111 11111111 11111111 11111101

最后我们就可以拿11111111 11111111 11111111 11111101 代替-3进行计算了。
而中途有一个过程:

先让-3 + 11111111 11111111 11111111 11111111
结果:11111111 11111111 11111111 11111100

你有没有发现,这个结果11111111 11111111 11111111 11111100 刚好就是-3的每一位取反?
数学角度来说,-3 + 11111111 11111111 11111111 11111111 是要执行减法的,可是二进制中可以用每一位取反达到同样的效果,而对于计算机而言,对每一个bit位取反并不是什么难事。因此计算机的计算,就是在这一步把减法消除掉的。
其实这就是得到反码的过程,所以反码要求按位取反

而最后一步

再+1:11111111 11111111 11111111 11111101

也就是得到补码的过程:反码 + 1,这是为了补齐前面少的一个数字,凑出刚好溢出的数字。

整个过程都是为了让一个负数转化为一个等效的正数。而之所以要分反码补码,不能一步到位,就是因为刚好差1,内存位数不够无法存储,必须拆一个1出来分两步计算

另外的,因为有符号整数,既可以存正数,也可以存负数,所以拿出第一位当符号位。而符号位不参与计算,因此取反的时候不包括符号位

至此,你应该明白了为什么要用补码的机制存储,也就是利用溢出机制,凑出一个等效的正数,然后再计算


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/446452.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法第二十六天-删除有序数组中的重复项Ⅱ

删除有序数组中的重复项 题目要求 解题思路 题目要求中提到原地修改,那么肯定需要一个指针指向当前即将放置元素的位置,需要另外一个指针向后遍历所有元素,所以[双指针]解法呼之欲出。 慢指针slow:指向当前元素放置的位置&…

蓝桥杯第一天

这题就是典型的位数贡献大于数量贡献, 1花的火柴更少,所以尽量用完10个1,然后其实就是简单的背包问题尽量拿最多的物品(数字),限重为300,各物品(数字)的重量即为所需火柴…

波动数列 刷题笔记

思路分析 dp 找出状态转移方程 设d为a或者-b 代码 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N1010,MOD100000007; int get_mod(int a,int b){ return (a%bb)%b; …

深入解读 Elasticsearch 磁盘水位设置

本文将带你通过查看 Elasticsearch 源码来了解磁盘使用阈值在达到每个阶段的处理情况。 跳转文章末尾获取答案 环境 本文使用 Macos 系统测试&#xff0c;512M 的磁盘&#xff0c;目前剩余空间还有 60G 左右&#xff0c;所以按照 Elasticsearch 的设定&#xff0c;ES 中分片应…

全国保护性耕作/常规耕作农田分类数据集

基于Sentinel-2遥感产品&#xff0c;使用来自文献调研和目视解译产生的保护性/常规耕作样本点&#xff0c;通过交叉验证方法训练随机森林分类器&#xff0c;生成了2016-2020年全国保护性耕作/常规耕作农田分类数据集。分类代码&#xff1a;0值代表非农田&#xff0c;1值表示第一…

laravel-admin 头部添加操作

新建html 样式及js namespace App\Admin\Extensions\Nav;class Links {public function __toString(){return <<<HTML<li><a href"" οnclick"js_method();return false;"><i class"fa fa-floppy-o"></i><s…

方法的使用

1.什么是方法(method) 在java中方法就是一个代码片段.。几乎相当于c语言的函数。 2.方法定义 方法跟函数是几乎一样的。所以语法是大差不差的。就多了一点东西。之前我们在c语言里已经很详细讲过了函数。这里就简便的讲一下。 相比c语言函数多了个修饰符 。 现在看下其注意…

深入理解JavaScript内存泄漏:原因与解决方法

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Visual C++ 2010学习版安装教程

1. 创建项目 点击 “创建新项目”&#xff0c;创建一个项目。 2. 创建 helloworld.c ⽂件 3. 在弹出的编辑框中&#xff0c;选中 “C文件(.cpp)”&#xff0c;将 下方 “源.cpp” 手动改为要新创建的文件名。 如&#xff1a;helloWorld.c 。注意&#xff0c;默认 cpp 后缀名&am…

记录西门子:IO隔离SCL编程

在PLC变量中创建IO输入输出 在PLC类型中创建输入和输出&#xff0c;并将PLC变量的输入输出名称复制过来 创建一个FC块或者FB块 创建一个DB块 MAIN主程序中&#xff1a;

文件包含漏洞初识

一、基础知识介绍 在web后台开发的时候&#xff0c;我们会使用PHP&#xff0c;Java这种代码&#xff0c;而在使用的过程中&#xff0c;我们经常会使用包含函数&#xff08;也就是调用&#xff09;&#xff0c;而很多时候&#xff0c;前端用户在选择浏览时会调用包含的文件这无…

使用CSS制作动态的环形图/饼图

使用纯 CSS Animation conic-gradient 实现一个环形图。 饼图的实现思路和环形图一样&#xff0c;去掉中间的圆形遮盖 after 伪类元素即可。 一、构建基础样式 构建圆形节点和中间的遮盖元素。 <style>body {background-color: rgb(130, 226, 255);}.circle {top: 16…

个人网站展示(静态)

大学期间做了一个个人博客网站&#xff0c;纯H5编码的网站&#xff0c;利用php搭建了一个留言模块。 有需要源码的同学&#xff0c;可以联系我~ 首页&#xff1a; IT杂记模块 文人墨客模块 劳有所获模块 生活日志模块 关于我 一个推崇全栈开发的前端开发人员 微信: itrzzh …

蓝桥杯备战刷题five(自用)

1.数字三角形&#xff08;方向次数限制&#xff0c;动态规划&#xff09; //如果n为奇数时&#xff0c;最后必然走到最后行最中间的数&#xff0c;如果为偶数&#xff0c;则取中间两个数的最大值&#xff0c; //因为向左下走的次数与向右下走的次数相差不能超过 1 #include …

【动态规划】代码随想录算法训练营第四十四天 |完全背包,518. 零钱兑换 II , 377. 组合总和 Ⅳ (待补充)

完全背包理论基础 完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和…

仿牛客网项目---项目总结

本篇文章是对整个项目的一个总结。下面这张图要好好理解。 整个项目都是构建在SpringBoot之上的&#xff0c;所以把它画到最底下&#xff0c;其它技术依托在springboot之上。但是springboot并不是技术的核心&#xff0c;而只是起到了一个辅助的作用&#xff0c;它的作用仅仅是降…

后端项目访问不了

问题&#xff1a; 后端启动不了&#xff0c;无法访问网站 原因&#xff1a; 1.防火墙没有关 2.有缓存 3、项目没有启动 4、docker没有启动 解决&#xff1a; 先查看进程&#xff1a;docker ps&#xff0c;必须有三个 详细查看&#xff1a;docker ps -a exited代表没有开启…

AI相关的实用工具分享

AI实用工具大赏&#xff1a;赋能科研与生活&#xff0c;探索AI的无限可能 前言 在数字化浪潮汹涌而至的今天&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面&#xff0c;无论是工作还是生活&#xff0c;都在悄然发生改变。AI的崛起不仅为我们带…

怎么看待Groq

用眼睛看。 就是字面上的意思用眼睛看。 我属于第一波玩到的,先给大家一个直观的印象,Groq到底有多快。 目前Groq只能选Llama的70b,和Mixtral的MoE,那我选7*8的这个MoE模型来实验。 这么好些字大概花了不到1秒,流式响应,其实是不是流式已经没那么重要了 ,然后看每秒Toke…

dbeaver 数据库连接工具使用教程

dbeaver是一款很强大的数据库连接工具&#xff0c;本人之前使用的是navicat&#xff0c;挺好用的&#xff0c;只不过每次激活都要整半天&#xff0c;然后看到了dbeaver这款工具&#xff0c;本着尝试的心态&#xff0c;体验了下&#xff0c;真香。 下面来配置dbeaver 1.下载安…