C语言填坑之有无符号数的转换
1、背景介绍
(1)表达式中符号选取,具有贪心特性,即从左到右,尽量多的匹配符号;
(2)C语言的表达式中,存在隐式类型转换,基本原则为向大数转换,具体说明如下:
- 占用字节数少的类型,向占用字节数多的类型转换;
- 占用字节数相同情况下,有符号向无符号转换;
- 整数类型向浮点类型转换;
- 单精度向双精度转换;
- Char型是否有符号取决于编译器,在运算时会提升为int;
2、无符号数与有符号数的转化过程分析
原理:计算机中的数据,均以二进制方式存储,并且以二进制补码形式存储。;类型转换时,内存中存储的数据位值不变,编译器根据类型改变解释方式。
最高位为符号位,符号位为0时表示正数,符号位为1时表示负数;
存储数值解释为补码,根据符号位决定补码的转换规则,正数的原码、反码、补码相同,负数需要将补码转换成原码(减一取反),获得真值。
示例(4位字长):内部存储为1010
无符号数: 表示数值10。
有符号数:符号位为1,表示负数;存储值为补码,将其转化原码1110,表示数值-6。
3、原码、反码、补码
机器数与真值
(1)机器数
将符号数字化的数,即数字在计算机中的二进制表示形式(仅为表示形式,不是数值在内存中真实二进制存储内容,后文将说明,数据以补码形式实际存储)。
- 特点1:符号数字化。
二进制数的最高位,称符号位,0表示正号,1表示负号。 - 特点2:位数受物理设备的限制。
机器内部运算单元一次能表示的二进制位数叫做机器的字长,一台机器的字长是固定的,由运算器(冯诺依曼结构)的寄存器长度决定。通常说的,32位系统或者64位系统,即表明该系统的字长为32位或64位。8位(bit)字长又叫做一个字节(Byte)。
(2)真值
用“+”和“-”来表示正负号的二进制数叫做符号数的真值。
机器数是数字在机器内部的表示形式,真值是利用数学符号表达的二进制数值。
如:“00001101”和“10001101”是两个机器数(字长八位),其真值分别为+0001101和-0001101。
原码、反码、补码
(1)原码
机器数表示法,最高位表示符号位,其他位存放数值(真值)的绝对值。
原码可以直观地表示数字的数值大小,但是使用原码进行加减法时需要对符号位进行判断,电路设计复杂。为了简化电路设计和运算逻辑,通过引入反码和补码,将减法转换成负数的加法。正数的原码、反码、补码均相同。
如,机器数“00001 101”和“1000 1101”的原码为0000 1101和1000 1101。
(2)反码
正数的反码与原码形式相同;负数的反码,最高位表示符号位,其他位为真值的绝对值取反,即对原码的数值为按位取反。
如,机器数“00001101”和“10001101”的原码为0000 1101和1000 1101,反码为00001101和1111 0010。
(3)补码
正数的补码与原码形式相同;负数的补码,表现为反码加一。
负数补码的快速计算方法:符号位不变,用模值减去补码二进制的数值位,即可得到负数的补码。“补齐”的是该负数的绝对值与模值之间的距离。
如,计算4字长场景下,原码1011的补码:
通常方法(对原码取反加一):[1011]原 = [1100]反 = [1101]补码
快速方法(有效模值 - 原码的数值绝对值):4字长机器,最高位表示符号位,实际有效模值为为2^3,即1000;原码数值部分绝对值011;
有效模值-原码的数值部分绝对值 = 1000 - 011 = 101,加上符号位,则其补码为1101。
补码的理解与应用
(1)理解分析
补码的引入来源于“同余定理”。数学上,两个整数除以同一个整数,若得相同余数,则说两个整数同余。
数学定义如下:两个整数a、b,若它们除以整数m所得的余数相等,则称a与b对于模m同余,记作:a≡b (mod m)。
显然,若a与b对于模M同余,(a+M)与b对模M也同余。
在运算器进行加减法时,寄存器的溢出特性类似于取余操作,寄存器表达的最大数字即为 模 - 1。
假设寄存器位数为n,则模为2^n,记为M,且有正值A和负值-B(B为其绝对值),
(A – B) (mod M)= (A + (-B) + M)(mod M) = (A + (M - B))(mod M) = (A + (-B)补)(mod M)
显然,(-B)补=M-B,即(-B)的补码为模值M减去(-B)真值的绝对值。
可见,在模为M的条件下,A减去B,可以用A加上-B的补码来实现。
(2)反码与补码的关系推导
假设机器字长为n,负数K, 模2^(n-1),
将符号位参与运算,原码与反码的最高位相同,进位后溢出丢弃,有
[K]原 + [K]反 = 2^(n - 1) - 1 (1)
由补码定义,可知
[K]补 = 2^(n) - [K]真值绝对值 (2)
结合(1)、(2)有,
[K]补 - [K]反 = 1 + 2^(n-1) + [K]原 - [K]真值绝对值 (3)
上式的后三项结合后,产生进位,溢出后丢弃,移项可得
[K]补 = [K]反 + 1 (4)
结论:负数补码的计算口诀“取反加一”,仅是有与补码性质与反码性质的“巧合”,通过寄存器的溢出特性更容器理解补码的构造思路。
(3)应用分析
补码引入,可以将加减法统一成加法,并将符号位同时参与运算,不需要要进行判断,极大地简化了电路。以4位字长运算,举例如下:
例1:7 - 5
7的原码、反码、补码相同,为0111。
-5的原码为1101,反码为1010,补码为1011。
在内存中,数据的存储是以补码的形式进行真实存放的,则在加法运算器中,最高位进位后丢弃
7 – 5 = [7]补码 + [-5]补码 = 0111 + 1011 = 1(进位丢弃) 0010 = 2
例2:-2 – 4
-2的原码为1010,反码为1101,补码为1110;
-4的原码为1100,反码为1011,补码为1100;
-2-4 = [-2]补码 + [-4]补码 = 1110 + 1100 = 1(进位丢弃) 1010
运算后的存储结果为1010,此时数据仍是补码形式,求其真值需要先转化成原码,利用“减一取反”的逆向过程求补码的原码,可得[1010]补 = [1100]原 = -6。