Java浮点数

标签: java 浮点数 | 发表时间:2011-09-26 16:40 | 作者:.翻译官. d0ngd0ng
出处:http://www.yeeyan.org

译者 .翻译官.

扑朔迷离的Java浮点数

Thomas Wang, 2000年3月

最后更新于2000年9月


摘要

Java浮点数的定义大体上遵守了二进制浮点运算标准(即IEEE 754标准)。IEEE 754标准提供了浮点数无穷,负无穷,负零和非数字(Not a number,简称NaN)的定义。在Java开发方面,这些东西经常被多数程序员混淆。

在本文中,我们将讨论计算这些特殊的浮点数相关的结果。我们也将指出一些通常的Java浮点数的陷阱。


前言

在Java编程语言中提供了两种内置的表示浮点数的类别:float和double。float占用4字节的存储空间,有23位用来表示精度;double占用8字节的存储空间,有52位用来表示精度。【1】

3.0d是个双精度浮点数常量。3.0,或者3.0f是单精度的浮点数常量。

float f = 3.0f;

double d = 3.0d;

Java核心库提供了两个包装类,java.lang.Float和java.lang.Double。这两个类使得浮点型对象也能够被存储在比如哈希表之类的Java集合中,并且这两个类也提供了一些解析和转换的帮助方法。

  

Float ff = new Float(1.0f); // 创建一个Java "Float" 对象

Double dd = new Double(2.0d); // 创建一个Java "Double" 对象


特殊的浮点数实体

非数字(Not A Number)

"NaN"指"not a number",即非数字。在浮点数运算中,如果有一些输入参数导致这个运算产生某种没有定义的结果,那么"NaN"就产生了。比如,0.0除以0.0就是没有算数定义的,负数的平方根也是没有算数定义的。

0.0 / 0.0   ->  NaN

Math.sqrt(-2.0)  ->  NaN

涉及到NaN的运算

Double.NaN + Double.NaN  ->  NaN

Float.NaN + 2.0  ->  NaN

Float.NaN * 3.0  ->  NaN  

(0.0 / 0.0) * Float.POSITIVE_INFINITY  ->  NaN

Math.abs(0.0 / 0.0) -> NaN

(int) (Double.NaN) -> 0

所有涉及到"NaN"的布尔操作都返回false这个值。

Double.NaN > 1.0  ->  false

Double.NaN < 1.0  ->  false

Double.NaN == 1.0  ->  false

Float.NaN < -3.0  ->  false

Float.NaN > Float.POSITIVE_INFINITY  ->  false

Float.NaN < Float.POSITIVE_INFINITY  ->  false

(0.0 / 0.0) == (0.0 / 0.0)  ->  false
无穷
(Infinity)

如果一个浮点运算产生了一个大到没有办法正常表示出来的浮点数,那么无穷就产生了。无穷是个特殊的值表示概念上的无穷大。

1.0 / 0.0  ->  Infinity

涉及到Infinity的运算

Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY  ->  true

Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY  ->  true

(1.0 / 0.0) + 2.0  -> Infinity

(1.0 / 0.0) * 0.5  -> Infinity

(1.0 / 0.0) * 0.0  ->  NaN

(1.0 / 0.0) + (1.0 / 0.0)  ->  Infinity

(1.0 / 0.0) * (1.0 / 0.0)  ->  Infinity

(1.0 / 0.0) - (1.0 / 0.0)  ->  NaN

(1.0 / 0.0) / (1.0 / 0.0)  ->  NaN

(int) (1.0 / 0.0)  ->  2147483647

负无穷(Negative Infinity)

如果一个浮点运算产生了一个没有办法正常表示出来的极端负浮点数,那么负无穷就产生了。负无穷是个特殊的值表示概念上的负无穷大。

-1.0 / 0.0  ->  -Infinity

涉及到Negative Infinity的运算

- Double.POSITIVE_INFINITY == Double.NEGATIVE_INFINITY  ->  true

Float.NEGATIVE_INFINITY < 1.0  ->  true

(-1.0 / 0.0 ) + 2.0  ->  -Infinity

(-1.0 / 0.0) == (-1.0 / 0.0)  ->  true

(-1.0 / 0.0) - (-1.0 / 0.0)  ->  NaN

(-1.0 / 0.0) * 0.5  ->  -Infinity

(-1.0 / 0.0) * 0.0  ->  NaN

Math.abs(-1.0 / 0.0)  ->  Infinity

