作者:fbysss
关键字:java strictfp IEEE754 浮点数运算
一、前言
本文是针对java语言的strictfp关键字的扩展性研究,所引用博文内容,只关注问题,不针对作者,若有不当之处,还望指正。
二、背景
在学习java volatile关键字的时候,发现了有关strictfp的描述,之前没注意过,觉得新鲜,于是查阅了一些资料,国内的文章,对strictfp的解释大概都来自同样的几篇blog,但对里面的解释产生了疑惑。比如《Java语言中关键字strictfp的用途》一文(http://aliax.bokee.com/2184263.html)有这么一段话:
”strictfp的意思是FP-strict,也就是说精确浮点的意思。在Java虚拟机进行浮点运算时,如果没有指定strictfp关键字时,Java的编译器以及运行环境在对浮点运算的表达式是采取一种近似于我行我素的行为来完成这些操作,以致于得到的结果往往无法令你满意。而一旦使用了strictfp来声明一个类、接口或者方法时,那么所声明的范围内Java的编译器以及运行环境会完全依照浮点规范IEEE-754来执行。因此如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,那就请用关键字strictfp。“
和大多数人一样,看到这段话,心里很是Happy。因为我们知道Java处理浮点数运算,存在不精确的情况,我们往往使用BigDecimal或者乘除10的n次方的办法来解决。但还是够麻烦的。如果加入这个关键字就能解决问题,岂不妙哉?
于是,用一个小程序来检验一下:
public strictfp void testAdd(){
System.out.println("result:"+(0.01f+0.04f));
}
输出结果
result:0.049999997
去掉strictfp之后也一样,看来和想象的不是一回事。
再查查,在百度百科中发现了strictfp的词条,http://baike.baidu.com/view/1866622.htm?fr=ala0
感觉说的和上面差不多,里面的例子跑了一下,结果如下:
float: 0.6710339
double: 0.04150553411984792
sum: 0.71253945297742238
quotient: 16.1673355
口算就能知道应该是4792结尾,与程序输出结果2238不一样。
看来,strictfp不能解决所谓”精确“问题。但到底有啥用处呢?这几个例子实在看不出什么名堂来。只有研究研究国外的资料了。
三、文档研究
http://en.wikipedia.org/wiki/Strictfp中的第一句:strictfp is a Java keyword used to restrict floating-point calculations to ensure portability。目的很明确,就是用来保证可移植性的。
再看看sun官方怎么说:
http://java.sun.com/developer/JDCTechTips/2001/tt0410.html#using
原文有这么一段:
strictfp is important because its use guarantees common behavior across different Java implementations. In other words, you can know that the floating-point arithmetic in your application behaves the same when you move your application to a different Java implementation or hardware platform.
同样,和不同的Java实现或硬件平台相关,还是移植性。
官方的例子:
public strictfp class FpDemo3 {
public static void main(String[] args) {
double d = 8e+307;
System.out.println(4.0 * d * 0.5);
System.out.println(2.0 * d);
}
}
输出结果为
Infinity
1.6E308
我们看到的是2.0 * d和4.0 * d * 0.5不一样。因为java是从左到右进行运算的(because the Java programming language guarantees a left-to-right order of evaluation),即在计算4.0 * d的时候,就已经溢出了(Double.MAX_VALUE大概是1.8e+308)。之后的运算也就无效了。
By contrast, if the expression is not FP-strict, an implementation is allowed to use an extended exponent range to represent intermediate results.这句话大概意思就是说,如果没有加strictfp关键字,在不同的平台下面,可能会得到不同的结果。
注意其中有一个extended exponent range,这涉及到浮点数的定义(详细内容请参考:http://blog.chinaunix.net/u1/55705/showart_1848540.html):
s · m · 2 (e-N+1)其中S是,+1,-1
m是正整数小于2N而e是个整数,值域是Emin = -(2K-1-2) 到 Emax = 2K-1-1
而java的float和double对应参数如下表所示:
Parameter | float | float-extended-exponent | double | double-extended-exponent |
N | 24 | 24 | 53 | 53 |
K | 8 | 11 | 11 | 15 |
Emax | +127 | +1023 | +1023 | +16383 |
Emin | -126 | -1022 | -1022 | -16382 |
文中提到的extended exponent range,应该就是float-extended-exponent或者double-extended-exponent,体会一下,也就是在某种情况下,数字的范围不一样。并不限定于32位float和64位double了。上面的计算结果,可能就不是Infinity,而是1.6E308或其他值。
而一旦使用了strictfp来声明一个类、接口或者方法时,那么所声明的范围内Java的编译器以及运行环境会完全依照浮点规范IEEE-754来执行。”
其中的IEEE-754到底是个什么东东呢?
四、扩展研究
“IEEE二进制浮点数算术标准(IEEE 754)是最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。”(详见http://zh.wikipedia.org/wiki/IEEE_754)
参考《The Java Language Specification 3rd Edition》(下载地址:http://java.sun.com/docs/books/jls/download/langspec-3.0.pdf)中P441-15.4 FP-Strict Expressions最后一段写道:
Within an FP-strict expression, all intermediate values must be elements of the float value set or the double value set, implying that the results of all FP-strict expressions must be those predicted by IEEE 754 arithmetic on operands represented using single and double formats.
大致意思是:在一个FP-strict表达式中间的所有值必须是float或者double,这意味着运算结果也肯定是IEEE754中所规定的单精度和双精度数,也即是32位的float和64位的double。
这里也只说明了,float和double的规定,是符合IEEE754标准的。
小结:
IEEE754是一个针对CPU或者FPU(浮点运算器)制定的标准,Java的浮点数是在其基础上,规定了某些参数值,确定了float和double类型。即使CPU/FPU都符合了IEEE_754,但浮点数超出了float或double的范围,运算就可能不一致。所以,该博文中提及的strictfp与IEEE_754的关系是没有根据的。
五、结论
1.strictfp翻译为“精确的浮点”不够贴切,容易引起误解。
2.strictfp关键字的使用与IEEE754没有直接因果关系。IEEE 754,是IEEE制定的,而不是J2EE标准:)
3.使用strictfp关键字的目的,是保证平台移植之后,浮点运算结果是一致的。
六、其他参考资料
《IEEE 754 浮点数的表示精度探讨》
《Java 算术运算与移植性》http://bornlone.javaeye.com/blog/310574