现在的位置: 首页 > 综合 > 正文

浮点数在存储器中的表示与计算

2013年05月28日 ⁄ 综合 ⁄ 共 3150字 ⁄ 字号 评论关闭

本文转载自:http://blog.csdn.net/stone688598/archive/2011/02/28/6214454.aspx

问题 1 ,变量在内存中的存储

 

 

Microsues 发的微薄:

C++ 中,把负值赋给 unsigned 对象是合法的,其结果是该负数对该类型的取值个数求模后的值。例如:把 -1 赋给 8 位的 unsigned char ,因为 -1对 256 求模后的值为 255 ,所以结果是 255 。

 

我的回复:

c/c++ 中赋值很随意的,只需要做好 " 合法 " 的类型转换。本质上是数 / 对象如何在机器中表示的问题,一个内存中的数,可以是 char, int, pointer, double.... 任何类型。你的例子中 , 内存中写入了一个 0xFF 字节,而

0xFF=-1(char)=255(u char)=3.57331e-043(float)=......

 

为此我写了两行代码

 

  1. float f = 3.57331e-043f;  
  2. cout << f << '/t' << (int)*(char*)&f << '/t' << (unsigned int)*(unsigned char*)&f << '/t' << std::hex << *(unsigned int*)&f << endl;  

 

这两行代码的输出是:

 

  1. 3.57331e-043    -1      255     ff  

 

 

也就是说 0x000000FF 表示浮点数的 3.57331e-043 ,看起来有点不可思议,这是怎么来的呢?

 

问题 2 :浮点数在内存中的表示

IEEE 规定,浮点标准用

    

形式来表示一个数,其中:

S 是符号 (sign) , M 是有效数 (significand) ,指数2 的 E 次幂,注意是2 不是 10 。

 

浮点数的位分成三个区域表示这些值:

1)s=1 个符号位 s ,直接编码 S

2) k 位的指数编码 E

3) n 位小数编码 M

 

在 C 语言中, float 类型对应 s=1, k = 8, n=23 ,表示 32 位的浮点数, doble 类型对应 s=1,k=11,n=52 的 64 位浮点数。

 

以 float 为例详细说明:

符号位很好理解, 0 表示正, 1 表示负,指数位 E 和有效 M 位比较绕了, IEEE 规定了 3 种亲光

1、  规格化:当 E 的二进制位不全为 0, 也不全为 1 时, V 为规格化形式。此时指数为被解释为表示偏置( biased )形式的整数:

E = e - Bias ,    e 是无符号数,e=ek-1 ...e0 也就是指数位的2 进制数,而Bias = 2k-1 - 1

因此 float 的 bias 是 127 ,取值范围 -126~+127 , double 的 bias 是 1023 ,取值范围 -1022~1023

有效位M=1+f ,其中 就是 n 位的小数编码 , 1 是隐含的第一位,因为这样可以获得额外的 1 位精度。总之:

(公式1 )        E = e - Bias, M = 1 + f, Bias = 2k-1 - 1

2、  非规格化:当 E 的二进制位全部为 0 时, V 为非规格化形式。此时 E , M 的计算都非常简单:

(公式2 )        E = 1 - Bias, M = f, Bias = 2k-1 - 1

此时小数点左侧的隐含位为 0 。

 

为什么 E 会等于 (1-bias) 而不是 (-bias) ,这主要是为规格化数值、非规格化数值之间的平滑过渡设计的。有了非规格化形式,我们就可以表示 0了。把符号位 S 值 1, 其余所有位均置 0 后,我们得到了 -0.0; 同理,把所有位均置 0, 则得到 +0.0 。非规格化数还有其他用途,比如表示非常接近 0 的小数,而且这些小数均匀地接近 0, 称为“逐渐下溢 (gradually underflow) ”属性。

 

3 、特殊数值:当 E 的二进制位全为 1 时为特殊数值。此时,若 M 的二进制位全为 0 ,则 n 表示无穷大,若 S 为 1 则为负无穷大,若 S 为 0 则为正无穷大 若 M 的二进制位不全为 0 时,表示 NaN(Not a Number) ,表示这不是一个合法实数或无穷,或者该数未经初始化。

 

现在来计算 0x000000FF 表示的浮点数:

因此s=0, 是一个正数

指数位 e 的每位都是 0 ,是非规格化表示,使用公式 2 ,

  

 

 

这就是所求的结果。

 

那么现在,我们把指数的最低位置成 1 ,也就是从左边数的第 9 位置为 1 ,这样就变成了规格化表示:

 

用公式 1 计算 :

 

 

有趣的是,这里的 E 同样也是 127 ,但是由于 M 不同,得到的 V 也不同,而且相差非常大,竟然有105 数量级。

 

全部的代码如下:

 

  1. float f = 3.57331e-043f;  
  2. cout << f << '/t' << (int)*(char*)&f << '/t' << (unsigned int)*(unsigned char*)&f << '/t' << std::hex << *(unsigned int*)&f << endl;  
  3. *(int*)&f |= 0x800000;  
  4. cout << f << '/t' << (int)*(char*)&f << '/t' << (unsigned int)*(unsigned char*)&f << '/t' << std::hex << *(unsigned int*)&f << endl;  

 

输出为:

 

  1. 3.57331e-043    -1      255     ff  
  2. 1.17553e-038    ffffffff        ff      8000ff  

 

 

问题 3 :字节顺序

这个问题不做详细说明,只是说明本文的例子。

 

在存储器中,不同的操作系统有两种顺序,按照以下方法记忆就可以:

1) 小端法 little endian ,就是同从左到右的书写顺序“相反”,这里的相反是按找字节相反,而不是全部逆序

2) 大端法 big-endian 就是同从左到右的书写顺序相同

 

采用什么方法并无明确规定,就像吃鸡蛋先从大头吃还是从小头吃都是一样的, Intel 的机器采用小头法,最高字节在最右边,最低字节在最左边,正好是反的,比如上例中:

 

0x000000FF ,在内存中存储为 FF 00 00 00

而 0x008000FF ,在内存中存储为 FF 00 80 00 , 注意这里 80 的位置

如果从内存中读取数值时,无论是什么数据类型,一定要先反相排列,然后再进行计算

 

关于浮点数的话题还有很多很多,尤其是在计算的时候,浮点数经常会有一些让人惊讶的表现。

 

以上参考了 Computer Systems, A Programmer’s Perspective (CSAPP) ,中译本叫深入理解计算机系统

更深入的参考书应该是 M. Overton. Numerical Computing with IEEE floating Point Arithmetic

以及 IEEE 754: Standard for Binary Floating-Point Arithmetic , http://grouper.ieee.org/groups/754/

抱歉!评论已关闭.