(int) (-1.0 / 0.0)  ->  -2147483648

负零(Negative Zero)

当一个浮点数运算产生了一个无限接近0并且没有办法正常表示出来的负浮点数,那么负零就产生了。

-2.0 / Float.POSITIVE_INFINITY  ->  -0.0

“-0.0”在数值上等于“0.0”,但是一些涉及到“-0.0”的运算在和相同的涉及到“0.0”的运算又不一样。

(-0.0) == 0.0  ->  true

2.0 / (0.0)  ->  Infinity

2.0 / (-0.0)  ->  -Infinity

涉及到Negative Zero的运算

0.0 > (-0.0)  ->  false

(-0.0) * (-0.0)  ->  0.0

3.0 + (-0.0)  ->  3.0

4.0 * (-0.0)  ->  -0.0

0.0 - 0.0  ->  0.0

(-0.0) - (-0.0)  ->  0.0

(-0.0) + (-0.0)  ->  -0.0

(-0.0) + 0.0  ->  0.0

0.0 + (-0.0)  ->  0.0

(-0.0) - 0.0  ->  -0.0

- (-0.0)  ->  0.0

- (0.0)  ->  -0.0

0.0 / (-0.0)  ->  NaN


Java的Float对象的比较

比较两个Java的Float对象和比较Java的两个浮点数值有不同的语义,回忆一下float类型是Java的一种基本类型,而java.lang.Float类是“Object”类的子类。

一个“NaN”值并不等于它本身,但是一个值为“NaN”的包装对象却和它本身是相等的(译注:通过Float.equals方法比较)。只所以这样定义是因为不这样定义的话一个值为“NaN”的Float对象将无法正确的从hash table中获取出来(译注:参看HashMap或者Hashtable的get方法实现)。

(new Float(0.0 / 0.0)).equals(new Float(0.0 / 0.0))  ->  true

对于java.lang.Float类里面的值是按照从低到高的顺序排序的,负无穷大,负数,负零,零,正数,无穷大,非数值。java.lang.Double也是如此。

(new Float(0.0)).equals(new Float(-0.0))  ->  false

(new Float(-1.0 / 0.0)).compareTo(new Float(-3.0))  ->  -1

(new Float(-3.0)).compareTo(new Float(-0.0))  ->  -1

(new Float(-0.0)).compareTo(new Float(0.0))  ->  -1

(new Float(0.0)).compareTo(new Float(3.0))  ->  -1

(new Float(3.0)).compareTo(new Float(1.0 / 0.0))  ->  -1

(new Float(1.0 / 0.0)).compareTo(new Float(0.0 / 0.0))  ->  -1


一些存在的陷阱

实现abs方法的错误方式

如下是一种错误的方式:

(f >= 0.0) ? f : - f

如果f取值为-0.0将会出现什么情况?因为0.0是和-0.0相等的,上面的表达式将返回-0.0本身,也就是-0.0,相比正确的结果0.0来说这是错误的。

正确的形式如下:

(f <= 0.0) ? 0.0 - f : f

当你在优化浮点数表达式的时候一定要小心。比如,如果你将上面的表达式优化成下面的这种:

(f <= 0.0) ? - f : f

那么你又引入了一个BUG,它会导致abs(0.0)返回-0.0。

优化浮点数的危险性

我们已经领略过了一种情况,(0.0-f)和(-f)并不相等。!(a < b)并不意味着(a >= b)。

! (1.0 < NaN)  ->  true

(1.0 >= NaN)  ->  false

《The Java Language Specification》(译注:这里指的是第一版)这本书308页详细讲述了优化浮点数表达式的危险性。

假定一个浮点数的值和它自身是相等的

“NaN”的值和他自身并不相等。类似的原因,假定一个浮点数减去它自身一定为0.0也是不正确的。“NaN”-“NaN”还是“NaN”,“Infinity”-“Infinity”是“NaN”,尽管两个正无穷是相等的。

不对NaN作检查

public employee(float monthly_salary){

    if (monthly_salary < 0.0)

        throw IllegalArgumentException("illegal monthly salary");

    this.yearly_salary = 12.0 * monthly_salary;

}

上面的一段代码当你传入“NaN”作为参数时并不会捕获到那个错误,但是下面的一段代码会。

public employee(float monthly_salary){

    if (monthly_salary >= 0.0)  {

        this.yearly_salary = 12.0 * monthly_salary;

    }  else

        throw IllegalArgumentException("illegal monthly salary");

}

