负整数为什么存成补码?

——理解补码的正确姿势

一、计算机为什么对负整数使用补码的形式存储

  出于简化计算机基本电路的考虑,让加减法都只需要用加法电路实现。所以需要把减去一个正数或加上一个负数都用加上一个正数的方式来表示,于是在存储的时候,负数被直接存储成一种可以直接当成正数加的形式,这种形式就是补码

二、那么补码具体是什么?补码是怎么做到加一个数跟减另一个数一样效果的?

1. 先从时钟这个身边的例子理解 “加一个数跟减另一个数一样效果”
  假设你对钟的时候如果发现它是6点,但实际上现在是2点,也就是它走快了4个小时,你可以有两种做法进行校正,一种是逆时针拨回4个小时到2点,另一种是顺时针拨6个小时到12点然后再拨2小时,也就是顺时针拨8个小时。也就是对于时钟的表盘来说,-4 = +8,同样还会有 -1 = +11,-5 = +7,甚至还有 -4 = +8 = +20 = +32 = -16
  他们间隐藏了什么规律呢?在数学中,-4、+8、+20、+32、-16可以归为符合某个条件的同一类数字——对于模12同余。wiki上对于模的定义是 “两个整数a、b,若它们除以正整数m所得的余数相等,则称a、b对于模m同余”
  而在一个可溢出计数系统中,把计数系统容量作为模,那么所有对此模同余的数在此计数系统中都会有同样的表示(加这个数也一样)。比如时钟表盘就是一个可溢出计数系统,模为12;一个n位二进制构成的计数系统中,因为会舍弃溢出的高位,所以也是一个可溢出的计数系统,模为2^n(从0数到2^n-1)
  所以假设一个3位二进制构成的模为8的计数系统,-2,-10,6,14都表示同样的数,也就是减10和加14是一样的效果

2. 引出“补码”
  为了让“补码”实现 “加一个正数跟加一个负数(减一个正数) 一样的效果”,“补码”就可以是跟原负数对于模同余的正数
  在计算机中为了减少不必要的运算,负数的“补码”就取其中最小的正数,正数直接就是它自己(而且如果负得太多,补一个模都不是正数,那其实算是左边越界溢出)
  可能是通过原码求“补码”就是一个补模运算,所以把它叫做“补码”
  (但要注意,这里的“补码”都被我打上了双引号,因为这还不是计算机里真正的存储的补码形式,它应该叫补数,不过相信我,已经差不多了)

三、但这种“补码”表示还有问题

  通过转换成“补码”,减一个数确实变成加一个数了,看似很不错,但却有一个明显的问题,那就是数本身的符号丢失了
  我们只存储了一个在加法运算中方便运算的负数(甚至在结果为负数时计算结果都会不正确),但它却不是一个能正常表示自己的负数(无法逆运算回去)
  比如3位二进制,正常能表示0~7,使用补码法能进一步表示 -8~-1(甚至更多) 的运算,但不能真正表示 -8~-1

四、怎么完美解决“补码”的正负表示问题?

  不知道人们是怎么想到的,但最后我们看到的这种做法,是真挺完美的。只需要在左边加一个二进制位来表示正负,就能同时实现这几个效果:

  1. 在保持补码特性的前提下。也就是减一个数还是照样变成加一个数
  2. 增加正负的表示。能真正表示 -8~-1了,就只用看符号位是0还是1
  3. 还能让运算时不用另外区分符号位,直接把符号位当成值位进行运算,而结果的正负号自然会符合这个正负表示法(也就是符号位的进位和值位的进位都会自然地合理)(有一种理解方式是,把这个负号1当成减一个模)

  具体来说是在左边加一个符号位,这个符号位不参与“补码”的运算,始终表示数的正负,然后在加法运算中可以跟值位一样参与运算,加了一个这样的符号位的“补码”就是真正计算机中存储的补码了

五、所以最后总结一下正常人怎么求补码
  正数不变;对负数求最小正同余数(模为二进制值位能存储的数的数量),把它们放入值位,把符号位置为1

六、然后补充一下计算机中求补码的技巧

  对于计算机来说,上面这个过程有点繁琐,尤其是需要先求模,然后求最小正同余数,而且这个过程是非常基础的过程,一点点效率提升都能起到非常大的效果
  所以这个过程可以被优化成这样:先直接把负数的绝对值存到值位,符号位为负,然后对值位取反+1,就得到了补码(这也是很多教材里告诉我们补码怎么求的方法。。。)
  能看到其实优化的是求“补码”的过程:值位取反加一 = 负数的最小正同余数,下面可以证明一下:

  1. 设一个没有溢出的负数F可以用3位二进制值位表示为:F = -( a*2^2 + b*2^1 + c)(a,b,c ∈ {0,1})
  2. 对F的值位取反 :F(反) = (1-a)*2^2 + (1-b)*2^1 + 1-c = 2^2 + 2^1 +1 - ( a*2^2 + b*2^1 + c ) = 2^3 -1 + F
  3. 然后3位二进制的模等于2^3,所以F的补码 F(补) = F + 2^3
  4. 所以结果就出来了: F(反) = 2^3 -1 + F = F(补) -1 -> F(补) = F(反)+1

