- 浏览: 47724 次
- 性别:
- 来自: 上海
最近访客 更多访客>>
最新评论
-
ivyloo:
不错,言简意赅,谢谢
serialVersionUID的作用 -
gjs622520:
今天碰到你提问过的问题netbeans JAVADB_DRIV ...
未启用对系统目录的特殊更新。系统管理员必须重新配置 SQL Server 以允许这种操作
许多程序员在其整个开发生涯中都不曾使用定点或浮点数,可能的例外是,偶尔在计时测试或基准测试程序中会用到。Java语言和类库支持两类非整数类型 ― IEEE 754 浮点(
float
和double
,包装类(wrapper class)为Float
和Double
),以及任意精度的小数(java.math.BigDecimal
)。在本月的 Java 理论和实践中,Brian Goetz 探讨了在 Java 程序中使用非整数类型时一些常碰到的陷阱和“gotcha”。请在本文的 论坛上提出您对本文的想法,以飨笔者和其他读者。(您也可以单击本文顶部或底部的讨论来访问论坛)。<!----><!----><!---->
虽然几乎每种处理器和编程语言都支持浮点运算,但大多数程序员很少注意它。这容易理解 ― 我们中大多数很少需要使用非整数类型。除了科学计算和偶尔的计时测试或基准测试程序,其它情况下几乎都用不着它。同样,大多数开发人员也容易忽略
java.math.BigDecimal
所提供的任意精度的小数 ― 大多数应用程序不使用它们。然而,在以整数为主的程序中有时确实会出人意料地需要表示非整型数据。例如,JDBC 使用BigDecimal
作为 SQLDECIMAL
列的首选互换格式。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 所示。
因为用科学记数法可以有多种方式来表示给定数字,所以要规范化浮点数,以便用底数为 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()
来比较两个 NaNFloat
对象会得到true
。造成这种现象的原因是,如果不这样的话,就不可能将 NaNFloat
对象用作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,第二个不会。第二种形式比较好,因为它显式地检测了您认为有效的值的范围。
// 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
字段检索值,则如何确保精确地转换该数字?您可能不希望使用由 JDBCPreparedStatement
和ResultSet
类所提供的setFloat()
和getFloat()
方法,因为浮点数与小数之间的转换可能会丧失精确性。相反,请使用PreparedStatement
和ResultSet
的setBigDecimal()
及getBigDecimal()
方法。对于
BigDecimal
,有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的String
表示作为输入。要小心使用BigDecimal(double)
构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或String
的构造函数。对于
BigDecimal
,有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的String
表示作为输入。要小心使用BigDecimal(double)
构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或String
的构造函数。如果使用
BigDecimal(double)
构造函数不恰当,在传递给 JDBCsetBigDecimal()
方法时,会造成似乎很奇怪的 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
。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 请参与本文的 论坛。(您也可以单击本文顶部或底部的 讨论来访问论坛)。
- David Goldberg 的经典文章 What Every Scientist Should Know About Floating-Point Arithmetic对各种浮点表示进行了权衡,并讨论了它们各自的缺点。
- IBM Hursley 实验室的 FAQ探讨了有关小数运算的一些问题。
- Bill Venners 在 JavaWorld 的 Under the hood 专栏(Bill Venners 撰写)研究了 JVM 中的浮点支持。
- IEEE 754 的首席架构设计师之一 William Kahan 在“ How Java's Floating-Point Hurts Everyone Everywhere”(PDF)一文中批评了不完整的 Java 浮点实现。
- Intel Architecture Software Developer's Manual(PDF)详细讲述了 IEEE 754 浮点的最常见实现。
- Microsoft 的 Steve Hollasch 写的 IEEE Standard 754 floating point numbers是一篇很好的总结。
- 这篇 HP 技术说明就 Java 语言的浮点支持详细探讨了其令人迷惑不解的方面。
- Castor XML 数据绑定框架使用
BigDecimal
作为小数的互换类型。- Java Developer Connection 的 Tech Tips就 Java 2 平台中数字计算提供了一些指导。
- JavaWorld文章“ Make cents with BigDecimal”为使用
BigDecimal
和NumberFormat
来进行一些财会计算提供了一些有意义的技巧。- 阅读 Java theory and practice 专栏 (Brian 撰写)中的所有文章。
Brian Goetz 是一位软件顾问,在过去的 15 年里一直是一位专业软件开发人员。他是 Quiotix的首席顾问,该公司是位于加利福尼亚 Los Altos 的软件开发和咨询公司。请在业界流行的出版物上查阅 Brian 已发表的和即将发表的文章。可以通过 brian@quiotix.com与 Brian 联系。
相关推荐
【Java理论与实践:您的小数点到哪里去了?】 在Java编程中,浮点数和小数的使用虽然相对较少,但在特定场景下却至关重要。这篇文章主要探讨了Java中处理非整数类型,尤其是浮点数(float和double)和任意精度小数...
总的来说,构建一个Java词法分析器是一个涉及语言理论、正则表达式和数据结构的综合实践。它不仅需要理解语言的规则,还需要掌握Java的输入/输出操作和正则表达式使用。通过这个项目,可以深入理解编译原理,并提升...
文档标题“点错的小数点.docx”以及描述暗示了一个关于数学精确性和...因此,无论是在理论学习还是实践操作中,都应养成良好的习惯,对待每一个数字都需严谨对待,确保计算的准确性,避免类似故事中的悲剧再次发生。
在本文中,我们将深入探讨如何使用Java编程语言构建一个完整的计算器应用,该应用包含了加、减、乘、除以及处理小数点的基本功能。通过学习这个项目,你可以增强对Java控制流程、运算符以及字符串处理的理解。 首先...
在Java编程语言中,开发一个计算器的小项目是一个常见的学习任务,它可以帮助初学者理解面向对象编程、GUI(图形...对于初学者来说,这是一个非常有价值的实践项目,因为它将理论与实践相结合,帮助巩固Java编程基础。
这篇实验报告描述的是一个使用Java Swing开发的计算器项目,...这个实验涵盖了基础的Java GUI编程,事件处理,以及使用外部API执行脚本的能力,是一个很好的实践项目,可以帮助学生巩固理论知识并提升实际编程技能。
根据提供的文档信息,本次Java课程设计的目标是开发一个具备图形用户界面(GUI...整个设计过程中涉及的技术点包括但不限于GUI设计、算法实现、异常处理等方面,对于提高学生的综合实践能力和理论知识水平具有重要意义。
【Java课程设计报告计算器】是计算机科学与技术专业学生的一次实践项目,旨在设计一个基于GUI界面的计算器程序。这个计算器允许用户通过鼠标输入数值,执行包括加、减、乘、除在内的各种混合运算。以下是关于这个...
在编程领域,编译原理是理解计算机如何将高级语言转换为机器可执行代码的重要理论基础。词法分析是编译器设计中的第一步,它将源代码分解成一系列有意义的符号,也就是我们所说的“token”。本实验是基于Java语言...
- 通过实际的计算器项目,学生可以巩固Java面向对象编程的概念,提升编程技能,理解如何将理论知识应用于实践。 3. **需求分析**: - 计算器应能执行基本的加、减、乘、除运算,满足日常生活中的计算需求。 - ...
- **未来发展**:在未来的学习和实践中继续提升编程能力,加强理论与实践相结合的能力。 通过本次课程设计,不仅加深了对Java编程语言的理解,还提高了解决实际问题的能力。同时也意识到了实际编程操作的重要性,为...
- **理论与实践结合**:将课堂上学到的理论知识应用于实际开发中,通过实践来检验自己对知识的掌握程度。 #### 二、实习环境 - **硬件**:一台配置适中的PC机,至少需要能够运行Java开发工具。 - **软件**:使用...
表2-1列举了Java的关键字,包括像"true"、"false"和"null"这样的特殊值,它们虽然理论上不是关键字,但在实践中与关键字等效。值得注意的是,Java不支持C语言中的"sizeof"运算符,因为所有数据类型的大小是固定的。...
根据提供的文档信息,我们可以深入探讨该Java课程设计项目——图形界面计算器的设计与实现。下面将按照文档中的结构,详细介绍各个部分的关键知识点。 ### 一、课设任务及要求 本课程设计的主要目标是让学生通过...
Java程序设计大作业是学习Java编程的重要实践环节,旨在巩固理论知识,提升实际编程能力。在这个作业中,学生们将面临两个具体题目,可以选择其中之一进行完成,或者选择独立完成,也可以两人一组合作。下面是这两个...
2. **理论结合实际**:在设计计算器的过程中,学生需将理论知识应用到实际编程中,例如,使用布局管理器设计用户界面,通过事件监听器处理用户输入。 3. **创新性**:除了满足基本功能,学生可以尝试进行创新设计,...
总之,这个Java电子计算器项目是一个很好的实践平台,让你能够在实践中巩固理论知识,提升编程技能。通过实际动手操作,你会对Java编程有更深的理解,也能更好地应用于其他类型的Java应用开发。
本Java课程设计报告通过实践的方式,使学生能够更加深入地理解Java编程语言,并掌握了如何使用Java语言实现一个简单的计算器和手机信息管理系统。通过对这些项目的实际开发,学生不仅巩固了Java的基础理论知识,还...