- 浏览: 5166561 次
- 性别:
- 来自: 天津
博客专栏
-
实战 Groovy
浏览量:29350
文章分类
- 全部博客 (639)
- 代码之谜 (6)
- JavaScript quirks (5)
- 程序员 (92)
- Java (93)
- BT编程 (7)
- html/css (64)
- Groovy&Grails (42)
- Android (20)
- C/C++ (5)
- PHP/Perl/Python (46)
- 经典文章 (51)
- CodeIgniter (14)
- JQuery (10)
- 笑话 (4)
- 其他 (32)
- javascript (69)
- 云计算 (0)
- html5 (7)
- 面试 (8)
- google (3)
- nosql (2)
- nodejs (11)
- go (5)
- erlang (1)
- 小常识 (3)
- 冷知识 (5)
- database (4)
- web (12)
- 架构 (12)
- Exception (0)
最新评论
-
jqw1992:
https://www.chromefor.com/libra ...
[福利] 开发者必备的 Chrome 插件——ChromeSnifferPlus -
litjerk:
初步算了一下,目前最最精简的Win98版是5M,他5个小时多敲 ...
让人目瞪口呆的三位世界级电脑大师 -
379855529:
。。似乎重点没说NIO啊,前面基础只是铺垫的很好的,可是我要的 ...
Java NIO与IO的详细区别(通俗篇) -
springmvc_springjpa:
spring mvc demo教程源代码下载,地址:http: ...
一步步开发 Spring MVC 应用 -
匡建武:
Good
四个程序员的一天
虽然几乎每种处理器和编程语言都支持浮点运算,但大多数程序员很少注意它。这容易理解 ― 我们中大多数很少需要使用非整数类型。除了科学计算和偶尔的计时测试或基准测试程序,其它情况下几乎都用不着它。同样,大多数开发人员也容易忽略 java.math.BigDecimal
所提供的任意精度的小数 ― 大多数应用程序不使用它们。然而,在以整数为主的程序中有时确实会出人意料地需要表示非整型数据。例如,JDBC 使用 BigDecimal
作为 SQL DECIMAL
列的首选互换格式。
Java 语言支持两种基本的浮点类型: float
和 double
,以及与它们对应的包装类 Float
和 Double
。它们都依据 IEEE 754 标准,该标准为 32 位浮点和 64 位双精度浮点二进制小数定义了二进制标准。
IEEE 754 用科学记数法以底数为 2 的小数来表示浮点数。IEEE 浮点数用 1 位表示数字的符号,用 8 位来表示指数,用 23 位来表示尾数,即小数部分。作为有符号整数的指数可以有正负之分。小数部分用二进制(底数 2)小数来表示,这意味着最高位对应着值 ?(2 -1),第二位对应着 ?(2 -2),依此类推。对于双精度浮点数,用 11 位表示指数,52 位表示尾数。IEEE 浮点值的格式如图 1 所示。
图 1. IEEE 754 浮点数的格式
因为用科学记数法可以有多种方式来表示给定数字,所以要规范化浮点数,以便用底数为 2 并且小数点左边为 1 的小数来表示,按照需要调节指数就可以得到所需的数字。所以,例如,数 1.25 可以表示为尾数为 1.01,指数为 0: (-1) 0*1.01 2*2 0
数 10.0 可以表示为尾数为 1.01,指数为 3: (-1) 0*1.01 2*2 3
除了编码所允许的值的标准范围(对于 float
,从 1.4e-45 到 3.4028235e+38),还有一些表示无穷大、负无穷大、 -0
和 NaN(它代表“不是一个数字”)的特殊值。这些值的存在是为了在出现错误条件(譬如算术溢出,给负数开平方根,除以 0
等)下,可以用浮点值集合中的数字来表示所产生的结果。
这些特殊的数字有一些不寻常的特征。例如, 0
和 -0
是不同值,但在比较它们是否相等时,被认为是相等的。用一个非零数去除以无穷大的数,结果等于 0
。特殊数字 NaN 是无序的;使用 ==
、 <
和 >
运算符将 NaN 与其它浮点值比较时,结果为 false
。如果 f
为 NaN,则即使 (f == f)
也会得到 false
。如果想将浮点值与 NaN 进行比较,则使用 Float.isNaN()
方法。表 1 显示了无穷大和 NaN 的一些属性。
表达式 | 结果 |
Math.sqrt(-1.0) |
-> NaN |
0.0 / 0.0 |
-> NaN |
1.0 / 0.0 |
-> 无穷大 |
-1.0 / 0.0 |
-> 负无穷大 |
NaN + 1.0 |
-> NaN |
无穷大 + 1.0 |
-> 无穷大 |
无穷大 + 无穷大 |
-> 无穷大 |
NaN > 1.0 |
-> false |
NaN == 1.0 |
-> false |
NaN < 1.0 |
-> false |
NaN == NaN |
-> false |
0.0 == -0.01 |
-> true |
使事情更糟的是,在基本 float
类型和包装类 Float
之间,用于比较 NaN 和 -0
的规则是不同的。对于 float
值,比较两个 NaN 值是否相等将会得到 false
,而使用 Float.equals()
来比较两个 NaN Float
对象会得到 true
。造成这种现象的原因是,如果不这样的话,就不可能将 NaN Float
对象用作 HashMap
中的键。类似的,虽然 0
和 -0
在表示为浮点值时,被认为是相等的,但使用 Float.compareTo()
来比较作为 Float
对象的 0
和 -0
时,会显示 -0
小于 0
。
|
|
由于无穷大、NaN 和 0
的特殊行为,当应用浮点数时,可能看似无害的转换和优化实际上是不正确的。例如,虽然好象 0.0-f
很明显等于 -f
,但当 f
为 0
时,这是不正确的。还有其它类似的 gotcha,表 2 显示了其中一些 gotcha。
这个表达式…… | 不一定等于…… | 当…… |
0.0 - f |
-f |
f 为 0
|
f < g |
! (f >= g) |
f 或 g 为 NaN |
f == f |
true |
f 为 NaN |
f + g - g |
f |
g 为无穷大或 NaN |
浮点运算很少是精确的。虽然一些数字(譬如 0.5
)可以精确地表示为二进制(底数 2)小数(因为 0.5
等于 2 -1),但其它一些数字(譬如 0.1
)就不能精确的表示。因此,浮点运算可能导致舍入误差,产生的结果接近 ― 但不等于 ― 您可能希望的结果。例如,下面这个简单的计算将得到 2.600000000000001
,而不是 2.6
:
double s=0; for (int i=0; i<26; i++) s += 0.1; System.out.println(s); |
类似的, .1*26
相乘所产生的结果不等于 .1
自身加 26 次所得到的结果。当将浮点数强制转换成整数时,产生的舍入误差甚至更严重,因为强制转换成整数类型会舍弃非整数部分,甚至对于那些“看上去似乎”应该得到整数值的计算,也存在此类问题。例如,下面这些语句:
double d = 29.0 * 0.01; System.out.println(d); System.out.println((int) (d * 100)); |
将得到以下输出:
0.29 28 |
这可能不是您起初所期望的。
|
|
由于存在 NaN 的不寻常比较行为和在几乎所有浮点计算中都不可避免地会出现舍入误差,解释浮点值的比较运算符的结果比较麻烦。
最好完全避免使用浮点数比较。当然,这并不总是可能的,但您应该意识到要限制浮点数比较。如果必须比较浮点数来看它们是否相等,则应该将它们差的绝对值同一些预先选定的小正数进行比较,这样您所做的就是测试它们是否“足够接近”。(如果不知道基本的计算范围,可以使用测试“abs(a/b - 1) < epsilon”,这种方法比简单地比较两者之差要更准确)。甚至测试看一个值是比零大还是比零小也存在危险 ―“以为”会生成比零略大值的计算事实上可能由于积累的舍入误差会生成略微比零小的数字。
NaN 的无序性质使得在比较浮点数时更容易发生错误。当比较浮点数时,围绕无穷大和 NaN 问题,一种避免 gotcha 的经验法则是显式地测试值的有效性,而不是试图排除无效值。在清单 1 中,有两个可能的用于特性的 setter 的实现,该特性只能接受非负数值。第一个实现会接受 NaN,第二个不会。第二种形式比较好,因为它显式地检测了您认为有效的值的范围。
清单 1. 需要非负浮点值的较好办法和较差办法
// Trying to test by exclusion -- this doesn't catch NaN or infinity public void setFoo(float foo) { if (foo < 0) throw new IllegalArgumentException(Float.toString(f)); this.foo = foo; } // Testing by inclusion -- this does catch NaN public void setFoo(float foo) { if (foo >= 0 && foo < Float.INFINITY) this.foo = foo; else throw new IllegalArgumentException(Float.toString(f)); } |
一些非整数值(如几美元和几美分这样的小数)需要很精确。浮点数不是精确值,所以使用它们会导致舍入误差。因此,使用浮点数来试图表示象货币量这样的精确数量不是一个好的想法。使用浮点数来进行美元和美分计算会得到灾难性的后果。浮点数最好用来表示象测量值这类数值,这类值从一开始就不怎么精确。
|
|
从 JDK 1.3 起,Java 开发人员就有了另一种数值表示法来表示非整数: BigDecimal
。 BigDecimal
是标准的类,在编译器中不需要特殊支持,它可以表示任意精度的小数,并对它们进行计算。在内部,可以用任意精度任何范围的值和一个换算因子来表示 BigDecimal
,换算因子表示左移小数点多少位,从而得到所期望范围内的值。因此,用 BigDecimal
表示的数的形式为 unscaledValue*10 -scale
。
用于加、减、乘和除的方法给 BigDecimal
值提供了算术运算。由于 BigDecimal
对象是不可变的,这些方法中的每一个都会产生新的 BigDecimal
对象。因此,因为创建对象的开销, BigDecimal
不适合于大量的数学计算,但设计它的目的是用来精确地表示小数。如果您正在寻找一种能精确表示如货币量这样的数值,则 BigDecimal
可以很好地胜任该任务。
如浮点类型一样, BigDecimal
也有一些令人奇怪的行为。尤其在使用 equals()
方法来检测数值之间是否相等时要小心。 equals()
方法认为,两个表示同一个数但换算值不同(例如, 100.00
和 100.000
)的 BigDecimal
值是不相等的。然而, compareTo()
方法会认为这两个数是相等的,所以在从数值上比较两个 BigDecimal
值时,应该使用 compareTo()
而不是 equals()
。
另外还有一些情形,任意精度的小数运算仍不能表示精确结果。例如, 1
除以 9
会产生无限循环的小数 .111111...
。出于这个原因,在进行除法运算时, BigDecimal
可以让您显式地控制舍入。 movePointLeft()
方法支持 10 的幂次方的精确除法。
SQL-92 包括 DECIMAL
数据类型,它是用于表示定点小数的精确数字类型,它可以对小数进行基本的算术运算。一些 SQL 语言喜欢称此类型为 NUMERIC
类型,其它一些 SQL 语言则引入了 MONEY
数据类型,MONEY 数据类型被定义为小数点右侧带有两位的小数。
如果希望将数字存储到数据库中的 DECIMAL
字段,或从 DECIMAL
字段检索值,则如何确保精确地转换该数字?您可能不希望使用由 JDBC PreparedStatement
和 ResultSet
类所提供的 setFloat()
和 getFloat()
方法,因为浮点数与小数之间的转换可能会丧失精确性。相反,请使用 PreparedStatement
和 ResultSet
的 setBigDecimal()
及 getBigDecimal()
方法。
对于 BigDecimal
,有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的 String
表示作为输入。要小心使用 BigDecimal(double)
构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String
的构造函数。
对于 BigDecimal
,有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的 String
表示作为输入。要小心使用 BigDecimal(double)
构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String
的构造函数。
如果使用 BigDecimal(double)
构造函数不恰当,在传递给 JDBC setBigDecimal()
方法时,会造成似乎很奇怪的 JDBC 驱动程序中的异常。例如,考虑以下 JDBC 代码,该代码希望将数字 0.01
存储到小数字段:
PreparedStatement ps = connection.prepareStatement("INSERT INTO Foo SET name=?, value=?"); ps.setString(1, "penny"); ps.setBigDecimal(2, new BigDecimal(0.01)); ps.executeUpdate(); |
在执行这段似乎无害的代码时会抛出一些令人迷惑不解的异常(这取决于具体的 JDBC 驱动程序),因为 0.01
的双精度近似值会导致大的换算值,这可能会使 JDBC 驱动程序或数据库感到迷惑。JDBC 驱动程序会产生异常,但可能不会说明代码实际上错在哪里,除非意识到二进制浮点数的局限性。相反,使用 BigDecimal("0.01")
或 BigDecimal(1, 2)
构造 BigDecimal
来避免这类问题,因为这两种方法都可以精确地表示小数。
|
|
在 Java 程序中使用浮点数和小数充满着陷阱。浮点数和小数不象整数一样“循规蹈矩”,不能假定浮点计算一定产生整型或精确的结果,虽然它们的确“应该”那样做。最好将浮点运算保留用作计算本来就不精确的数值,譬如测量。如果需要表示定点数(譬如,几美元和几美分),则使用 BigDecimal
。
发表评论
-
Java NIO与IO的详细区别(通俗篇)
2014-01-02 10:51 69426内核空间、用户空间 ... -
Java EE 单元测试
2013-09-12 10:56 4781觉得测试 Java EE 应用程序太困难、不方便或者太复杂? ... -
JUnit 测试建议
2013-07-22 09:04 3482以下是对JUnit实践的一 ... -
老生常谈:面试算法有必要吗?
2013-04-01 09:55 32764声明:虽然发表在愚人节,但是文章内容很严肃。 前几天在知 ... -
一步步开发 Spring MVC 应用
2013-02-23 10:53 10352Spring MVC 框架 Spring 框 ... -
「译」Java集合框架系列教程四:Set接口
2013-01-18 13:20 2531原文:The Set Interface 译文:Java集 ... -
「译」Java集合框架系列教程三:Collection接口
2013-01-18 13:16 2371原文:The Collection Interface 译 ... -
「译」Java集合框架系列教程二:集合接口
2013-01-18 13:13 2012原文:http://docs.oracle.c ... -
Java集合框架系列教程一:集合框架简介
2013-01-18 12:47 3031原文:http://docs.oracle.com/jav ... -
Java泛型-类型擦除
2012-12-05 15:48 14618一、概述 Java泛型在使用过程有诸多的 ... -
千万不要把 bool 当成函数参数
2012-11-16 08:33 12494我们有很多 Coding Style 或 代码规范。 但这一条 ... -
网站建设中关于eclipse启动参数的优化
2012-08-23 09:23 2503在网站建设中,经常用 ... -
Eclipse程序员要掌握的常用快捷键
2012-08-22 09:33 10062判断一个人的编程水平,就看他用键盘多,还是鼠标多。用键 ... -
java中equals和==的区别
2012-08-15 16:31 2032值类型是存储在内存中的堆栈(简称栈),而引用类型的变量在 ... -
名词王国里的死刑(翻译) - A Story of Hello World
2012-07-23 10:43 11812翻译自Steve Yegge的大 ... -
好代码是廉价的代码
2012-07-19 08:03 3296长久以来我一直主张:好代码是廉价的代码。 当我跟做开发 ... -
只要一个返回语句
2012-05-18 13:18 1821别再这样写了: publ ... -
java编程的78条黄金法则
2012-05-16 12:57 2365创建和销毁对象 1、考虑用静态工厂方法(返回类的实例的 ... -
for 循环为何可恨?
2012-05-15 12:44 1621Java的闭包(Closure)特征最近成为了一个热门话 ... -
Java 8 新功能
2012-05-02 08:48 1902我们已经急不可待想知道Java 8可用的新功能,其中大部 ...
相关推荐
在本篇文章中,我们将深入探讨IEEE754浮点数的结构、数值转换以及如何在不同表示之间进行转换。 首先,让我们了解IEEE754浮点数的基本结构。浮点数由三部分组成:符号位、指数和尾数。对于单精度(32位)和双精度...
标题中的“模拟浮点数”指的是在编程中使用整数或者其他数据类型来模拟浮点数的行为,这通常在处理浮点数精度问题、平台兼容性或者优化计算性能时发生。浮点数在计算机中是以二进制表示的,由于二进制无法精确表示...
本篇文章将深入探讨浮点数精度问题的原因、影响以及如何在实践中处理这些问题。 首先,我们需要理解浮点数在计算机中的存储方式。浮点数遵循IEEE 754标准,分为单精度(32位)和双精度(64位)两种。它们由三部分...
标题《学习java的第三天》揭示了文章是关于Java编程语言学习的进阶教程,特别关注在学习的第三天所涉及的主题。该文档深入浅出地介绍了Java中的运算符及其优先级,算术运算符、关系运算符和自增自减运算符等。在描述...
### Java基本数据类型及其封装类 Java是一种广泛使用的面向对象编程语言,支持多种数据类型来满足不同的编程需求。其中,基本数据类型是最基础且最常用的元素之一。...理解这些基本概念对于学习 Java 编程至关重要。
### Java正则表达式学习 #### 一、简介 在Java编程中,正则表达式是一种非常实用且强大的工具,用于处理字符串模式匹配与搜索替换等操作。通过本篇文章,我们将快速入门Java中的正则表达式,并掌握其基本用法。 #...
本篇文章将详细探讨Java环境下实现三种画线方法:中点画线法、Bresenham算法以及数值微分算法。 首先,我们来看中点画线法。这种方法基于几何原理,适用于画出任意斜率的线段。其基本思想是沿着线段的中垂线进行...
本篇文章将详细探讨如何使用Java来解析SO(ELF)文件,包括文件头部、程序头部表和节区头部表等关键组成部分。 首先,我们需要理解ELF文件格式的基本概念。ELF是一种广泛使用的Unix和类Unix系统中的可执行文件、...
### Java8 知识点详解 ...通过本篇文章对Java8基础知识的介绍,我们不仅了解了Java语言的基本概念,也学习了一些实用的编码技巧。希望这些内容能够帮助初学者快速入门Java编程,并为进一步学习打下坚实的基础。
本篇文章将深入探讨Java中一些常用的函数API,尤其是那些在日常开发中频繁使用的。 首先,我们要关注的是`java.util.Date`和`java.text.SimpleDateFormat`类,这两个类常用于日期和时间的操作。`Date`类代表了一个...
文章通过一个复杂的算术类示例来说明,即使是在流体动力学这样的复杂计算中,精确的可重复性也可能不是必需的。例如,在流体流动模拟中,由于数据的近似和计算误差,即便是最细微的变化也会导致最终结果的显著差异。...
### Java随机生成数字和字母知识点解析 ...通过本篇文章的学习,相信您已经掌握了如何使用Java生成随机的字母。在实际应用中,随机生成功能是非常实用且强大的,掌握这项技能将有助于解决许多实际问题。
本篇文章详细介绍了如何利用Java编写一个简单的摇骰子游戏,包括了枚举类型、随机数生成、条件判断等基础知识的应用。通过学习这段代码,可以加深对Java语言基础语法的理解,并掌握编写简单游戏的基本技巧。
《Java开发全面通》还提供了丰富的实例与资源,帮助学习者更好地理解和运用所学知识。 1. **实例代码** - 通过实际的例子演示各种语法和概念的应用,例如使用`BufferedReader`和`BufferedWriter`进行文件读写。 -...
本篇文章将详细讲解如何利用Java编程语言实现这一功能,主要基于给定的"经纬度计算"标签以及压缩包中的"Caculate.java"源码。 首先,我们了解地球坐标系统。在地理坐标系统中,每个位置由经度和纬度来表示,经度...
JavaFocus:hammer: Java学习重点 博客文章 样例语言基础Java如何实现平台无关性的JDK、JRE、JVM的关系局部变量为什么需要初始化浮点数为什么不精确BigDecimal的坑数据转换的坑运算符需要注意的递归的效率问题编码,...
这个任务通常使用哈弗辛公式(Haversine formula)来实现,该公式基于球面三角学,可以计算地球上任意两点间的大圆距离。 哈弗辛公式的基本思路是将地球视为一个完美的球体,然后利用经纬度坐标来计算两点之间的...
Java Util是Java编程语言中的一个核心包,包含了各种实用工具类,这些类提供了大量的静态方法,用于处理数组、集合、日期时间、字符流等常见...通过学习和应用Java Util,开发者可以编写出更加高效、健壮的Java程序。
因此,Java代码中应使用浮点数以避免这种问题,如: ```java double fahrenheit = 212.0; double celsius = (fahrenheit - 32) * 5.0 / 9.0; ``` 最后,文章提到了使用快照图(Snapshot diagrams)来理解程序运行...