更多课程 选择中心

C/C++培训
达内IT学院

400-111-8989

C语言值的表示形式知识点

  • 发布:C++培训
  • 来源:学习笔记
  • 时间:2017-07-07 16:44

1. 计算机的数据单位

在计算机中,常用的数据单位有位、字节、半字和字,微处理器根据位数的不同支持8位字节、16位半字或32位字的数据类型。

  • 位(bit):它是一个二进制数的位,位是计算机数据的最小单位,一个位只有0和1 两种状态。为了表示更多的信息,必须将更多位组合起来使用,比如,两位二进制数就有00、01、10、11四种状态,依此类推。

  • 字节(Byte)::一个8位二进制数称为一个字节,即1B=8bit,那么一个字节就可以表示0-255种状态或十六进制0~FF之间的数,8位微处理器的数据是以字节方式存储的。

  • l半字:从偶数地址开始连续的2个字节构成一个半字,半字的数据类型为2个连续的字节,有些32位微处理器的数据是以半字方式存储的,比如,32位ARM微处理器支持的Thumb指令的长度刚好是一个半字。

  • 字:以能被4整除的地址开始的连续的4个字节构成1个字,字的数据类型为4个连续的字节,32位微处理器的数据全部支持以字方式存储的格式。

2. 负数的表示法

当需要带符号的signed整数时,虽然可以单独指定某位为1表示“符号”,但计算机不会简单地将“符号位”加到一个数上去。计算机如何存储负数?

由于计算机中无减法器,那么最好的方法就是将减法也通过加法器来完成。为了解决负数在计算机中的存储问题,这里有必要引入补码的概念。可以举个例子说明一下,指针式钟表,假如要将时针从5点拔到2点,有2种拔法,一种是逆时针拔3个时格,相当于5减3等于2;另一种拔法是顺时针拔9个时格,相当于5加9也等于2,即对于这种模为12计数制来说,9和3互补,9是3的补码,反之亦然。对于刚才时钟的拔法可以写出如下算式:

5-3 = 5-(12-9) = 5+9-12 = 2

由此可见,既然补码的概念是为了方便减法运算而引入的,那么不妨约定:其最高位为符号位。也就是说,其最高有效位的数字具有不同的“权值”,当最高有效位为0时,其权值为2n-1,否则其权值为-2n-1。比如,当一个8位二进制数10110111被解释为一个无符号数数时,那么其十进制数的多项式求值结果为:

(10110111)2  = 1×(27)+1×(25)+1×(24) +1×(22) +1×(21) +1×(20) = (183)10

如果将(10110111)2解释为一个带符号数时,则其十进制数的多项式求值结果为:

(10110111)2  = 1×(-27)+1×(25)+1×(24) +1×(22) +1×(21) +1×(20) = (-73)10

当约定用最高有效位作为符号位来确定singned整数后,即可使用“补码”将符号位和其它位统一处理,那么减法也就可以当作加法来处理了。求负数补码的规则如下:

当一个n位二进制数的原码为N时,它的补码定义为(N)补=2n-N

 当一个8位二进制数的原码为1时,其补码定义为 (28-1)10 = (255)10 = (1111 1111)2,即将该数绝对值原码所有为取反,然后将结果加1,其计算过程为:

(-1)补 = (11111110+1)2 = (11111111)2

当用补码表示两个数相加时,如果最高位(符号位)有进位,则进位位被舍弃。正数的补码与其原码一样,而负数的补码,其符号位为1,将该数绝对值原码的所有位取反,然后将结果加1。由此可见,在计算机中数值一律用补码表示(存储),比如,计算8位二进制数减法(图 1.5):

(58-39)10 =(00111010-00100111)2 =(00111010+11011001)2 =(00010011)2 =(19)10

3. 溢出

当一个n位二进制数用于表示unsigned数时,其可能的取值范围为0~2n-1,比如,一个8位数的范围在0~28-1(0~255)之间。假设在一个8位unsigned数248上加10,那么需要9位才能存储正确的结果258,而正确结果258与实际结果2(最低8位有效位)之间的差(256)对应的第9位被丢弃了,因此它不能存储在结果中。

当用于表示signed数时,则只有一半用于正值,一半用于负值,因此n位二进制数的补码带符号数的整个取值范围为-2n~2n-1,即一个8位二进制数可以保存一个范围在-27~27-1(-128~127)之间的带符号值。因此对于signed数,当加上具有不同符号的数或减去具有相同符号的数时,将永远不会溢出。如果将两个具有相同符号的整数相加,或将两个具有不同符号的整数相减时,将可能发生溢出。比如,从一个8位signed整数-120中减去20:

((-120) + (-20))10 = (10001000 +11101100)2 = (01110100)2 = (116)10

