深入理解计算机系统(2.5)------C语言中的有符号数和无符号数以及扩展和截断数字

  上一篇博客我们讲解了计算机中整数的表示,包括无符号编码和补码编码,以及它们之间的互相转换,个人觉得那是非常重要的知识要点。这篇博客我们将介绍C语言中的有符号数和无符号数以及扩展和截断数字。

1、C语言中的有符号数和无符号数

  上一篇博客我们给出了C语言中在32位机器和64位机器中支持的整型类型数据,我们这里只给出32位机器上的:

  尽管 C 语言标准没有指定有符号数要采用某种编码表示,但是几乎所有的机器都使用补码。通常大多数数字是默认有符号的,比如当声明一个像12345或者0xABC这样的常量的时候,这个值就被认为是有符号的。

  C 语言允许有符号数和无符号数之间的转换。在一台采用补码的机器上:

①、无符号数转换成有符号数

②、有符号数转换成无符号数

  我们可以看下面这个程序:

#include <stdio.h>

int main()
{
	char t = 0xFF;
    //%d把对应的整数按有符号十进制输出,%u把对应的整数按无符号十进制输出
    //有符号的转换成无符号的 
    printf("t=%d,t2u=%u\n",t,(unsigned char)t);
 
 	unsigned char u = 0xFF;
    //%d无符号转换成有符号的 
    printf("u=%u,u2t=%d\n",u,(char)u);
	return 0;
}

  结果为:

  为什么是这个结果,我在上一篇博客:深入理解计算机系统(2.4)------整数的表示(无符号编码和补码编码)已经讲过了,这就是数据类型的强制转换

  还有第二种情况是当一种类型的表达式被赋值给另一种类型的变量时,转换是隐式的。比如:

#include <stdio.h>

int main()
{
	unsigned char u = 0xFF;
 	char t = u;
    //%d无符号转换成有符号的是默认的 
    printf("u=%u,u2t=%d\n",u,t);
	return 0;
}

  结果是:

  我们将一个无符号的数赋值给有符号的,其转换是隐式的发生的。这对于标准的运算来说并无差异,但是对于像 < 和 > 这样的关系运算来说,会导致错误的结果。

注意:在 C 语言中,当执行一个运算,会隐式的将有符号参数强转为无符号参数。

#include <stdio.h>

int main()
{
    printf("%d\n",-1<0u);//结果是0,0表示错误 1表示正确 
    printf("%d\n",-123<123u);//0 
	return 0;
}

  我们解释第一个 -1 < 0u 为什么是错误的。因为0u是无符号的,-1是有符号的。那么-1就会被转换成无符号的。

  也就是T2Uw(-1)=-1+232=4 294 967 296-1=4 294 967 295

  那么 -1 < 0u 表达式也就变成了 4 294 967 295u < 0u ,结果当然是错误的。第二个例子我们也可以这样分析,这里就不详细描述了。

  所以我们要注意实际编码过程中由于隐式转换所造成的错误运算。

2、扩展一个数字的位表示

  扩展一个数字的位,简单来说就是在不同字长的整数之间转换,而这种转换我们可以需要保持前后数值不变。当然将一个数据转换为字长更小的数据类型的时候,它的值肯定会发生变化。那么我们只能将较小的数据类型转换为较大的数据类型。比如将短整型short int 转换为整型 int。,那该怎么办呢?

  ①、零扩展

    将一个无符号数转换为一个更大的数据类型,我们只需要简单的在二进制序列前面添加 0 即可。

  ②、符号位扩展

    将一个补码数字转换为一个更大的数据类型,我们需要在开头添加符号位。

  由上面两条我们可以总结:如果我们原始位为[xw-1 , xw-2 , … , x2 , x1 , x0],那么扩展后就可以表示为:[xw-1 ,xw-1 ,...,xw-1 , xw-2 , … , x2 , x1 , x0]。

  即我们想证明:

  在表达式的左边,我们增加了 k 位的xw-1副本。如果我们证明符号位扩展一位,即 k=1,而值是保持不变的。那么对于任意的k都能保持这种属性。那么等式变为:

    由于无符号的,添加0,这很好理解,前后数值不变。那么我们证明有符号的补码编码:

  由于:

  将上面的补码编码替换等式右边,即:

  上面的证明我们只需要知道:2w-2w-1=2w-1 即很好理解了。

