补码/反码、零扩展和符号位扩展(Zero extension and Sign extension)-程序员宅基地

技术标签: 零扩展  C/C++  计算机基础  符号位扩展  std::bitset  

众所周知,每种基本数据类型都有一个固定的位数,比如byte占8位,short占16位,int占32位等。正因如此,当把一个低精度的数据类型转成一个高精度的数据类型时,必然会涉及到如何扩展位数的问题。这里有两种解决方案:
(1)补零扩展:填充一定位数的0。
(2)补符号位扩展:填充一定位数的符号位(非负数填充0,负数填充1)。
对于无符号类型(相当于都是非负数)与有符号类型中的非负数部分,这两种方法没有区别,都是填充0;对于有符号类型中的负数部分,这两种方法就会产生差异了,补零扩展会填充0,而补符号位扩展会填充1。下面将byte类型的-127转为int类型为例,探讨一下这两种方法的区别。

 

首先必须明确一些知识点:

  • 计算机是用补码来存储数字的;
  • 一个数的补码的补码等于原码。

反码、补码

首先,我们从位权的含义说起。例如,十进制39的各个数位的数值,并不是简单的3和9,这点大家都知道,3表示的是3x10,9表示的是9x1。这里和各个数位的数值相乘的10和1,就是位权。数字的位数不同,位权也不同。第一位(最右边的一位)是10的0次幂,第二位是10的1次幂....以此类推。

位权的思考方式同样适用于二进制。即第一位是2的0次幂,第二位是2的1次幂.... “OO的XX次幂”表示位权,其中,十进制数的情况下OO部分为10,二进制的情况为2,这个则称为基数

在日常生活当中,可以看到很多这样的事情:

  1. 把某物体左转 90 度,和右转 270 度,在不考虑圈数的条件下,最终的效果是相同的;
  2. 把分针倒拨 20 分钟,和正拨 40 分钟,在不考虑时针的条件下,效果也是相同的;
  3. 把数字 87,减去 25,和加上 75,在不考虑百位数的条件下,效果也是相同的;
  4. ……。

上述几组数字,有这样的关系:

  •   90 + 270 = 360
  •   20 + 40 = 60
  •   25 + 75 = 100

式中的 360、60 和 100,就是“”。
式中的 90 和 270、20 和 40,以及 25 和 75,就是一对对“互补”的数字。

知道了“模”,求某个数字的“补数”,就是轻而易举的了:
如果模为 365,数字 120 的补数为:365 - 120 = 245。

用补数代替原数,可把减法转变为加法。出现的进位就是模,此时的进位,就应该忽略不计。

接下来我们正式引入补数:

二进制数中表示负数值时,一般把最高位作为符号位来使用,符号位为0时表示正数,为1时表示负数。

那么-1用八位二进制来表示的话是怎么样的呢?可能很多人认为1的二进制'0000 0001'(常规思维),因此-1的二进制就是‘1000 0001’。但这个答案是错位的,正确答案是‘1111 1111’(计算机补码形式)。(PS:有些教程可能写0000 0001,它可能非计算机八位二进制补码形式,而查用我们数学表达?而下面使用符号位不变取反也是这批人。)

而计算机里面,只有加法器,没有减法器,所有的减法运算,都必须用加法进行。计算机再做减法运算时,实际上内部是在做加法运算,在表示负数时就需要使用“二进制的补数”补数就是用正数来表示负数,很不可思议吧。

为了获得补数,我们需要将二进制的各数位的数值全部取反,然后再将结果加一。例如,用八位二进制表示-1时,只需求得1,也就是0000 0001得补数即可。具体来说,就是将各位数得0取反得到1,1取反成0,然后将取反得结果为1,最后就转化为了1111 1111。

(ps:国内还有一种算法就是符号位不变,balala.. 按照上面钟表的例子,其实也行的通,但是其似乎偏离了设计者得本意与它得本质)

补码的思考方式,虽然直观上不易理解,但逻辑上非常严谨,例如1-1也就是1+(-1)这一运算,我们都知道答案为0。首先,让我们将-1表示为1000 0001(错误方式,原码)来运算,看看结果如何,0000 0001 + 1000 0001 = 1000 0010,很显然结果不是0。

接下来,我们把-1表示为1111 1111 (补码)来进行运算。 0000 0001 + 1111 1111 = 1 0000 0000 。最高位溢出,对于溢出位,计算机回自动忽略掉。在八位这个范围内计算,1 0000 0000 这个 九位二进制会被认为是 0000 0000 这一八位二进制数。

