APP下载

探讨Visual C++2010环境下浮点型数据的存储形式

2020-09-27王兆华

电脑知识与技术 2020年23期
关键词:小端浮点数浮点

王兆华

摘要:针对高校本科生课程《C语言程序设计》中有关浮点数数据类型的认识和使用中出现的问题,学生存在对浮点数的认知不够清晰,对Visual C++2010环境下有关浮点数的相关计算结果存在各种困惑。根据多年的教学经验,查阅相关书籍和IEEE754标准,论文分析Visual C++2010环境下浮点型数据的存储形式,阐述了有关浮点数相关几个重要知识点的理解。文中引入计算思维的指导思想,采用从现象到本质,从理论到实践来逐步解决问题。实践证明,该方法取得的了较好的学习效果,夯实了学生对基础知识的掌握和正确应用。

关键词:浮点数;IEEE754;存储形式;Visual C++2010环境;计算思维

中图分类号:TP311        文献标识码:A

文章编号:1009-3044(2020)23-0041-04

Abstract: In view of the problems in the understanding and use of floating point data types in the undergraduate course C Language Programming, students are not clear enough about Floating point number, and they are confused about the calculation results of Floating point data in the Visual C++2010 environment. According to years of teaching experience, consult relevant books and IEEE754 standard, the paper analyzes the storage form of floating point number in Visual C++2010 environment, and expounds the understanding of several important knowledge points related to floating point Numbers. This paper introduces the guiding ideology of computational thinking and solves the problem step by step from phenomenon to essence and from theory to practice. The practice has proved that the method has achieved a good learning effect .The students' mastery and correct application of basic knowledge are consolidated.

Key words:floating point number; IEEE754; storage form; visual C++2010 environment; computational thinking;

1 引言

C語言是最受欢迎、最重要的和最流行的编程语言之一。《C语言程序设计》课程是大部分大学本科理工类学生的一门必修课。C语言有丰富的数据类型,在介绍数据类型一章会介绍基本类型中的整型、字符型、浮点型。在这三种基本类型中浮点型是课程教学的重点和难点,根据多年的教学经验积累,笔者深入研究了Visual C++2010环境下浮点型数据的存储形式,梳理教学重点和难点,引入计算思维的思想,使得学生更容易理解浮点数的存储形式。

周以真教授认为:计算思维(Computational Thinking)是运用计算机科学的基础概念进行问题求解、系统设计以及人类行为理解等涵盖计算机科学之广度的一系列思维活动[1]。它包括了涵盖计算机科学之广度的一系列思维活动。当我们必须求解一个特定的问题时,首先会问:解决这个问题有多么困难?怎样才是最佳的解决方法?计算机科学根据坚实的理论基础来准确地回答这些问题。表述问题的难度就是工具的基本能力,必须考虑的因素包括机器的指令系统、资源约束和操作环境。

本文的思路是首先通过实践发现浮点数计算中存在的问题,为什么结果不准确?为什么会存在一些错误判断。要想弄清楚这些问题,需要清楚理论知识和计算机的操作环境,以及计算机计算能力的限制。

2 抛出问题

在使用浮点数的时候,我们发现一些问题:

(1)一个浮点数不能准确的输出,为什么?程序代码如下:

#include

int main()

{

float fa=56.982f;

printf("fa=%f\n",fa);

return 0;

}

这个程序的执行结果是:fa=56.981998,而不是fa=56.982。

(2)一个浮点数与一个极小的浮点数求和,结果不正确,为什么?程序代码如下:

#include

int main()

{

float fa=2.5f;

fa=fa+0.0000001f;

printf("fa=%f\n",fa);

return 0;

}

这个程序的执行结果是:fa=2.500000,而不是fa=2.5000001。

(3)数学上两个不相等的浮点数程序却认为是相等的,为什么?程序代码如下:

#include

int main()

{

float fa=1.00000001f,fb=1.00000002f;

if(fa==fb)

printf("两个数相等!\n");

return 0;

}

这个程序的执行结果是:“两个数相等!”,显然是错误的。

笔者所给的三个程序的运行环境是Win7+Visual C++2010,因此带着这几个疑问,有必要深入研究一下在这样的编译和运行环境下浮点数的存储形式。

3 IEEE754标准

浮点数是指小数点位置根据需要可以浮动的数据,浮点数的一般表示形式为:N=RE×D,其中N为浮点数,R称为基数,E称为阶码,D称为尾数。如图1所示。