3、截断数字

  这和上面的扩展刚好相反。即我们不需要额外的扩展一个数的位,而是减少一个数字的位数。

将一个 w 位的数 [xw-1 , xw-2 , … , x2 , x1 , x0] 截断为一个 k 位数字时,我们会丢弃高 w-k 位。得到 [xk-1 , xk-2 , … , x2 , x1 , x0]

  对于无符号截断公式为:

  证明过程如下:

   而对于有符号(补码编码)的截断,我们只需要多加一步,将无符号编码转换为补码编码就可以了。

  比如下面这个程序:

#include <stdio.h>

int main()
{
	int i = 53191;
	short int j = (short)i; 
	int k = j;
    printf("%d %d %d\n",i,j,k);
	return 0;
}

  结果为:

  我们将 i 强转为 short int,在 64位机器上,就是将 32 位的 int 截断为 16 位的short int,这个16位的位模式就是 -12345 的补码表示。当我们把它强转为 int 时,符号位扩展把高 16 位设置为 1,从而生成 -12345 的32 位补码表示。

4、总结

  本篇博客讲解了 C 语言中的有符号数和无符号数,以及扩展和截断一个数值是如何进行的,理解它们的原理是十分必要的。

  我们从上面已经看到了许多无符号运算的特殊性,尤其是有符号数到无符号数的隐式转换会导致错误。而避免这类错误的方法是不使用无符号数。实际上,除了 C 语言,很少有语言支持无符号数。比如 Java只支持整型数据,并且要求补码运算。

  那么计算机中整数的表示就已经讲完了,下篇博客将会讲解计算机中整数的运算,我们出现的两个数运算会产生莫名其妙的结果在下一篇博客会得到解答。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

字符串的方法汇总

name="aBcababc" #计算文本字符个数 print(len(name)) #统计a出现的次数 print(name.count('a',1,-1))...

1805
来自专栏CDA数据分析师

学会这8个(组)excel函数,轻松解决工作中80%的难题

文 | 兰色幻想-赵志东 函数是excel中最重要的分析工具,面对400多个excel函数新手应该从哪里入手呢?下面是实际工作中最常用的8个(组)函数,学会后工...

1787
来自专栏柠檬先生

常用正则表达式

一, 1.^\d+$    //匹配非负整数(正整数 + 0) ---^:以数字开头 +:之前紧邻出现的一次或多次 2.[0-9]*[1-9][0-9]*$...

18710
来自专栏阿凯的Excel

【虐心】统计符合条件的不重复单元格个数

昨天有个网友在公众号留言问我~ 统计符合B列条件的A列不重复的计数(多个重复算一个) 我读了两边,领悟了他的问题,就是统计符合条件的另外一列的不重复单元格个数...

3824
来自专栏生信技能树

用 Excel 怎么了,你咬我啊?

伪题图:逼死强迫症之重新加载。下图为真题图 ? 2400字,约6分钟,思考问题的熊 专栏6 懒是人类进步的绊脚石,偷懒是人类进步的阶梯。如果你完成任何一项工作心...

3597
来自专栏机器之心

入门 | 一文带你了解Python集合与基本的集合运算

了解 Python 集合: 它们是什么,如何创建它们,何时使用它们,什么是内置函数,以及它们与集合论操作的关系

963
来自专栏柠檬先生

css3 RGBA 红色R+绿色G+蓝色B+Alpha通道

语法:   R:红色值。正整数 | 百分数   G:绿色值。正整数 | 百分数   B:蓝色值。正整数| 百分数   A:透明度。取值0~1之间...

18110
来自专栏数值分析与有限元编程

Fortran知识 | tiny函数

Fortran中的tiny函数tiny(x)表示查询x的最小正值,x所能表示的最小的数,近似于0。tiny这个函数的参数,只与类型有关。两个数之间的差,不可能比...

2724
来自专栏工科狗和生物喵

【编程能力不行?那就写啊!】二叉索引树

本文直接借鉴下面的博客进行补充: 区间信息的维护与查询(一)——二叉索引树(Fenwick树、树状数组)

1006
来自专栏大数据和云计算技术

算法基础7:平衡查找树概述

算法是基础,小蓝同学准备些总结一系列算法分享给大家,这是第7篇《平衡查找树概述》,非常赞!希望对大家有帮助,大家会喜欢! 前面系列文章: 归并排序 #算法...

3189

扫码关注云+社区