请牢记“将二进制数的值取反后加一的结果,和原来的值相加,结果为零”这一法则。

那么 1111 1110 表示的负数是多少大家知道吗?这时,我们可以利用负负得正的性质,假若1111 1110是负,那么1111 1110的补数就是正。通过求解补数的补数,就可知道该值的绝对值。1111 1110的补数,取反加1后为0000 0010。这个是2的十进制数,因此1111 1110表示的就是-2。

另外,关于下面网上说法,我不知道其观点的具体含义,我在中文维基百科看到有这样描述,但在英文版似乎没有发现。 这里也不是理解正数的补码与负数的补码这种说法。

  • 正数的补码等于原码; 
  • 负数的补码等于反码+1;

我认为,补码不过是表示负数的一种方式,补码不是相对正数的吗?"正数的补码"有何含义吗?希望有人给我解答其具体含义?

高级程序设计语言允许程序员使用包含不同字节大小整数的对象表达式。

那么,当一个表达式的两个操作数大小不同时,有些语言会报错,有些语言则会自动将操作数转换成一个统一的格式。这种转换是有代价的,因此如果你不希望编译器在你不知情的情况下自动加入各种转换到原本非常完美的代码中,你就需要掌握编译器如何处理这些表达式。

零扩展

在移动或转换操作中,零扩展是指将目标的高位设置为零,而不是将其设置为源的最高有效位的副本。 如果操作的源是无符号数字,则零扩展通常是在保留其数值的同时将其移至更大字段的正确方法,而符号扩展对于有符号数字是正确的。

高位直接补0的扩展,如1111变成00001111,补0并不影响计算结果,这个很好理解,但如果二进制数带了符号,就不一样了,因为最高位是符号位,所以1111就从一个负数,变成了一个正数00001111,由此,产生了符号扩展。

在x86和x64指令集中,movzx指令(“零扩展移动”)执行此功能。 例如,movzx ebx,al将一个字节从al寄存器复制到ebx的低位字节,然后用零填充ebx的其余字节。

在x64上,大多数写入任何通用寄存器的低32位的指令都会将目标寄存器的高一半置零。 例如,指令mov eax,1234将清除rax寄存器的高32位。

符号扩展

符号扩展是计算机算术中在保留数字的符号(正/负)和值的同时增加二进制数的位数的操作。 这是通过根据所使用的特定带符号的数字表示的过程,将数字附加到数字的最高有效位来完成的。

例如,如果使用六位表示数字“ 00 1010”(十进制正数10),并且符号扩展操作将字长增加到16位,则新的表示形式就是“ 0000 0000 0000 1010”。因此,既保持了价值,又保持了价值为正的事实。

如果用10位表示用二进制补码值“1111110001”(十进制负15),并且将其符号扩展为16位,则新表示为“1111 1111 1111 0001 ”。因此,通过在左侧填充ones,可以保持负号和原始编号的值。 1111 1111 1111 0001

例如,在Intel x86指令集中,有两种方式进行符号扩展:

  • 使用指令cbw,cwd,cwde和cdq:分别将字节转换为字,将字转换为双字,将字转换为扩展双字和将双字转换为四字(在x86上下文中,一个字节有8位,一个字有16位,一个双字和扩展的双字32位和四字64位);
  • 使用由movsx(“带符号扩展的移动”)指令系列完成的符号扩展移动之一。

实例

举个例子:

-127原码1111 1111,反码1000 0000,补码1000 0001。计算机存储的是1000 0001,用十六进制表示为0x81。

  • 当使用补零扩展时,结果为:

0000 0000 0000 0000 0000 0000 1000 0001 (与补码数值形式一致)
用十六进制表示为0x81。为了计算十进制值,计算它的补码,结果为:
0000 0000 0000 0000 0000 0000 1000 0001
将这个二进制数转成十进制的结果是129。

  • 当使用补符号位扩展时,结果为:

1111 1111 1111 1111 1111 1111 1000 0001 (和补码数值看上去差别较大)
用十六进制表示为0xFFFFFF81。为了计算十进制值,计算它的补码,结果为:
1000 0000 0000 0000 0000 0000 0111 1111
将这个二进制数转成十进制的结果是-127。

由此可以得出结论:
(1)使用补零扩展能够保证二进制存储的一致性(和我们数学常理一致),但不能保证十进制值不变。所以,处理无符号二进制数的时候,可以使用零扩展(zero extension)将小位数的无符号数扩展到大位数的无符号数
(2)使用补符号位扩展能够保证十进制值不变,但不能保证二进制存储的一致性(负数的补码变了,需要 &0xff)而处理不同长度的有符号数时,我们必须使用符号扩展