R为一常数,与尾数的基数相同,一般为2、8、或16,在一台计算机中,所有数据的R都是相同的,不需要在每个数据中表示出来。任意一个二进制浮点数N可以表示为:N=2E×D。

在浮点数表示中,即使数据字长相同时,不同的计算机可能选用不同的格式、不同的阶码与尾数位数及其编码,从而导致不同计算机之前浮点机器数差异很大,不利于软件移植,为此,美国电气电子工程师协会于1985年提出了浮点机器数IEEE754标准,并得到广泛应用[2]。在软件中IEEE 754以浮点库的形式实现,在硬件(如许多CPU和FPU)中的指令中实现。 第一个实现IEEE 754-1985草案的集成电路是Intel 8087。

当今流行的计算机几乎都采用IEEE754浮点数标准,在这个标准中,每个浮点数均由数符S、阶码E、尾数M三部分组成。用三元组{S,E,M}表示一个数N,如图2所示。

IEEE754标准规定了四种浮点数的表示方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80比特实做)。C语言中float和double浮点型分别对应的是单精度和双精度浮点数,下面以单精度浮点数为例介绍浮点数的存储形式,如图3所示:单精度浮点数(32位):N共32位,其中S占1位,E占8位,M占23位。

单精度浮点数根据阶码和尾数的取值情况分为:0,非规格化数,规格化数,无穷和NaN(Not a Number,非数)。如表1所示。

浮点数的尾数采用补码形式存储,阶码采用移码形式存储。下面需要清楚什么是补码和移码。

4 原码、反码、补码、移码的概念

要想了解浮点数在计算机中的存储学生务必清楚原码、反码、补码、移碼相关概念。原码、补码,反码是把符号和数值一起编码的表示方法,是机器数的表示形式。

(1)原码:是用“符号码+二进制绝对值”表示的机器码。符号码用0表示正,用1表示负。

(2)反码:引入反码是为了求负数的补码。正数的反码与原码相同,负数的反码除符号位不变外,其余各位取反。

(3)补码:补码的引入是为了在计算机中把减法运算转换为加法运算。使减法运算变得简单,在计算机中容易实现。正数的补码与原码相同,负数的补码在反码的末尾加1。

(4)移码:又称为增码或偏码,在计算机中主要用于表示浮点数的阶码,而阶码是整数,因此阶码仅需要定点证书编码法。移码实质是在真值X基础上加一个固定正整数(称为偏置值),把真值映射到一个正数域,相当于在数轴上将真值X向正方向平移一段距离,这也是该编码命名为“移码”的来由。对于二进制纯整数,移码与真值得映射式为:[X]移=偏置值+X。偏置值选取的原则是使真值X中绝对值最大的负数对应的编码量值为0,因此偏置值一般取2n。当偏置值取2n时,移码与真值得映射式为:[X]移=2n+X (-2n <=X<=(2n-1))。

纯整数偏置值2n移码与补码之间的关系为:同一纯整数真值的偏置值2n移码与补码,它们的有效数值位相同,符号位互反。

清楚补码和移码编码之后,对于多个字节的浮点数,在存储器中应该怎么存储呢?

5 计算机存储的大端法和小端法

对于多字节的程序对象,必须建立两个规则:这个对象的地址是什么,以及在存储器中如何排列这些字节。在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用在字节最小的地址。例如:假设在VC++2010中编译的C程序中一个int的变量x的地址是a,那么x的4个字节将分别被存储在存储器地址为:a、a+1、a+2、a+3的四个存储单元。

某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。最低有效字节在前面的方式,称为“小端法(little endian)”。最高有效字节在最前面的方式,称为“大端法(big endian)”。大多数Intel兼容机都采用“小端法”的规则。大多数IBM和Sun Microsystems的机器都采用“大端法”的规则。IBM和Sun制造的个人计算机使用的是Intel兼容的处理器,因此使用的就是“小端法”。许多比较新的微处理器使用“双端法(bi-endian)”,也就是可以把它们配置成作为大端或者小端的机器运行[3]。

示例:变量x类型为int,位于地址a,int型数据在VC++2010中占32字节,假如它的十六进制为0x01234567。采用“大端法”(图4)和“小端法”(图5)的存储顺序分别如图4和图5所示。

可以通过下面的程序确认机器所采用的存储方式,代码编译运行平台Win32 + VC++2010,机器为联想ThinkPad T430,CPU型号是Intel酷睿i52520M。

