Tips

书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code

注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

60. 需要精确的结果时避免使用float和double类型

float和double类型主要用于科学和工程计算。 它们执行二进制浮点运算,经过精心设计,可在很宽的范围内快速提供准确的近似值。 但是,它们不能提供准确的结果,不应在需要确切结果的地方使用。 float和double类型特别不适合进行货币计算,因为不可能将0.1(或任何其他10的负次方)精确地表示为float或double。

例如,假设你的口袋里有1.03美元,花了42美分。 你还剩多少钱? 以下是试图回答这个问题的天真的代码片段:

System.out.println(1.03 - 0.42);

不幸的是,它输出了0.6100000000000001。这不是个例。假设你口袋里有一美元,你买了9垫圈,每个10美分。你还剩多少零钱?

System.out.println(1.00 - 9 * 0.10);

根据这个程序片段,可以得到0.0999999999999999998美元。

你可能认为,只需在打印之前将结果四舍五入就可以解决这个问题,但不幸的是,这种方法并不总是有效。例如,假设你口袋里有一美元,你看到一个货架上有一排好吃的糖果,它们的价格仅仅是10美分,20美分,30美分,以此类推,直到1美元。你每买一颗糖,从10美分的那颗开始,直到你买不起货架上的下一颗糖。你买了多少糖果,换了多少零钱?这里有一个简单的程序设计来解决这个问题:

// Broken - uses floating point for monetary calculation!
public static void main(String[] args) {
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10; funds >= price; price += 0.10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}

如果你运行该程序,会发现你可以买三块糖果,剩下0.3999999999999999美元。 这是错误的答案! 解决此问题的正确方法是使用BigDecimal,int或long进行货币计算

这里是对上面程序的直接转换,使用BigDecimal类型代替double。 请注意,使用BigDecimal的String类型的构造方法,而不是其double类型构造方法。 这是必要的,以避免在计算中引入不准确的值[Bloch05,Puzzle 2]:

public static void main(String[] args) {
final BigDecimal TEN_CENTS = new BigDecimal(".10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS;
funds.compareTo(price) >= 0;
price = price.add(TEN_CENTS)) {
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}

如果你运行修改后的程序,你会发现可以买到四块糖果,剩下0.00美元。 这是正确的答案。

但是,使用BigDecimal有两个缺点:它没有比使用基本算术类型方便,而且速度要慢得多。 如果你只解决一个简单的问题,后一种缺点是无关紧要的,但前者可能会让你烦恼。

除了使用BigDecimal以外,还可以使用int或long类型,具体取决于所涉及的数量,并自己控制十进制小数点。 在这个例子中,最明显的方法是用美分而不是美元来计算。下面是采用这种方法的简单转换:

public static void main(String[] args) {
int itemsBought = 0;
int funds = 100;
for (int price = 10; funds >= price; price += 10) {
funds -= price;
itemsBought++;
} System.out.println(itemsBought + " items bought.");
System.out.println("Cash left over: " + funds + " cents");
}

总之,对于任何需要精确答案的计算,不要使用float或double。如果希望系统控制十进制小数点,并且不介意不使用基本类型带来的不便和成本,请使用BigDecimal。使用BigDecimal的另一个好处是,它可以完全控制舍入,当执行需要舍入的操作时,可以从八种舍入模式中进行选择。如果你使用合法的舍入行为执行业务计算,这将非常方便。如果性能是最重要的,那么不介意自己控制十进制小数点,而且数量不是太大,可以使用int或long。如果数量不超过9位小数,可以使用int;如果不超过18位,可以使用long。如果数量可能超过18位,则使用BigDecimal。

Effective Java 第三版——60. 需要精确的结果时避免使用float和double类型的更多相关文章

  1. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  2. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  3. Effective Java 第三版——10. 重写equals方法时遵守通用约定

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——18. 组合优于继承

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. Effective Java 第三版——30. 优先使用泛型方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. Effective Java 第三版——34. 使用枚举类型替代整型常量

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  7. Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Effective Java 第三版——7. 消除过期的对象引用

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. MySQL导入数据报 Got a packet bigger than‘max_allowed_packet’bytes 错误的解决方法

    MySQL根据配置文件会限制Server接受的数据包大小.有时候大的插入和更新会受 max_allowed_packet 参数限制,导致大数据写入或者更新失败. 通过终端进入mysql控制台,输入如下 ...

  2. IDEA快捷键 日常整理

    F9 : debug Ctrl+” +/- ” : 当前方法展开.折叠 Ctrl+Shift+”+/-” : 全部展开.折叠 Alt+1 : 工程面板 Alt+4:控制台 Alt+7:查看本类方法 S ...

  3. Lambda根据属性名字选择或筛选

    using System; using System.Linq.Expressions; internal class LambdaHelper { /// <summary> /// 指 ...

  4. uni-app 顶部导航点击更换图标

    更换顶部导航的iconfont.ttf图标,先在配置文件配置好按钮: pages.json文件 "buttons": [ { "text": "\ue ...

  5. JavaScript基础视频教程总结(111-120章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  6. Python request库与爬虫框架

    Requests库的7个主要方法  requests.request():构造一个请求,支持以下各方法的基础方法  requests.get():获取HTML网页的主要方法,对应于HTTP的GET  ...

  7. ICO图标下载地址

    http://findicons.com/ http://www.iconfont.cn/

  8. Nginx 教程(1):基本概念

    简介 嗨!分享就是关心!所以,我们愿意再跟你分享一点点知识.我们准备了这个划分为三节的<Nginx教程>.如果你对 Nginx 已经有所了解,或者你希望了解更多,这个教程将会对你非常有帮助 ...

  9. 11、OpenCV实现图像的灰度变换

    1.灰度变换的基本概念 灰度变换指对图像的单个像素进行操作,主要以对比度和阈值处理为目的.其变换形式如下: s=T(r) 其中,T是灰度变换函数:r是变换前的灰度:s是变换后的像素.图像灰度变换的有以 ...

  10. optimizer

    在很多机器学习和深度学习的应用中,我们发现用的最多的优化器是 Adam,为什么呢? 下面是 TensorFlow 中的优化器, https://www.tensorflow.org/api_guide ...