Effective Java(2nd Edition) Item 48 如果需要精确结果,请避免使用float与double(译文)
2009-01-07 14:30
579 查看
设计float与double类型主要用于科学与工程计算。它们用于二进制浮点运算,二进制浮点运算被仔细设计,可在一个很宽的数量级范围内提供足够精确的近似计算。然而它们不能提供精确的结果,当需要精确的结果时就不应使用它们。Float与double类型特别不适合于货币计算,因为不可能用float或double精确的表示0.1(及任何10的负幂次方的数)。
例如,假设你手头有$1.03,你花了42¢,你还剩多少钱呢?下面是解答这个问题的最自然的代码片断:
System.out.println(1.03 - .42);
不幸的是,它的输出是0.6100000000000001。这并非一个孤立现象。假设你手头有一美元,你买了9只垫圈,每只10美分,还剩多少钱呢?
System.out.println(1.00 - 9 * .10);
如果按这段代码,你得$0.09999999999999998.
你可能会认为只要在打印前先四舍五入就可以解决这个问题,不幸的是,这并非总是可行的。例如,假设你手头有1美元,贷架上放着一排精美的糖果,标价为10美分、20美分、30美分,如些值到1美元。你想买每颗这样的糖果,从10美分开始,值到余下的钱不够买下一颗为止。你能买几颗糖呢?还留下多少钱呢?下面是一个解决这个问题的自然代码段。
// Broken - uses floating point for monetary calculation!<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
public static void main(String[] args) {
double funds = 1.00;
int itemsBought = 0;
for (double price = .10; funds >= price; price += .10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}
如果你运行这段代码,你可以发现你能买到三颗糖,剩下$0.3999999999999999。这是一个错误的答案。解决这个问题的正确做法是在货币计算中使用BigDecimal、int或long。
下面的程序段在前面的程序中用BigDecimal直接替换了double。
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)) {
ItemsBought++;
funds = funds.subtract(price);
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
如果你运行改版了的程序,你可以发现你可以买到四颗糖,余$0.00。这是正确的答案。
然而使用BigDecimal也有两点劣势,它没有基本算术类型方便,而且比较慢。如果你解决的是单个小问题,比较慢就不成问题。但前面这个劣势可能会让你不舒服。
可依据所处理的数量大小,使用int或long来代替BigDecimal,并由自已跟踪处理小数点位。在上面的例子中,一个显然的方法是使用美分而不是美元来进行所有的运算。下面的程序展示了这个方法。
public static void main(String[] args) {
int itemsBought = 0;
int funds = 100;
for (int price = 10; funds >= price; price += 10) {
itemsBought++;
funds -= price;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: "+ funds + " cents");
}
总之,不要将float或double用于需要精确结果的计算。如果你希望系统跟踪处理小数点,并且不介意不使用基本类型的不便与花销,就使用BigDecimal。使用BigDecimal还有一个额外的好处是你可以完全控制四舍五入,允许你只要有小数尾数,就可以选择八种四舍五入模式之一进行操作。如果你所运行的业务运算要求有特别地四舍五入行为,这会带来很大便利。如果性能是关键的,而且你也不介意自已跟踪处理小数点位,数值也不特别大,就使用int或long。如果数值不超过十进制9位数,你可以使用int,如果数值不超过18位数,你可以使用long。如果数值可能超过18位,你就得使用BigDecimal。
例如,假设你手头有$1.03,你花了42¢,你还剩多少钱呢?下面是解答这个问题的最自然的代码片断:
System.out.println(1.03 - .42);
不幸的是,它的输出是0.6100000000000001。这并非一个孤立现象。假设你手头有一美元,你买了9只垫圈,每只10美分,还剩多少钱呢?
System.out.println(1.00 - 9 * .10);
如果按这段代码,你得$0.09999999999999998.
你可能会认为只要在打印前先四舍五入就可以解决这个问题,不幸的是,这并非总是可行的。例如,假设你手头有1美元,贷架上放着一排精美的糖果,标价为10美分、20美分、30美分,如些值到1美元。你想买每颗这样的糖果,从10美分开始,值到余下的钱不够买下一颗为止。你能买几颗糖呢?还留下多少钱呢?下面是一个解决这个问题的自然代码段。
// Broken - uses floating point for monetary calculation!<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
public static void main(String[] args) {
double funds = 1.00;
int itemsBought = 0;
for (double price = .10; funds >= price; price += .10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}
如果你运行这段代码,你可以发现你能买到三颗糖,剩下$0.3999999999999999。这是一个错误的答案。解决这个问题的正确做法是在货币计算中使用BigDecimal、int或long。
下面的程序段在前面的程序中用BigDecimal直接替换了double。
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)) {
ItemsBought++;
funds = funds.subtract(price);
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
如果你运行改版了的程序,你可以发现你可以买到四颗糖,余$0.00。这是正确的答案。
然而使用BigDecimal也有两点劣势,它没有基本算术类型方便,而且比较慢。如果你解决的是单个小问题,比较慢就不成问题。但前面这个劣势可能会让你不舒服。
可依据所处理的数量大小,使用int或long来代替BigDecimal,并由自已跟踪处理小数点位。在上面的例子中,一个显然的方法是使用美分而不是美元来进行所有的运算。下面的程序展示了这个方法。
public static void main(String[] args) {
int itemsBought = 0;
int funds = 100;
for (int price = 10; funds >= price; price += 10) {
itemsBought++;
funds -= price;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: "+ funds + " cents");
}
总之,不要将float或double用于需要精确结果的计算。如果你希望系统跟踪处理小数点,并且不介意不使用基本类型的不便与花销,就使用BigDecimal。使用BigDecimal还有一个额外的好处是你可以完全控制四舍五入,允许你只要有小数尾数,就可以选择八种四舍五入模式之一进行操作。如果你所运行的业务运算要求有特别地四舍五入行为,这会带来很大便利。如果性能是关键的,而且你也不介意自已跟踪处理小数点位,数值也不特别大,就使用int或long。如果数值不超过十进制9位数,你可以使用int,如果数值不超过18位数,你可以使用long。如果数值可能超过18位,你就得使用BigDecimal。
相关文章推荐
- 《effective java》48—如果需要精确的答案,请避免使用float和double
- 第48条:如果需要精确的答案,请避免使用float和double
- 第48条:如果需要精确的答案,请避免使用float和double
- Java:Effective Java 学习笔记(第48条:如果需要精确的答案,请避免使用float和double)
- Effective Java(2nd Edition) Item 59 避免使用不必要的受检查异常(译文)
- 如果需要精确答案,请避免使用float和double
- 第四十八条:如果需要精确的答案,请避免使用float和double
- 需要精确答案,避免使用float和double
- 如果要求精确的答案,请避免使用float和double
- 31-如果要求精确的答案,请避免使用float和double
- 表达式之谜2找零时刻(需要精确答案的地方,避免使用float和double;货币运算使用int,long或BigDecimal)
- 如果要求精确的答案,请避免使用float和double
- 如果要求精确的答案,请避免使用float和double
- Effective Java读书笔记--如果想要知道精确的答案,就要避免使用double和float
- [读书笔记][Effective Java]不要在精确计算中使用float和double类型
- Effective Java(2nd Edition) Item 60 优先使用标准异常(译文)
- Effective Java(2nd Edition) Item 62 归档方法所抛出的异常(译文)
- Effective Java(2nd Edition) Item 57 仅为例外条件使用异常(译文)
- 天天记录 - Java 精确计算避免使用float和double