#include

int main()

{

int x=0x01234567;

char *px=(char *)&x;

if(*px==0x01)  //判断高字节0x01是否保存在低地址

printf("大端法\n");

else

printf("小端法\n");

return 0;

}

程序执行结果:“小端法”。

清楚了当前计算机采用的是“小端法”之后,需要通过程序来验证浮点数的具体存储形式。

6 Visual C++2010环境下单精度浮点数的具体存储形式

下面以具体的数为例,在编译环境下验证浮点数的存储形式。

(1)十进制0.5

第一步:把十进制0.5转换为二进制结果为(0.5)10=(0.1)2。

第二步:把(0.1)2规格化为2e×D 即2-1× (1)2。

第三步:分别求解S,M,e,E。S=0, M=[000 0000 0000 0000 0000 0000]2,e=-1,E=e+127=126。

第四步:求解机器码(实际存储形式):[0.5]10= [0011 1111 0000 0000 0000 0000 0000 0000]2。

第五步:用十六进制表示机器数:3F000000。

(2)十进制1.25

第一步:把十进制1.25转换为二进制结果为(1.25)10=(1.01)2。

第二步:把(1.01)2规格化为2e×D 即20× (1.01)2。

第三步:分別求解S,M,e,E。S=0, M=[010 0000 0000 0000 0000 0000]2,e=0,E=e+127=127。

第四步:求解机器码(实际存储形式):[1.25]10= [0011 1111 1010 0000 0000 0000 0000 0000]2。

第五步:用十六进制表示机器数:3FA00000。

(3)十进制-1.25

第一步:把十进制-1.25转换为二进制结果为(-1.25)10=(-1.01)2。

第二步:把(-1.01)2规格化为2e×D 即20 ×(-1.01)2。

第三步:分别求解S,M,e,E。S=-1, M=[010 0000 0000 0000 0000 0000]2,e=0,E=e+127=127。

第四步:求解机器码(实际存储形式):[-1.25]10= [1011 1111 1010 0000 0000 0000 0000 0000]2。

第五步:用十六进制表示机器数:BFA00000。

(4)十进制124.25

第一步:把十进制124.25转换为二进制结果为(124.25)10=( 1111100.01)2

第二步:把( 1111100.01)2规格化为2e×D 即26 ×( 1.11110001)2。

第三步:分别求解S,M,e,E。S=0, M=[111 1000 1000 0000 0000 0000]2,e=6,E=e+127=133。

第四步:求解机器码(实际存储形式):[124.25]10= [0100 0010 1111 1000 1000 0000 0000 0000]2。

第五步:用十六进制表示机器数:42F88000。

程序验证(代码编译运行平台Win32 + VC++2010):

#include

int main(void)

{

float fa=0.5,fb=1.25,fc=-1.25,fd=124.25;

unsigned int *pa=(unsigned int *)&fa;

unsigned int *pb=(unsigned int *)&fb;

unsigned int *pc=(unsigned int *)&fc;

unsigned int *pd=(unsigned int *)&fd;

printf("%.1f的内存存储形式:%X\n",fa,*pa);

printf("%.2f的内存存储形式:%X\n",fb,*pb);

printf("%.2f的内存存储形式:%X\n",fc,*pc);

printf("%.2f的内存存储形式:%X\n",fd,*pd) ;

return 0;

}

程序执行结果:

0.5的内存存储形式:3F000000

1.25的内存存储形式:3FA00000

-1.25的内存存储形式:BFA00000

124.25的内存存储形式:42F88000

可见程序的执行结果与上面的分析和计算结果是一致的。

在VC++2010的监视窗口(图6)和内存窗口(图7)观察的结果如下图所示。

清楚了Visual C++2010环境下单精度浮点数的具体存储形式,需要进一步探讨浮点数的取值范围和精度问题。

7单精度浮点数的取值范围和精度

猜你喜欢

小端浮点数浮点
LEO星座增强GNSS PPP模糊度浮点解与固定解性能评估
四种Python均匀浮点数生成方法
基于浮点DSP的铁路FSK信号检测
小端接触同步器后备量分析与验证
在C语言中双精度浮点数线性化相等比较的研究
非精确浮点数乘法器设计
唯一的守军
基于FPGA的浮点FIR滤波器设计
改进的Goldschmidt双精度浮点除法器
旧锻模的再利用