在C/C++中,如果把一个char向一个整形转换的时候,就会存在着这个问题。

如果你想得到一个正数,那么如果一个字符的ASCII码值是小于零的,而直接用(int)c进行强制类型转换,结果是通过符号扩展得到的也为一个负数。

要得到正数,一定要用(int)(unsigned char)c;因为unsigned char去除了c的符号位,所以,这样的类型转换后,再用(int)进行转换得到的就是一个正数。

#include <iostream> 
#include <string>
#include <algorithm>
#include <bitset>       

int main()
{

	int i = 129;
	char chA = (char)i;
	int c = (int)(unsigned char)chA;
	int b = (int)chA;

	std::cout << "sign extension: " << b << std::endl;
	std::cout << "zero extension: " << c << std::endl;

    char d = -127;
	std::bitset <sizeof(int) * 8> x(d);   
	std::cout << "sign extension: " << x << std::endl;

	unsigned char e = (d & 0XFF);
	std::bitset <sizeof(int) * 8> y(e);
	std::cout << "sign extension: " << y << std::endl;
	
	return 0;
}

 结果

std::bitset

std::bitset 是 一种 位集存储位(元素只有两个可能的值:0或1 truefalse,...)。

  • bitset存储二进制数位。
  • bitset就像一个bool类型的数组一样,但是有空间优化——bitset中的一个元素一般只占1 bit(在大多数系统上,相当于一个char元素所占空间的八分之一,一个char占用一个字节byte,8位bits)
  • bitset中的每个元素都能单独被访问,例如对于一个叫做foo的bitset,表达式foo[3]访问了它的第4个元素,就像访问了数组其元素一样。但是,因为在大多数C ++环境中没有元素类型是单个位,所以可以将单个元素作为特殊引用类型进行访问(请参见bitset :: reference)。
  • bitset具有可以从整数值和二进制字符串构造并转换为整数值的功能(请参阅其构造函数和成员to_ulong和to_string)。它们也可以直接以二进制格式插入和从流中提取(请参阅适用的运算符)。
  • bitset的大小在编译时就需要确定(由其模板参数确定)。有关还可优化空间分配并允许动态调整大小的类,请参见vector的布尔特殊化(vector <bool>)。

同一长度数据类型中,有符号数与无符号数相互转换

直接将内存中的数据赋给要转化的类型,数值大小则会发生变化。另外,短类型扩展为长类型时,短类型与长类型分属有符号数与无符号数时,则先按规则一进行类型的扩展,再按本规则直接将内存中的数值原封不动的赋给对方。以下是有符号数与无符号数之间的转换:

有符号数的转换
 

无符号数的转换 

参考:

关于补零扩展与补符号位扩展_swt369的博客-程序员宅基地_无符号short型存储后面补零

C++ 中注意,零扩展和符号位扩展_jaylong35的专栏-程序员宅基地_符号位扩展

符号扩展,零扩展与符号缩减

《程序是怎样跑起来的》 - 矢泽久雄 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_40539125/article/details/103058420

智能推荐

PIVOT和UNPIVOT使用详解_pivot unpivot-程序员宅基地

文章浏览阅读1.4w次。一、使用PIVOT和UNPIVOT命令的SQL Server版本要求1.数据库的最低版本要求为SQL Server 2005 或更高。2.必须将数据库的兼容级别设置为90 或更高。3.查看我的数据库版本及兼容级别。如果不知道怎么看数据库版本或兼容级别的话可以在SQL Server Management Studio新建一个查询窗口输入:print @@version_pivot unpivot

luoguP2838 瓶子国的故事——倍增_luogu倍增-程序员宅基地

文章浏览阅读219次。算法标签:肝法_luogu倍增

PostMan安装使用教程(非常详细)从零基础入门到精通,看完这一篇就够了_postman工具-程序员宅基地

文章浏览阅读3.1k次。为了验证接口能否被正常访问,我们常常需要使用测试工具,来对数据接口进行检测。好处:接口测试工具能让我们在不写任何代码的情况下,对接口进行调用和调试。_postman工具

30天自制操作系统-导入c语言_c语言自制系统-程序员宅基地

