`
tianlihu
  • 浏览: 314054 次
  • 性别: Icon_minigender_1
  • 来自: 石家庄
社区版块
存档分类
最新评论

扑朔迷离的Java浮点数

 
阅读更多
摘要


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 精确的浮点数运算java 精确的浮点数运算

    java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java ...

    java 浮点数举例

    java 浮点数举例java 浮点数举例java 浮点数举例

    java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类

    java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 ...

    JavaIEEE754浮点数的转换方法

    [Java]IEEE754浮点数的转换方法,方法都写好了,直接复制过去就可以用了,就这么简单!

    Java浮点数.pdf

    Java中的浮点数类型主要包括两种:float和double。它们分别占据了4个字节(32位)和8个字节(64位)的内存空间。这两种类型在计算机内部的存储方式遵循IEEE 754浮点数表示标准,这是一种高效且广泛使用的浮点数存储...

    Java输入浮点数分别输出整数部分和小数部分

    Java输入浮点数分别输出整数部分和小数部分

    JAVA中浮点数的运算

    在Java编程语言中,浮点数的运算涉及到的是数值计算,特别是小数部分的处理。浮点数在计算机内部是以二进制表示的,这与我们通常使用的十进制系统不同,因此在进行浮点数运算时可能会出现精度丢失的问题。这篇博客...

    疯狂Java讲义 浮点数转换成人民币读法

    Java提供了Math.floor()函数可以获取浮点数的整数部分,小数部分则通过减去整数部分得到。整数部分可以通过除以相应的货币单位(元、角、分、厘、毫、丝)转换为人民币的读法。小数部分则需要通过乘以适当的倍数...

    java精确计算浮点数工具类

    Java对浮点数的计算是不精确的,比如0.05+0.01结果不是0.06,而是0.060000000000000005,更有甚者,一个数除以0.0,Java是不会抛异常,而是得出无穷大的结果.本工具类解决了上述问题.该类提供了加减乘除四则运算的精确计算...

    Java中如何正确进行浮点数运算

    在Java编程中,浮点数运算常常会遇到精度问题,这是由于浮点数在计算机内部的存储方式导致的。为了确保浮点数运算的准确性,开发者需要采取特定的方法。以下将详细阐述这个问题,并提供正确的处理策略。 首先,理解...

    用java从文件中读取浮点数

    在Java编程语言中,从文件中读取浮点数是一个常见的操作需求,尤其是在处理大量数据或进行数据分析时。本文将详细解析如何利用Java标准库中的类来实现这一功能,包括必要的异常处理,确保代码的健壮性和实用性。 ##...

    Java中实现浮点数的精确计算

    ### Java中实现浮点数的精确计算 在Java编程中,使用`float`和`double`类型进行浮点数计算时经常会遇到精度丢失的问题。这是因为浮点数在计算机内部是以二进制形式存储的,而某些十进制小数无法用二进制精确表示,...

    java中浮点数的应用

    该案例演示了浮点数的应用

    Java源码获取浮点数类型的最大最小值

    在Java编程语言中,浮点数类型包括`float`和`double`,它们分别用于存储单精度和双精度浮点数。了解如何在源码级别获取这两种类型的最大和最小值对于编写高效且精确的代码至关重要。本文将深入探讨Java中获取浮点数...

    JAVA浮点数计算精度损失底层原理与解决方案

    浮点数计算在Java中常常会遇到精度损失的问题,这是由于计算机硬件的限制以及浮点数在二进制表示上的特性所导致的。在Java中,浮点数类型包括单精度浮点数`float`(4字节,32位)和双精度浮点数`double`(8字节,64...

    Java中浮点数精度问题的解决方法

    在Java编程中,浮点数精度问题是一个常见的困扰,尤其是在进行数学计算或财务计算时。浮点数精度问题主要体现在,当使用float或double类型的数值进行运算时,结果并不总是与预期相符,这主要是由于计算机内部对...

    java.math包下计算浮点数和整数的类的实例

    Java.math 包下计算浮点数和整数的类的实例 Java.math 包是 Java 语言中用于数学运算的包,提供了多种数学类,用于实现高精度的浮点数和整数运算。在本篇文章中,我们将详细介绍 Java.math 包下计算浮点数和整数的...

    midlet java 浮点运算函数

    做midlet开发,如果需要用到乘幂运算,会用到这个函数。 文件名是float.java 里面包含了pow函数等midlet原本不具备的数学运算函数。

    浮点数(单精度浮点数,双精度浮点数)

    浮点数(单精度浮点数,双精度浮点数) 浮点数是一种数字表示方法,用于近似表示任意实数。在计算机中,浮点数由一个整数或定点数(即尾数)乘以某个基数(通常是 2)的整数次幂得到。这种表示方法类似于基数为 10 ...

    ModbusRTU协议中浮点数转换

    本文将深入探讨如何在C#中处理Modbus RTU协议中的浮点数转换,包括浮点数到整型以及整型到浮点数的转换,并简要介绍Modbus RTU协议。 首先,让我们理解一下Modbus RTU协议。Modbus是基于串行通信的一种简单协议,...

Global site tag (gtag.js) - Google Analytics