而其正确的结果-140需要9位才能存储,因此正确结果-140与实际结果116(最低8位有效位)之间的差(256)对应的第9位被丢弃了,它不能存储在结果中。由于整数数据类型的溢出是悄悄发生的,因此一定要注意每个变量可能的取值范围。

4. 定点数与浮点数

(1)定点格式

下面将从整数的二进制表示开始,这里的整数被数学家称为“自然数”,即计算机程序员口中的“正整数”。此外,数学家还定义了用两个整数的比值表示的一类数,称为有理数或。比如,3/4是一个有理数,也可以将3/4表示为十进制小数0.75。尽管可以将它写成十进制数的形式,但它实际上代表一个分数,即75/100。

在十进制数字系统中,虽然小数点左边的数的每一位都和10的正整数次幂相关,其右边的数的每一位都和10的负整数次幂相关,但还是有一些有理数很难表示为小数,最明显的例子是1/3,其结果为0.333333333...在小数点后面有无穷个3。尽管如此,将1/3表示为小数还是不方便,但它毕竟是一个有理数,因为在本质上它是两个整数的比。

无理数更是一些奇特的数,比如,2的平方根等。它们不能表示为两个整数的比,即其小数部分是无穷的,而且毫无规律。到此为止讨论的所有数——有理数和无理数都统称为实数。使用实数定义它们的目的是为了将其与虚数区别开来,虚数是负数的平方根,而实数与虚数又构成了复数。

通常人们习惯将数字看成连续的,任意给出的两个有理数,都可以找出一个位于它们之间的数,实际上只要取这两个数的平均值即可。但计算机却无能为力,因为二进制中的每一位非0即1,两者之间没有任何数。这一特点决定了计算机只能处理离散数据,因此二进制数的位数直接决定了所能表示的离散数值的个数,比如,对于8位数来说,所能表示的自然数的范围为0~255。如果要在计算机中存储4.5这个数,则需要选择新的表示方式。

小数可以表示二进制数吗?最简单的方法是使用BCD码(二进制编码的十进制数)。通常将两个BCD数一起使用,这种方式称为压缩BCD。由于2的补数不和BCD数一起使用,则压缩BCD需要增加1位用于标识数的正负,该位被称作符号位。虽然用一个字节保存某个特定的BCD数是非常方便的,但要为这个短小的符号位牺牲4位或8位的存储空间。假设计算机程序要处理的钱款数目在+/-100万之间,则表示前的数目的范围为:

-9,999,999.99 ~ 99,999,999.99

因此保存在存储器中的一笔钱的金额都需要5字节。比如,-4,325,120.25可以表示为:

0001 0100  0011 0010  0101 0001  0010 0000  0010 0101

将每个字节转换成十六进制数,则上面的数可以等价地表示为14H 32H 51H 20H 25H,最左边的半个字节所构成的1用于指明该数是负数,这个1即符号位。如果这半个字节所构成的数是0,则说明该数是整数。组成该数的每一个数字都需要用4位表示,从十六进制的表示形式中可以很直观地看到这一点。如果将数的范围扩大,则需要更多的字节来实现。

这种基于二进制的存储和标记方式被称为定点格式,所谓的“定点”是指小数点的位置总是在数的某个特定位置。但在表示非常大或非常小的数时,使用定点格式数是不合适的。因此科学家和工程师喜欢使用一种称为“科学计数法”的方法记录这类较大或较小的数,利用这种计数系统可以更好地在计算机中存储这些数。

科学计数法将每个数表示为有效位与10的幂的乘积的形式,从而避免了写一长串的0。比如,490,000,000,000可以表示为4.9×1011,而0.00000000026可以表示为2.6×10-10。在这两个例子中,4.9和2.6被称为小数部分或首数,有时也称为尾数。在计算机术语中,这部分被称为有效数,为了保持一致,在这里将科学计数法表示形式中的这一部分称为有效数。

采用科学计数法表示的数可以分为两部分,其中的指数部分用于表示10的几次幂,指数可以表示小数点相对于有效数移动的距离。为了便于操作,规定有效数的取值范围是大于或等于1而小于10。比如,4.9×1011这种写法被称为科学计数法的规范化。这里需要说明,指数的正负性只是表明了数的大小,它不能指明数本身的正负性。

(2)浮点格式

在计算机中,对于小数的存储方式,除了定点格式外还有一种选择,它被称为浮点格式。因为浮点格式是基于科学计数法的,所以它是存储极大或极小数的理想方式。但计算机的浮点格式是借助二进制数实现的科学计数法形式,因此需要先了解如何用二进制表示小数。

在二进制数中,二进制小数点右边的数字和2的负整数次幂相关。比如,将101.1101转换为十进制数为:

1×4+0×2+1×1+1÷2+1÷4+0÷8+1÷16

将乘数和除数用2的整数次幂替换,即:

1×22+0×21+1×20+1×2-1+1×2-2+0×2-3+1×2-4

2的负整数次幂等于从1开始反复除以2,即:

1×4+0×2+1×1+1×0.5+1×0.25+0×0.125+1×0.0625

经过计算后得出101.1101与十进制数5.8125是相等的。

在二进制的科学计数法中,规范化的有效数应该大于或等于1且小于10(十进制的2),则101.1101的规范格式为1.0111011×22,这个规则暗示这样一个有趣的现象,在规范化的二进制浮点数中,小数点的左边只有一个1,除此之外没有其它数字。

  • 单精度格式

在计算机中处理浮点数所遵循的标准是由IEEE于1985年制定的,IEEE浮点数标准定义了两种基本格式:以2字节表示的单精度格式和8字节表示的双精度格式。单精度格式的4个字节分为三个部分:1位符号位(0代表整数,1代表负数),8位用做指数,最后的23位用做有效数。下面给出单精度格式的三部分的划分方式,其中有效数的最低位在最右边:

s=1位符号

e = 8位指数

f = 23位有效数

对于二进制科学计数法的规范格式,其有效数的小数点左边有且仅有一个1,而在IEEE浮点数标准中,这一位没有分配存储空间。仅存储有效数的23位小数部分,尽管存储的只有23位,但仍称其精度为24位,将在下面的内容中体会24位精度的含义。

8位指数部分的取值范围为0~255,称为偏移指数,它的意思是——对于有符号指数,为了确定其实际所代表的的值必须从指数中减去一个值——称为偏移量。对于单精度浮点数,其偏移量为127。如果指数的取值范围为0~254,那么对于一个特定的数,可以用s(符号位)、e(指数)和f(有效数)来描述。即:

(-1)s×1.f×2e-127

其中,-1的s次幂是数学上所采用的一种巧妙的方法,其含义为:如果s=0,则该数是正的(因为任何数的0次幂都是1);如果s=1,则该数是负的(因为-1的1次幂等于-1)。

表达式中的中间部分1.f,其含义为:1的后面是小数点,小数点后面跟着23位的有效数。1.f与2的幂相乘,其中的指数等于内存中的8位偏移量指数减去127。现在我们来讨论一种特殊情况,其详细介绍如下:

(a)如果e=0且f=0,则该数为0。通常将23位都设置为0以表示该数为0,当符号

位置1时,这种数解释为负0,而负0可以表示非常小的数。虽然这些数极小以至于不能在单精度格式下用数字和指数表示,但它仍然小于0。

(b)如果e=0且f≠0,虽然该数是合法的,但不符合规范。这类数可以表示为:

(-1)s×0.f×2-127

注意,在有效数中,小数点的左边是0。

(c)如果e=255且f=0,则该数被解释为无穷大或无穷小,其取决于符号位s的值。

(d)如果e=255且f≠0,则该值被解释为“不是一个数”,通常被缩写为NaN(not a number),NaN用于表示未知的数或非法操作的结果。

在单精度格式下,可以表示的规格化的最小正负二进制数为:

1.000000000000000000000002×2-126

在单精度格式下,可以表示规格化的最大正负二进制数为:

1.111111111111111111111112×2127

在十进制下,这两个数近似地等于1.175494351×10-38和3.402823466×1038,这就是单精度浮点数的有效表示范围。

一般来说,10位二进制数可以近似地用3位十进制数表示,其含义是,如果将10都置为1,即十六进制的3FFH或十进制的1023,它近似地等于将十进制数的3位都置为9,即999,可以表示为210≈103,两者的关系意味着:单精度浮点格式存放的24位二进制数大体上与7位的十进制数相等。因此可以说单精度浮点数提供24位的二进制精度或7位的十进制精度。其深层含义是什么呢?

当我们查看定点数时,其精确度是非常明显的。比如,当用两位定点小数表示钱时,可以精确到分。但对于浮点格式的数来说,就不能如此肯定了。其精确度依赖于指数的值,有时浮点数可以精确到比分还小的单位,但有时其精确度甚至达不到元。因此这样说可能更合适:单精度浮点数的精度为1/224,或1/16777216,或百万分之六,但其真正的含义是什么?

也就是说,在单精度浮点格式下,16,777,216和16,777,217表示为同一个数,不仅如此,处于两个数之间的所有的数都表示为同一个数。由此可见,在程序中使用单精度格式表示浮点数也会出现问题,这时可以考虑使用双精度浮点数。

  • 双精度格式

双精度浮点数需要用8字节表示,它的结构如下:

s=1位符号

e = 11位指数

f = 52位有效数

双精度浮点数的指数偏移量为1023,或十六进制的3FFH,因此以该格式存储的数可以表示为:

(-1)s×1.f×2e-1023

