【原创醒目】这两天关于浮点数的讨论这么火,我觉得还是有必要写点东东了——《再析IEEE浮点数表示法》

fireseed 2006-06-22 04:58:15
小时候(不见得多小,吼吼)写过类似的一篇文章,发表在CSDN的专栏上。现在看起来简直粗陋无比,因此萌发了重写的念头。

众所周知,计算机中的所有数据都是以二进制表示的,浮点数也不例外。然而浮点数的二进制表示法却不像定点数那么简单了。

先澄清一个概念,浮点数并不一定等于小数,定点数也并不一定就是整数。所谓浮点数就是小数点在逻辑上是不固定的,而定点数只能表示小数点固定的数值,具用浮点数或定点数表示某哪一种数要看用户赋予了这个数的意义是什么。

C++中的浮点数有6种,分别是:

float:单精度,32位
unsigned float:单精度无符号,32位
double:双精度,64位
unsigned double:双精度无符号,64位
long double:高双精度,80位
unsigned long double:高双精度无符号,80位(嚯,应该是C++中最长的内置类型了吧!)

然而不同的编译器对它们的支持也略有不同,据我所知,很多编译器都没有按照IEEE规定的标准80位支持后两种浮点数的,大多数编译器将它们视为double,或许还有极个别的编译器将它们视为128位?!对于128位的long double我也仅是听说过,没有求证,哪位高人知道这一细节烦劳告知。

下面我仅以float(带符号,单精度,32位)类型的浮点数说明C++中的浮点数是如何在内存中表示的。先讲一下基础知识,纯小数的二进制表示。(纯小数就是没有整数部分的小数,讲给小学没好好学的人)

纯小数要想用二进制表示,必须先进行规格化,即化为 1.xxxxx * ( 2 ^ n ) 的形式(“^”代表乘方,2 ^ n表示2的n次方)。对于一个纯小数D,求n的公式如下:
n = 1 + log2(D); // 纯小数求得的n必为负数
再用 D / ( 2 ^ n ) 就可以得到规格化后的小数了。接下来就是十进制到二进制的转化问题,为了更好的理解,先来看一下10进制的纯小数是怎么表示的,假设有纯小数D,它小数点后的每一位数字按顺序形成一个集合:
{k1, k2, k3, ... , kn}
那么D又可以这样表示:
D = k1 / (10 ^ 1 ) + k2 / (10 ^ 2 ) + k3 / (10 ^ 3 ) + ... + kn / (10 ^ n )
推广到二进制中,纯小数的表示法即为:
D = b1 / (2 ^ 1 ) + b2 / (2 ^ 2 ) + b3 / (2 ^ 3 ) + ... + bn / (2 ^ n )
现在问题就是怎样求得b1, b2, b3,……,bn。算法描述起来比较复杂,还是用数字来说话吧。声明一下,1 / ( 2 ^ n )这个数比较特殊,我称之为位阶值。
例如0.456,第1位,0.456小于位阶值0.5故为0;第2位,0.456大于位阶值0.25,该位为1,并将0.45减去0.25得0.206进下一位;第3位,0.206大于位阶值0.125,该位为1,并将0.206减去0.125得0.081进下一位;第4位,0.081大于0.0625,为1,并将0.081减去0.0625得0.0185进下一位;第5位0.0185小于0.03125……
最后把计算得到的足够多的1和0按位顺序组合起来,就得到了一个比较精确的用二进制表示的纯小数了,同时精度问题也就由此产生,许多数都是无法在有限的n内完全精确的表示出来的,我们只能利用更大的n值来更精确的表示这个数,这就是为什么在许多领域,程序员都更喜欢用double而不是float。

float的内存结构,我用一个带位域的结构体描述如下:
struct MYFLOAT
{
bool bSign : 1; // 符号,表示正负,1位
char cExponent : 8; // 指数,8位
unsigned long ulMantissa : 23; // 尾数,23位
};