不知道为啥目前还没看到解释得这么好理解的补码相关内容,还请博友批评指正~

时间: 2024-10-18 07:56:25

负整数为什么存成补码?的相关文章

个人觉得存成char(12),优于varchar(12)

w 延展一点:0----(还是上边的url),varchar(10)则数据库的存储1-11bytes,而不是0-10bytes;varchar(256)则为2-258bytes; 1----如果待入库string长度为0-12(相对较小,或者说推测或历史记录中大概率为11,12),个人觉得存成char(12),优于varchar(12)(最初设计为或修改为);2-对于入库的string长度过滤和未过滤下的db自处理,是另外的话题了: https://dev.mysql.com/doc/refma

视频采集,存成avi

视频采集,存成aviunit Unit1; interface uses  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,   Dialogs, ExtCtrls, StdCtrls; type  TForm1 = class(TForm)    Panel1: TPanel;    OpenVideo: TButton;    CloseVideo: TButton;    GrabFrame: TButton;

IT痴汉的工作现状40-将其存成pdf,永久保存

学习Android最好的教材莫过于Google提供的SDK文档.伟仔从Android1.5一路走来,伴随着几个重要版本的更迭与变迁,感受着一个移动系统从青涩到成熟的苦涩成长经历.是的,从出生牛犊不怕虎直到想成就霸业的野心,这个绿色小机器人快速的成长着,成长着. 随着版本的越来越高,文档的也越来越齐备.但是,这个系统也越来越庞大,学习曲线也逐渐陡峭.由于Java的普及性,Android的学习曲线是线性升高的,门槛低,精通难.这是伟仔5年多Android开发经历给出的评价. 与不懂技术的经理沟通,有

练习1:将一个6*6数组的第一行,第六行,主对角线和副对角线上的元素都存1,其他元素都存成-1,不需使用初始化,尽量少使用循环

暂时只想到最简单的两层循环实现,留待后看,慢慢优化: 1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 int arr[6][6] = {0}; 6 7 for (int i = 0; i < 6; i++) 8 { 9 for (int j = 0; j < 6; j++) 10 { 11 if (i == 0 || i == 5) 12 { 13 arr[i][j] = 1; 14 } 15 else

[C++][Office] Excel 增益集自订另存新档及存成XPS、PDF问题

摘要:[C++][Office] Excel 增益集自订另存新档 在 Excel 增益集当中我们需要取得存档之后的文件名,所以在 WorkbookBeforeSave 事件当中进行尝试. 因为这个事件在存档前就已经触发了,无法取得使用者到底存成了什么文件名,所以我们取消掉 Office 自己调用的 SaveAs Dialog,让我们自行调用,这样就可以让使用者在我们自己产生的 Dialog 中存档,进行后续动作. 调用 SaveAsDialog 的方式有两种,各有优缺 如何自行调用 SaveAs

如何在将Datagrid的数据存成Excel列出

摘要:如何在将Datagrid的数据存成Excel列出 按下button产生Excel档,(好像是直接下载)将你DataGrid的html输出到HtmlText而HtmlText使用StringWriter的数据 Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click Dim sw As New System.IO.StringWriter Dim

[C#]透过ReportViewer将报表另存成文件

[SQL Server]透过ReportViewer将报表另存成文件 以Reporting Service作为系统的报表方案的各位应该都会有需要将报表转成实例档的机会,在面的文章中我们有看到可以直接在Report操作画面上进行报表导出的动作,但这样的动作毕竟需要开启报表档,再按下导出,如果今天我们希望程序执行到一半,可以自动将报表转成文件后导出到Client端,又或者当成Mail的附件直接寄送给主管当成周报表或者月报表的参考数据,岂不方便的多. Reporting Service的报表布署有两种

pandas将csv文件存成文本

参考:1.https://blog.csdn.net/toshibahuai/article/details/79034829 2.https://blog.csdn.net/lanyang123456/article/details/55804982 1.可以利用to_csv方法将csv存为文本格式,并指定分隔符.需要注意的是,在写入后,如报错 ValueError: invalid literal for int() with base 10: '8.0' a = int(val) 改为 a

使用python获得git中分支存成list

通过这个搜集git工程下的branch信息例子,来说明一下python和终端的信息交互,和字符串的简单处理.代码如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import os import subprocess def get_branches(project_dir):     try:         os.chdir(project_dir)        #转到工程路径下     except Exception,error: