超级版主
注册日期: 04-03
帖子: 18592
精华: 36
现金: 249466 标准币
资产: 1080358888 标准币
|
【转帖】浮点类型深谈
标题:浮点类型深谈
1.编写本文目的
浮点运算已属常用,但仍有深究必要,以消除其中可能的错误使用。
2.预期读者
3.编者语
㈠ 在其他任何公共论坛上转载本文,请标明出处和作者,而无需授权和确认。
4.正文
㈠ float格式简谈
Inter 80386/80387 及以上型号CPU有三种浮点类型,即短实数、长实数和80位临时实数,分别占用4字节、8字节和10字节,对应着C/C++中的float、double和long double[注1],我以 Real4、Real8和Real0表示之。
每种浮点格式皆应符合IEEE 754标准,称为规格化数,不符合IEEE标准的浮点格式称为非格式化数(NAN),我以最简单的float格式举例。
Float格式数据长32 bits,最高位为符号位:0为正,1为负;紧接着的8位为阶码:为了便于比较大小,其固定偏移7FH长,即0实际表示-7FH,7FH实际表示0,0FFH实际表示80H;余下的低23位为尾数(有效数字),为了使有效数字达到最大精度,这23个有效数字隐含着固定位1[注2],比如尾数10000000000000000000001其实就是1.10000000000000000000001,1被省略,而小数点固定在首位。
根据以上规则可以知道float所能表示的绝对值大小范围是
0 000,0000,0 000,0000,0000,0000,0000,0000 B 至
0 111,1111,1 111,1111,1111,1111,1111,1111 B
即±1.00000000000000000000000B×2(0x00-0x7F) 到±1.11111111111111111111111B×2(0xFF-0x7F)
然而事实上并不是这样,因为从这个范围可以看出它并不能表示0.0,而0.0为常用数字,所以特别规定±1.00000000000000000000000B×200为零,规定指数为0xFF的数字为非法数字,因此float实际的绝对值范围(除去0以外)是
0 000,0000,0 000,0000,0000,0000,0000,0001 B 至
0 111,1111,0 111,1111,1111,1111,1111,1111 B
即 0 并上 ±1.00000000000000000000001B×2(0x00-0x7F) 到±1.11111111111111111111111B×2(0xFE-0x7F)
可能的格式状态:
+不支持
+非有效数
-不支持
-非有效数
+规格化
+∞
-规格化
-∞
+0
+空
-0
-空
+不能规格化
-不能规格化
可能的异常:
无效操作
上溢
下溢
除零
不可规格化操作数
精度不足
㈡ 注意事项
a. 可以看出0可以用+0表示,也可以用-0表示,为此,CPU在比较零值时作了特殊处理,结果是±0虽然在存储器上格式不同,但比较值相同,然而如果是用字节来比较float类型大小时却需要注意这一点。
b. 运算上溢的值不是实际值,而是特值 0 111,1111,1 000,0000,0000,0000,0000,0000 B,例如 1.0×2127 + 1.5×2127 不等于 1.5×2128,虽然它有能力表示1.5×2128。
c.
㈢ 在C++中的部分解决方案及遗留问题
5.参考资料
《Inter 80X86/80387 汇编指南》
6.附录
[注1]:M$认为double精度已足够,故在MVC++5.0及以后取消80位了临时实数,令long double等同于double,但在本文中的long double还是指80位临时实数。
[注2]:各种浮点类型格式类似,double指数偏移基数3FFH,long double指数偏移基数3FFFH,但long double特殊在无有效数字隐含位。
real04:符号位1,阶码08(固定偏移 7F),尾数23,固定隐含位有;
real08:符号位1,阶码11(固定偏移 3FF),尾数52,固定隐含位有;
real10:符号位1,阶码15(固定偏移3FFF),尾数64,固定隐含位无;
7. C99 之 fenv.h
#pragma STDC FEVN_ACCESS ON
FE_DIVBYZERO 除0异常
FE_INEXACT 不精确值异常
FE_INVALID 非法值异常
FE_OVERFLOW 上溢异常
FE_UNDERFLOW 下溢异常
FE_ALL_EXCEPT 所有浮点异常
FE_DOWNWARD 向下舍入
FE_TONEAREST 最近舍入
FE_TOWARDZERO 趋0舍入
FE_UPWARD 向上舍入
FE_DFL_ENV 默认环境
等等,太多了
posted on 2004-05-20 05:59 周星星 阅读(7455) 评论(17) 编辑 收藏
评论
# re: float谈( ASM/C/C++,未完待续 ) 2004-05-21 04:37 Hunpo326
处理器不是采用了补码形式吗?这样还没有消除+0和-0问题呀。有点不明白,等我回头看看。
# to Hunpo326 2004-05-21 06:02 周星星
对于整型才使用补码表示;看float的格式,如果使用补码表示负数只会带来麻烦。
# to [xiong_107]: 2004-05-25 05:34 周星星
double(1.000997) 在内存中的存储:
double存储格式: 符号位1bit,阶码11bits,尾数52bits,还有一个隐含固定位
先将十进制表示的1.000997换算成二进制科学计数法表示,即
1.0000000001000001010101101110001001100100111001001000 乘以 2 的 0 次方,
第一bit是符号位,此时为正,故放0
接着是阶码,阶码此时为0,但要加上偏移基数3FFH,变成了01111111111(二进制表示)
接着是尾数,也就是小数点后面的52bits,小数点前面的1就不需要了,这就是隐含固定位的意义
这样就变成了0 01111111111 0000000001000001010101101110001001100100111001001000(二进制表示)
即 3FF004156E264E48(十六进制表示)
# re: float谈( ASM/C/C++,未完待续 ) 2004-06-08 22:30 NoMatter
The following parameters are used to define the model for each floating-point type:
s sign (±1)
b base or radix of exponent representation (an integer > 1)
e exponent (an integer between a minimum emin and a maximum emax)
p precision (the number of base-b digits in the significand)
fk nonnegative integers less than b (the significand digits)
A normalized floating-point number x ( f1 > 0 if x ≠ 0) is defined by the following model:
p -k
x = s × be ×Σ fk × b , emin ≤ e ≤ emax
k=1
# re: float谈( ASM/C/C++,未完待续 ) 2005-07-09 10:56 Magus
问个问题:
如果在VC++中定义一个FLOAT,然后赋值:-0.01
编译的时候会有警告,这是为什么呢。。。
# to Magus: 2005-07-09 12:05 周星星
如《立即数的后缀修饰》所说,严格的应当写成 float var = -0.01f; 不加f后缀默认其为double类型,而之所以有些立即数不报出警告这是因为这个立即数在float和double中一致或精度足够。
注意精度对最终结果的影响:
float a = 6.6/2.2;
int b = 6.6/2.2;
int c = a;
此时 c 是多少?如果把float a换成double a,那时 c 又是多少?
# re: float谈( ASM/C/C++,未完待续 ) 2005-08-15 22:16 kaby
浮点是CPU里实现一半、compiler实现一半啊?
只看过整数运算实现浮点的,浮点实现浮点有点意思。。
# 问 2005-09-01 12:39 fzx
如果用float作为CMap的键值(key),则程序可能隐藏哪些缺陷?
如何规避呢?
请帮忙分析一下。
# to fzx: 2005-09-01 13:25 周星星
不是很明白你所问的问题:
1. C++中有std::map,但没有CMap,CMap是MFC的,不是C++的。
2. float本来就没有缺陷,既然没有缺陷,就谈不上“隐藏哪些缺陷?”,此文说的是“可能的错误使用”,“可能的错误使用”是程序员的事,它不是float的缺陷,如同菜刀能割开手指,“能割开”是菜刀的功能,如果没有“能割开”这个功能那么菜刀也就不能切菜切肉,也就失去它存在的意义,所以虽然菜刀能割开手指,但“能割开”不是菜刀的缺陷,是菜刀主人的使用不当。
# to 周星星 2005-09-07 12:26 fzx
你的意思我明白。我想表达的也是“可能的错误使用”。
MFC中CMap,如果用float作键值,查表的时候,会直接导致浮点数直接比较(通过f1==f2来判断),如果菜刀用不好,会割到手指。问题可能归根于浮点比较吧。
# to fzx: 2005-09-07 12:55 周星星
:) 浮点数直接比较有问题吗?这网上以讹传讹,竟然说浮点数不能直接比较,我晕。
浮点数是有限精度的实数,比如doule就不能精确的表示6.6和2.2,那么if( 6.6/2.2 == 3.0 )肯定是false,但不是浮点数本身不能比较
if( 6.6 == 6.6 ) 一定是true
if( 2.2 == 2.2 ) 一定是true
if( 6.6/2.2 == 6.6/2.2 ) 一定是true
# re: float谈( ASM/C/C++,未完待续 ) 2005-10-29 02:31 leojay
“浮点数直接比较有问题吗?这网上以讹传讹,竟然说浮点数不能直接比较,我晕。 ”
我认为大家说的“浮点数不能直接比较”的意思不是说6.6 == 6.6会编译出错,而是由于浮点数的不精确,你直接比较浮点数的大小,经常会出一些莫明其妙的问题。
手边没有VC,我用Dev-C++4.9.9.2,下面的程序
int i;
double a = 0.1;
double sum = 0.0;
for (i=0; i<10000; i++)
{
sum += a;
}
if (sum == 1000.0)
{
cout << "equal" << endl;
} else
{
cout << "error" << endl;
}
cout << (sum-1000.0) << endl;
上面的程序输出:
error
1.58821e-010
# re: float谈( ASM/C/C++,未完待续 ) 2005-10-29 02:39 leojay
我一般在程序中比较浮点数的时候,都是这样做的:
先定义好:
const double esp = 1e-6;
#define equal(a, b) (fabs((a)-(b))<esp)
然后要比较的地方:
if (equal(sum, 1000.0))
{
cout << "equal" << endl;
} else
{
cout << "error" << endl;
}
这样就正确了。
最多就只要根据需要调节一下esp的大小就好了。
# re leojay: 2005-10-29 12:20 周星星
我这几天非常忙,不再有时间回复跟贴了,sorry。
1. 6.6 == 6.6,……,而是由于浮点数的不精确
------ 浮点数是绝对精确的,比如它用double 6.5999999999999996 来表示 6.6,而绝对不会用 6.5999999999999995,也绝对不会用 6.5999999999999997。
记住,浮点数不能表示所有精度的实数,但浮点数本身是绝对精确,不可能这次用 6.59…6 来表示 6.6,却在其他地方或其他时候用 6.59…7 来表示 6.6。
2. 对于第一段代码,你犯了一个错误,你用无限精度的计算结果来比较有限精度的计算结果,double是有限精度的,既然大家都知道,你这段代码能说明什么呢?如果你要证明浮点数不精确的话,你应该这样写:
for( i=0; i<10000; i++ ) sum1 += 0.1;
for( i=0; i<10000; i++ ) sum2 += 0.1;
if( sum1 != sum2 ) cout << "浮点数不精确。" << endl;
3. 对于你说的 epsilon != 1e-6,我猜想你工作不久,或者是你没有从事过科学计算相关的工作。如果你从事过科学计算相关的工作,那就简单了,我问你,为什么是 1e-6 而不是1.1e-6,而不是0.9e-6 ?
当然,如果你没有从事过科学计算相关的工作,对于我这个问题你一定听得莫名其妙。没关系,把你的代码改一改就成了:
const double a = 0.1000000001;
double sum = 0.0;
for( int i=0; i<10000; ++i ) sum += a;
cout << (equal(sum,1000.0)?"equal":"unequal") << endl;
也就是 0.1000000001 累加一万次等于 1000.0 。你把一个看起来错误但实际是正确的计算弄成了看起来也正确,了不起,但却把一个看起来错误实际也是错误的计算却弄成了正确,这不是得不偿失嘛,哈哈!
# 补充: 2005-10-29 12:25 周星星
为了防止好争论者有意找茬,我在此对
for( i=0; i<10000; i++ ) sum1 += 0.1;
for( i=0; i<10000; i++ ) sum2 += 0.1;
if( sum1 != sum2 ) cout << "浮点数不精确。" << endl;
进行补充说明:
某些编译器的某些选项允许用浮点数的计算误差来换取效率,这样一来,sum1就未必等于sum2,但是这不能说明浮点数不精确,因为你不让某甲参加考试却一定要他拿第一名,这个要求太变态。
# re: float谈( ASM/C/C++,未完待续 ) 2005-10-30 14:53 leojay
我忘记加声明了:我的程序不能用于有高精度要求的科学计算。呵呵。^_^
被您发现了,我刚工作一年,而且也没从事过科学计算。呵呵。我工作上的程序一般用于处理钞票方面的比较多,只要精确到小数点后3位就好了。所以一般用eps=1e-6,而且我的帖子最后我也说了一句“最多就只要根据需要调节一下esp的大小就好了”
不过我猜想,如果要用到有高精度要求的科学计算上的话,不能直接用double或long double这种数据类型吧?像您上面的这种const double a = 0.1000000001的数据,随便来个乘乘除除的数据不就都乱了吗?比方说,我cout << a / 10000.0 * 10000.0;的结果就是0.1了。
之前在大学里参加ACM程序比赛时,如果要处理高精度要求的数据(比如小数点前、后2000位),我都是自己写高精度类型的类来实现的。
这方面经验不多,又比较感兴趣,所以想请教一下,如果有空的话,跟我说说有关这方面的知识吧。多谢了。
# re leojay: 2005-11-01 11:07 周星星
(A)科学计算未必都是高精度,事实上也并没有无限高精度的实现手段,如果有的话,就能把0.999…计算成1.0 ^_^
(B)精度按需设计,有一个比精度更重要的是结果的可再现性,这是一切科学研究的根本。对于一个运算过程,要保证相同的数据得出相同的结果,如果每次0.3*3都等于1.0那就说明这个运算逻辑正确,虽然它与无限精度的实数运算结果(0.9)之间存在误差。误差大家都能理解,但不能飘忽不定,有的时候是0.899,有的时候是0.901,那就不行了,“一次不忠,百次不用”^_^
(C)epsilon用于误差检查,但它存在的前提就是运算过程的精确。我举个反例:假设运算过程不精确,0.3*3只有90%的几率等于1.0,那么此时epsilon应该取多少?没法取吧!所以说运算的精确是其他一切的前提,包括精度。而计算机的浮点运算就能保证运算的精确,虽然它的精度不是无限的。
|