符号就不用多说了,1表示负,0表示正
指数是以2为底的,范围是 -128 到 127,实际数据中的指数是原始指数加上127得到的,如果超过了127,则从-128开始计,其行为和X86架构的CPU处理加减法的溢出是一样的。比如:127 + 2 = -127;127 - 2 = 127
尾数都省去了第1位的1,所以在还原时要先在第一位加上1。它可能包含整数和纯小数两部分,也可能只包含其中一部分,视数字大小而定。对于带有整数部分的浮点数,其整数的表示法有两种,当整数大于十进制的16777215时使用的是科学计数法,如果小于或等于则直接采用一般的二进制表示法。科学计数法和小数的表示法是一样的。
小数部分则是直接使用科学计数法,但形式不是X * ( 10 ^ n ),而是X * ( 2 ^ n )。拆开来看。

0 00000000 0000000000000000000000
符号位 指数位 尾数位


下面是一个分析float类型内存数据的程序,经测试是完好的,如果发现有问题,请提出来,我好改进。

...全文
7582 169 打赏 收藏 转发到动态 举报
写回复
用AI写文章
169 条回复
切换为时间正序
请发表友善的回复…
发表回复
yuanchuang 2007-04-12
  • 打赏
  • 举报
回复
置顶贴居然是2006-6-22 4:58:16发的,还没有结啊?呵呵
toxyboy 2006-11-27
  • 打赏
  • 举报
回复
我只是来仰视下各位,按照各位的算法,我学c++因该有10年了,大学4年,工作6年...但是看你们的发言,俺怎么感觉自己就没接触过c++?
littlebao 2006-11-27
  • 打赏
  • 举报
回复
mark
dongmusic 2006-11-27
  • 打赏
  • 举报
回复
做个记号,有时间再看!
mimo331 2006-11-27
  • 打赏
  • 举报
回复
别说了
咋有这么大差距呢?
anikan_yu 2006-11-27
  • 打赏
  • 举报
回复
不要说原创了。汗!
jackyqk 2006-11-27
  • 打赏
  • 举报
回复
学习学习,谢谢楼主
caosm 2006-11-27
  • 打赏
  • 举报
回复
理工科的底层都是与数学有关的问题,
又岂止是计算机科学一门如此呢?
jiangkeredgirl 2006-11-23
  • 打赏
  • 举报
回复
计算高精确度的圆周率PI
程序:
#include <stdlib.h>
#include <stdio.h>

long a=10000,b,c=2800,d,e,f[2801],g;

main()
{
for(;b-c;)
f[b++]=a/5;
for(;d=0,g=c*2;c-=14,printf("%.4d",e+d/a),e=d%a)
for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);
}
===============================================
转载,不问出处
jiahui220 2006-11-23
  • 打赏
  • 举报
回复
mark
lkpo 2006-11-23
  • 打赏
  • 举报
回复
MK
HoBoss 2006-11-22
  • 打赏
  • 举报
回复
mark
expter 2006-11-22
  • 打赏
  • 举报
回复
。。。。。
morphymorphy 2006-11-21
  • 打赏
  • 举报
回复
mrk
breakout 2006-11-21
  • 打赏
  • 举报
回复
收藏先
navyblue1982 2006-11-21
  • 打赏
  • 举报
回复
上面都是星爷 拜一个
chai2010 2006-11-21
  • 打赏
  • 举报
回复
《Hackers Delight》是否有一节讲浮点数的
(网上有电子版),不过我也没看,
这么多N人在这里就不评论了:)
swimmer2000 2006-11-17
  • 打赏
  • 举报
回复
up下,
有没有人知道怎样进行符点数的加减乘除操作.
gapwind 2006-11-16
  • 打赏
  • 举报
回复
mark
swimmer2000 2006-11-16
  • 打赏
  • 举报
回复
mark
有空看看
加载更多回复(149)

64,640

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