文章浏览阅读733次。0.准备 换了一个32G的u盘,不过没关系按照之前的博客30天自制操作系统-Hello OS填写fat32文件格式。对于每一个u盘,要注意逻辑扇区和具体物理扇区的关系:选定59904扇区作为写入磁盘数据的起始,对应的柱面和磁头和扇区写入ipl10.nas然后要更改asmhead.nas。该文件的作用将从保护模式跳至实模式,具体的可以看该博客《30天自制操作系统》学习..._c语言自制系统

cadence allegro原理图DRC,生成网表与导入PCB_如何生成drc-程序员宅基地

文章浏览阅读2.2w次,点赞30次,收藏169次。前言  allegro的原理图设计和PCB设计用的是两款软件。而连接两款软件的桥梁是一种叫网表(netlist)的东西。网表记录了原理图中所以的元器件,元器件封装以及网络连接。原理图规则检查(DRC)  在生成网表之前肯定需要一个完全正确无误的原理图,因此先对原理图进行规则检查。  回到原理图根目录界面,选中原理图文件  点击Tools ->Design rule check,弹..._如何生成drc

操作系统学习01-程序员宅基地

文章浏览阅读582次。操作系统学习_操作系统学习

随便推点

mui.css 滚动条消失 导致超出部分无法显示 overflow属性_mui 滚动条消失-程序员宅基地

文章浏览阅读405次。导入mui class属性mui-scroll-wrapper后,发现滚动条消失,页面无法滚动导致超出页面部分无显示.查看 class = "mui-scroll-wrapper"带来的样式,其中有个overflow= hidden去掉这个样式,发现滚动条就出现,可以向下滑动显示 下面的内容这是mui.css 默认样式所以再写一个overflow 覆盖 mui.css的默认样式演示:但是我发现 , 只要不是hidden , 其他的都可以实现.就要查 overflow的用法了:overfl_mui 滚动条消失

【ART-Pi与RT-Thread入门】⑤ART-Pi配置PWM设备(避坑指南,已验证)-程序员宅基地

文章浏览阅读1.8k次,点赞6次,收藏12次。文章目录开发环境创建项目步骤1:RT-Thread Studio项目设置步骤2:打开board.h宏定义步骤3:STM32CubeMX(或者STM32CubeIDE)配置3.0 新建基于STM32H750XBHx的项目。3.1 在Pinout view中配置PI5为TIM8_CH13.2 配置时钟3.3 RCC中配置使用外部高速晶振HSE3.4 TIM8配置CH13.5 设置代码输出选项3.6 点击Generate Code4. 修改board.c和board.h5. 修改main.c6. 避坑指南开发环_art-pi

在CentOS服务器上安装Tesseract完整版,附带解决错误的办法,Java程序进行图像识别_could not initialize class net.sourceforge.tess4j.-程序员宅基地

文章浏览阅读2k次。安装安装gcc、gcc-c++、make,如果有就不需要安装:yum install gcc gcc-c++ make 安装编译相关工具,没有的话编译时候可能报错:yum install autoconf automake libtool 安装对图片识别相关支持工具,没有这些在后续执行Tesseract命令时会报错,可以尝试一下:yum install libjpeg-devel l..._could not initialize class net.sourceforge.tess4j.tessapi at net.sourceforge

#MySQL各种bug汇总#_mysql bug 53352-程序员宅基地

文章浏览阅读2.6k次。目录1.MySQL: Host '127.0.0.1' is not allowed to connect to this MySQL server2.The MySQL server is running with the--skip-grant-tables option3.MySQL——修改root密码的4种方法(以windows为例)4."Host 'localhost' ..._mysql bug 53352

win10电脑显示网络未连接到服务器,教你win10电脑网络连接显示未连接不可用的方法...-程序员宅基地

文章浏览阅读7.3k次,点赞4次,收藏13次。win10电脑使用时间久了,会出现各种各样的故障问题,最常见属于网络问题。近期一位用户说电脑莫名其妙无法识别网络,桌面右下角提示“连接不可用”,无法上网是一个比较烦人...下面本站小编介绍下使用方法,希望大家喜欢!1.我们点击右下角的无线网络,打开网络和共享中心。2.点击“更改适配器设置”。3.在“网络连接”窗口,如果网络连接里面有无线网络连接是灰色情况,我们就右键点击“启用”。4.当网络连接里面..._未连接连接不可用

Mysql join大表优化案例_mysql left join 大表-程序员宅基地

文章浏览阅读4.3k次,点赞2次,收藏11次。Mysql join查询的相关原理,实现,由此推出的优化策略;join大表后进行groupby操作慢sql通过临时表+join进行优化_mysql left join 大表

推荐文章

热门文章

相关标签