看看修复后的代码,如果传入的参数是“Infinity”我们该怎么做?根据API的规范,我们可以做甚多事情,关键点是每次当我们处理浮点数是,我们一定要想想“NaN”,“Infinity”,“-Infinity”,“-0.0”这些情况该如何处理。


引用文献

(译注:这里是作者引用文献,在这里没有进行翻译,请自行查阅相关资料)

James Gosling, Bill Joy, Guy Steele, "The Java Language Specification", Addison Wesley, (1996)
IEEE, "ANSI/IEEE Std 754-1985, An American National Standard IEEE Standard for Binary Floating-Point Arithmetic", (1985)


译者添加

【1】float是32位的,第一位是符号位,以二进制0和1表示(0为正,1为负),最后有8位阶码,中间就有23位精确数值可以填写,所以float小数点后面最多包含23位小数。double是64位。第一位也是符号位,阶码为11位,所以中间有52位精确数值填写。

译者理解的过程得到了其他人的一些帮助,感谢你们。另外由于个人能力问题,翻译出来的文章可能没有完全把握住作者的意思,如有误导请指正,版权规原作者所有,译者仅保留该简体中文版本的署名权利(http://guoh.org/lifelog/)。

原文链接:http://www.concentric.net/~Ttwang/tech/javafloat.htm

相关 [java 浮点数] 推荐:

Java浮点数

- d0ngd0ng - 译言-电脑/网络/数码科技
Thomas Wang, 2000年3月. Java浮点数的定义大体上遵守了二进制浮点运算标准(即IEEE 754标准). IEEE 754标准提供了浮点数无穷,负无穷,负零和非数字(Not a number,简称NaN)的定义. 在Java开发方面,这些东西经常被多数程序员混淆. 在本文中,我们将讨论计算这些特殊的浮点数相关的结果.

Java中的锁(Locks in Java)

- - 并发编程网 - ifeve.com
原文链接 作者:Jakob Jenkov 译者:申章 校对:丁一. 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂. 因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字( 译者注:这说的是Java 5之前的情况).

Java PaaS 对决

- 呆瓜 - IBM developerWorks 中国 : 文档库
本文为 Java 开发人员比较了三种主要的 Platform as a Service (PaaS) 产品:Google App Engine for Java、Amazon Elastic Beanstalk 和 CloudBees RUN@Cloud. 它分析了每种服务独特的技术方法、优点以及缺点,而且还讨论了常见的解决方法.

Qt——转战Java?

- - 博客 - 伯乐在线
编者按:事实上,在跨平台开发方面,Qt仍是最好的工具之一,无可厚非,但Qt目前没有得到任何主流移动操作系统的正式支持. 诺基亚的未来计划,定位非常模糊,这也是令很多第三方开发者感到失望,因此将导致诺基亚屡遭失败的原因. Qt的主要开发者之一Mirko Boehm在博客上强烈讽刺Nokia裁了Qt部门的决定,称其为“绝望之举”,而非“策略变更”.

java 验证码

- - ITeye博客
// 创建字体,字体的大小应该根据图片的高度来定. // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到. // randomCode用于保存随机产生的验证码,以便用户登录后进行验证. // 随机产生codeCount数字的验证码. // 得到随机产生的验证码数字. // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同.

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

java面试题

- - Java - 编程语言 - ITeye博客
 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面. 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节. 抽象包括两个方面,一是过程抽象,二是数据抽象.  继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法. 对象的一个新类可以从现有的类中派生,这个过程称为类继承.

Java使用memcached

- - 互联网 - ITeye博客
首先到 http://danga.com/memcached下载memcached的windows版本和java客户端jar包,目前最新版本是memcached-1.2.1-win32.zip和java_memcached-release_1.6.zip,分别解压后即可. 然后是安装运行memcached服务器,我们将memcached-1.2.1-win32.zip解压后,进入其目录,然后运行如下命令:c:>;memcached.exe -d install
c:>memcached.exe -l 127.0.0.1 -m 32 -d start.

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.

java 缩略图

- - 开源软件 - ITeye博客
文章说明:根据用户上传的图片按等比例生成相应的的缩略图,两小例笔记. 第一、java-image-scaling 开源小工具生成图片缩略图. Maven地址: http://mvnrepository.com/artifact/com.mortennobel/java-image-scaling/.