引言
最近我将自己平时工作中记录的一些问题点进行一个复盘或者说进行一个学习记录。今天记录的这个知识点,已经距离我工作入职第一次答辩5个月不到的时间了,这个点上,我依稀记得领导问的问题,出了一个C语言知识考,就是关于无符号数和有符号数的比较问题。下面就记录一下这个知识点。
定义
在计算机中,数值类型分为整数型或实数型,其中整型又分为无符类型或有符类型,而实型则只有符类型。 字符类型也分为有符和无符类型。
- 有符号数:就是区分正负号的数呗,带+、-。如+5,-6;char型数据范围为:-127~+127
- 无符号数:不区分正负号的数,没有符号位一说。char型数据范围为:0~255;
基本概念理解
原码
这个数的绝对值转换成对应的二进制数。(0和1构成的值)
- 对于正整数而言,取符号位为 0 ,后面直接加上真值的二进制表示即可
- 对于负整数而言,取符号位为 1,后面直接加上真值的绝对值的二进制表示即可
反码
将原码进行一位一位的取反得到的值(仅限于负数)。
需要注意如果是有符号的数,这里取反符号位不参与取反操作。
正数的反码为原码,负数的反码是原码符号位外按位取反。
补码
反码加1称为补码(仅限于负数)。
介绍一下负数补码的计算的方法:负数的补码 = 模 - |该数的绝对值|。
这里模实际上就是一个最大的值,如8位的数据模就是2^8=256;
机器数
保存在计算机中的数字我们称为机器数,在计算机中用0或1 表示数字的符号 。也就是0和1组成的数。计算机并不会去关心它是什么类型的数,也没得符号一说。
真值
实际我们表示的数值,可以带符号,也可以不带符号,具体一定的数学意义。
举例理解说明
<pre data-language="c" id="GIc2F" class="ne-codeblock language-c" style="border: 1px solid #e8e8e8; border-radius: 2px; background: #f9f9f9; padding: 16px; font-size: 13px; color: #595959">例如针对有符号数的±2内存存储形式为
+2:0000 0010(原码)
+2: 0000 0010(反码)
+2: 0000 0010(补码)
-2: 1000 0010(原码)
-2: 1111 1101(反码)
-2: 1111 1110(补码)
转换的核心:
正数的原码、反码、补码都是同一个!
负数的原码取出最高位为符号位1外,其余位按照其绝对值进行转换;
负数的反码等于除符号位外其余进行取反;
负数的补码等于反码加1!!
储存方式
我们需要明确一点,计算机内部对数据或者代码来说,最终都是以二进制数0和1存在计算机储存单元中的。对于数据来说,那对于正数和负数是如何区分的,我们需要思考,因为像上述的 1000 0010,在不清楚它怎么储存的,这个数可以表示是无符号数130,或者是有符号数-2,那就会产生这种分歧?
计算机中储存运算数据都是以补码的形式储存的!!!不用关心它是啥类型的数据,实际上,计算机本质上也是不会区分有符号和无符号的,都是一样的,对于计算机来说。
举例说明:
单纯从一个字节8位二进制存储上来看,1111 1111 既可以表示有符号的-1又可以表示无符号的255。计算机里面就是这种理解,当然实际是有无符号还需要我们如何去定义的类型。
大小比较
先看一下如果将一个有符号数和无符号数进行比较会产生什么结果?(领导考我的题)
#include<stdio.h>
void main()
{
unsigned int a = 2;
int b = -5;
if(a > b )
{
printf("a > b, a = %u, b = %d\n", a, b);
}
else
{
printf("a <= b, a = %u, b = %d\n", a, b);
}
}
我们直观从数学的角度看,结果肯定是会是
a > b, a = 2, b = -5
但是运行后结果为:
为啥嘞,这个就是我们容易忽视的一个细节,简单分析,根据上述铺垫的基础知识。
在C语言中,进行混合运算时,编译系统遵循一定规则,完成数据类型的自动转换,即隐式类型转换。一般是占用内存字节数少类型,向占用内存字节数多的类型进行转换,(这里是指数值大小)以防止精度丢失。
unsigned int与int相比,拥有更大的正数范围。所以,无符号数与有符号数进行运算时,先需要将有符号数转化为无符号数进行操作。
需要注意一点:处理同样字长的有符号数和无符号数之间的相互转换的一般规则是:数值可能会改变,但是位模式不变。也就是说,将unsigned int强制类型转换成int,或将int转换成unsigned int底层的位表示保持不变。
在上述例子中:
a=2(变量a的值:0000 0000 0000 0000 0000 0000 0000 0010)值为2;
b=-5(变量b的值: 1111 1111 1111 1111 1111 1111 1111 1011)这个有符号数强制转换成无符号数
(1111 1111 1111 1111 1111 1111 1111 1011= 4,294,967,291,
从二进制来看,4,294,967,291>2,故b>a;
再次验证一下上述的结论:
#include<stdio.h>
//C语言中没有直接输出二进制的定义,如果要输出二进制数需要我们自己定义输出,下面输出十六进制数
void Display_Hex(unsigned char *date, int leng)
{
int i;
for(i=0; i<leng; ++i)
{
printf("%.2x ",date[i]);
}
printf("\n\r");
}
void main()
{
unsigned int a = 4294967291; //这个值如果是看成有符号的已经大于了最大值
int b = -5;
printf(" a = %d, a = %u \n",a,a);
printf(" b = %d, b = %u \n",b,b);
Display_Hex((unsigned char*)&a, sizeof(a));//打印出a的十六进制数
Display_Hex((unsigned char*)&a, sizeof(b));//打印出b的十六进制数
}</pre>
运行结果:
可以看出来有符号的数4294967291和-5储存上是一样的,这也解释了上面进行比较时-5被转换成了无符号数参与了比较,所以结果不是我们直观理解的那种。
总结
通过上面的论证和这个问题的产生,那总结一下。
无符号数和有符号数不进行大小比较
类型不同的数据进行数学运算需要注意合理性,特别是减法哦,无符号的减需谨慎。
实际上,这里的数据大小情况,可以类比坐标系中的360度角度问题一样,-30度和330度是一样的位置。
对于无符号数来说,数值达到最大值后,超了那就从0又开始数了;
对于有符号数来说,数值达到最大值后,超了那就从-(最大值)开始数了;
欢迎关注本人微信公众号:那个混子
记录自己学习的过程,分享乐趣、技术、想法、感悟、情感!
单片机类嵌入式交流学习可加企鹅群:120653336