在单精度格式下,提到的单精度浮点格式下的0,无穷大(小)和NaN的判断规则同样适用于双精度浮点格式。双精度浮点数所能表示的范围,用十进制可以近似记为:

2.2250738585072014×10-308 ~ 1.7976931348623158×10308

10的308次幂是一个非常巨大的数,在1的后面跟着308个0。双精度浮点格式的有效数为53位(IEEE754规定隐藏位1的位置在小数点之前),大致相当于十进制的16位。

关于浮点数的用法,与整数不同的是浮点数是有精度的,而初学者常在这些看起来似乎很不起眼的问题上阴沟里翻船,其相应的范例程序详见程序清单 1.4。

  程序清单 1.4 浮点数误差测试范例程序(1)

1#include<stdio.h>

2int main(int argc, char *argv[])

3{

4float fNum = 1000001.111111;

5

6printf("fNum=%f\n", fNum);

7return 0;

8}

通过上机实践你会发现,其输出结果不是1000001.111111,而是1000001.125000,这是因为float型变量仅能接收浮点数常量的7位有效数字,在有效数字后面输出的数字都是不准确的。也就是说,浮点数在计算机中存储的都是近似值,比如,10进制的0.1,用2进制来表示的话,则是0.000110011001......的无限循环,所以不能将浮点变量用“==或!=”与任何数比较,而初级程序员却常常容易犯以下这样的错误,详见程序清单 1.5。

程序清单 1.5  浮点数误差测试范例程序(2)

1#include<stdio.h>

2int main(int argc, char *argv[])

3{

4float f1 = 123.456001;

5float f2 = 123.456002;

6

7if(f1 == f2){

8printf("相等");

9}else{

19printf("不相等");

11    }

12return 0;

13}

对于浮点数来说,只要足够接近0,就应该认为它的值为0,其合法的比较语句如下:

#define  EPSINON  0.0001// 定义程序可接受的浮点数0值

if(value < EPSINON && value > - EPSINON)// value等于0

if(value >= EPSINON || value <= - EPSINON)// value不等于0

5. 类型转换

数据类型是值的集合(内置类型或其它类型)和在这些值上的操作(函数)集,当执行一个操作时,必须确保操作数和结果具有正确的类型,忽视这一点是程序设计中的常见错误。

如果两个表达式的类型相容,则编译器对操作数隐式地进行自动转换,隐式类型转换是将范围窄的的数类型转换为范围更宽的数的类型。也就是说,一些变量在编译期与运行时的结果不一样。即:

char、short→int→unsigned int→long→double  (float→double)

计算的结果将以转换后的类型表示,这样可以保证结果尽可能准确。如果表达式的操作数分别为int型和double型,则int型的操作数被转换为double型。比如:

int a = 4;

double b = 3.521, c;

c = a + b;// 编译器将a自动转换为double型,即4.0

a = a + b;// 编译器将a+b的结果自动转换为int

其结果为c = 7.521,a = 7(即截尾操作)。另有:

unsigned int a = 9;

int b = - 4, c;

c = a / b;

其中,a为unsigned int,b为int,编译器会隐式地将int转换为unsigned int。9对应的二进制数为0x00001001,4对应的二进制数为0x00000100,而-4的补码形式为原码4取反加1,即b为0x0111 1100,则a/b为0。如程序清单 1.6所示的范例程序,这是招聘软件工程师经常考察的经典试题。

程序清单 1.6 变量的类型自动转换范例程序

1 #include<stdio.h>

2i nt main(int argc, char *argv[])

3 {

4 unsigned char a = 0;

5 unsigned char b = 0xFF;

6

7 if (a == ~b){

8 printf("a == ~b");

9 }else{

10 printf("a != ~b");

11 }

12 }

(a)0,1(b)与编译器相关(c)a != ~b(d)a == ~b

解题思路:大多数初学者会认为,0xFF取反后得到0x00,因为a和b在编译期都是char类型变量,所以结果为a == ~b。而实际上在32位计算机中,表达式在运行时将unsigned char型的变量自动转换成了unsigned int型变量,即~b = 0xFFFFFF00,显然其结果a != ~b。

除了隐式类型转换外,有时还需要强制类型转换。比如,i是一个int值,那么表达式“double i;”就会对i的值进行强制类型转换,使该表达式变成double类型。


预约申请免费试听课

填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:初识C语言,C语言入门基础知识总结
下一篇:C语言指针专题——为何要学习指针

C语言创建windows窗口实例

C++回调函数是什么?

C++ shared_ptr和动态数组

C语言有哪些关键词,C语言44个关键词大全

  • 扫码领取资料

    回复关键字:视频资料

    免费领取 达内课程视频学习资料

  • 搜索抖音号

    搜索抖音号:1821685962

    免费领取达内课程视频学习资料

Copyright © 2021 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省