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

 1 ,变量在内存中的存储


 

Microsues 发的微薄:

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

 

我的回复:

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

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

 

为此我写了两行代码

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

这两行代码的输出是:

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 位的浮点数, double 类型对应 s=1,k=11,n=5264 位浮点数。

 

表1.  数的存储

 


符号域 指数域 小数域 Bias
单精度
1 [31] 8 [30-23] 23 [22-00] 127
双精度 1 [63] 11 [62-52] 52 [51-00] 1023

 

 

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

因此 floatbias127 ,取值范围 -126~+127doublebias1023 ,取值范围 -1022~1023

有效位M=1+f ,其中f=fn--1 ....f1 f0 就是 n 位的小数编码 ,也就是M= 1.fn--1 ....f1 f0整数部分1 是隐含的第一位,省略掉,因为这样可以获得额外的 1 位精度。总之:

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

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

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

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

 

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

 

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

 

NaN有两种,QNaN和SNaN,QNaN表示一个不确定的运算,而SNaN表示一个错误的运算。例如INF-INF是一个不确定的运算,而1/0是一个错误的运算。

 

3: 无穷/NaN的运算和判断

 

在数值计算中非常重要. 

 

表2. 关于无穷的运算可以总结成如下表格:

运算 结果
n ÷ ±Infinity 0
±Infinity × ±Infinity ±Infinity
±nonzero ÷ 0 ±Infinity
Infinity + Infinity Infinity
±0 ÷ ±0 NaN
Infinity - Infinity NaN
±Infinity ÷ ±Infinity NaN
±Infinity × 0 NaN

 

在C语言中,可以用float.h中的

int _isnan(double x),返回布尔值

int _finite(double x),返回布尔值

int _fpclass(double x)函数来判断

fpclass的返回结果代表意义分别为

_FPCLASS_SNAN (Signaling NaN)

_FPCLASS_QNAN (Quiet NaN)

_FPCLASS_NINF (Negative Infinity, –INF)

_FPCLASS_PINF (Positive Infinity, +INF)


在C++中,可以用STL中的limits类:

numeric_limits::quiet_NaN()

numeric_limits::signaling_NaN()

numeric_limits::infinity()等方法判断

 

4. 练习

1)现在回到本文最初提出的问题, 计算 0x000000FF 表示的浮点数:

因此s=0, 是一个正数

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

 

这就是所求的结果。


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

用公式 1 计算 :

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

 

3)对于特殊的情况,我们除了使用输出f之外,再使用C runtime library中的几个函数如_fpclass做一个判断

A. 现在把所有的指数位都置成1,也就是从左边的第2位到第9位都置为1,这样就是第三种特殊情况:

0x7F8000FF16 =011111111000000000000000111111112

这个数的M位不全为0,因此应该是一个 NaN(Not a Number),

 

B. 然后我们让所有的小数位都为0,也就是

0x7F80000016 =011111111000000000000000000000002

 

C. 再把符号位设置为1,

0xFF80000016 =111111111000000000000000000000002

 

D. 最后我们做几个特殊运算0/0, INF/0, 0/INF, 看看是什么效果

 

全部代码如下:

#include #include #include #include int _tmain(int argc, _TCHAR* argv[]) { float f = 3.57331e-043f; cout << "f is " << f << endl; *(int*)&f |= 0x800000; cout << "f is " << f << endl << endl; *(int*)&f |= 0x7F800000; cout << "f is " << f << " , " << "it is a NaN? " << _isnan(f) << " , " << "fpclass is " << _fpclass(f) << endl; *(int*)&f &= 0x7F800000; cout << "f is " << f << " , " << "it is a finite number? " << _finite(f) << " , " << "fpclass is " << _fpclass(f) << endl; *(int*)&f |= 0xFF800000; cout << "f is " << f << " , " << "it is a finite number? " << _finite(f) << " , " << "fpclass is " << _fpclass(f) << endl << endl; f = 0; float p = 0 / f; cout << "0/0 is " << p << " , " << "it is a NaN? " << _isnan(p) << " , " << "fpclass is " << _fpclass(p) << endl; p = 0x7F800000/f; cout << "INF/0 is " << p << " , " << "it is a finite number? " << _finite(p) << " , " << "fpclass is " << _fpclass(p) << endl; p = f/0x7F800000; cout << "0/INF is " << p << " , " << "it is a finite number? " << _finite(p) << " , " << "fpclass is " << _fpclass(p) << endl; }

在 MS VC++2008下,运行结果如下:

f is 3.57331e-043 f is 1.17553e-038 f is 1.#QNAN , it is a NaN? 1 , fpclass is 2 f is 1.#INF , it is a finite number? 0 , fpclass is 512 f is -1.#INF , it is a finite number? 0 , fpclass is 4 0/0 is -1.#IND , it is a NaN? 1 , fpclass is 2 INF/0 is 1.#INF , it is a finite number? 0 , fpclass is 512 0/INF is 0 , it is a finite number? 1 , fpclass is 64

其中的 fpclass ,请对照float.h中的定义自行理解.

 

 

5.  关于浮点数的总结:

根据以上说明,可以总结成如下的表格:

 

表3 . 全部的总结

浮点数值(下表中 b = bias)
符号位 指数位(e ) 小数位(f ) 数值
0 00..00 00..00 +0
0 00..00 00..01
:
11..11
Positive Denormalized Real
0.f × 2(-b +1)
0 00..01
:
11..10
XX..XX Positive Normalized Real
1.f × 2(e -b )
0 11..11 00..00 +Infinity
0 11..11 00..01
:
01..11
SNaN
0 11..11 10..00
:
11..11
QNaN
1 00..00 00..00 -0
1 00..00 00..01
:
11..11
Negative Denormalized Real
-0.f × 2(-b +1)
1 00..01
:
11..10
XX..XX Negative Normalized Real
-1.f × 2(e -b )
1 11..11 00..00 -Infinity
1 11..11 00..01
:
01..11
SNaN
1 11..11 10..00
:
11.11
QNaN

 

通过使用公式1和公式2,可以计算得到:

 

表4 . 浮点数表示的范围


非规格化 规格化 表示数的范围
单精度
± 2-149 to (1-2-23 )×2-126 ± 2-126 to (2-2-23 )×2127 ± ~10-44.85 to ~1038.53
双精度
± 2-1074 to (1-2-52 )×2-1022 ± 2-1022 to (2-2-52 )×21023 ± ~10-323.3 to ~10308.3

 

 

6:字节顺序

需要强调的一个问题是字节顺序,在此不做详细说明,只是说明本文的例子。


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

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

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


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


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

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

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

 

7. 参考文献

关于浮点数的话题还有很多很多,尤其是在 浮点数 计算的时候,会有一些让人惊讶的表现,  一些编译器的也许会有bug.更深入的资料有:

 

1.  Computer Systems, A Programmer’s Perspective (CSAPP) ,中译本叫深入理解计算机系统

2. M. Overton. Numerical Computing with IEEE floating Point Arithmetic

3 . IEEE 754: Standard for Binary Floating-Point Arithmetic ,http://steve.hollasch.net/cgindex/coding/ieeefloat.html   http://grouper.ieee.org/groups/754/


你可能感兴趣的:(算法,数值与非数值,C++/C,Winows编程/MFC/VC)