Effective Java(2nd Edition) Item 48 如果需要精确结果,请避免使用float与double(译文)

来源:互联网 发布:最好的汽车报价软件 编辑:程序博客网 时间:2024/04/27 17:14

设计floatdouble类型主要用于科学与工程计算。它们用于二进制浮点运算,二进制浮点运算被仔细设计,可在一个很宽的数量级范围内提供足够精确的近似计算。然而它们不能提供精确的结果,当需要精确的结果时就不应使用它们。Floatdouble类型特别不适合于货币计算,因为不可能用floatdouble精确的表示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!

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。这是一个错误的答案。解决这个问题的正确做法是在货币计算中使用BigDecimalintlong

       下面的程序段在前面的程序中用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也有两点劣势,它没有基本算术类型方便,而且比较慢。如果你解决的是单个小问题,比较慢就不成问题。但前面这个劣势可能会让你不舒服。

       可依据所处理的数量大小,使用intlong来代替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");

       }

       总之,不要将floatdouble用于需要精确结果的计算。如果你希望系统跟踪处理小数点,并且不介意不使用基本类型的不便与花销,就使用BigDecimal。使用BigDecimal还有一个额外的好处是你可以完全控制四舍五入,允许你只要有小数尾数,就可以选择八种四舍五入模式之一进行操作。如果你所运行的业务运算要求有特别地四舍五入行为,这会带来很大便利。如果性能是关键的,而且你也不介意自已跟踪处理小数点位,数值也不特别大,就使用intlong。如果数值不超过十进制9位数,你可以使用int,如果数值不超过18位数,你可以使用long。如果数值可能超过18位,你就得使用BigDecimal

 

原创粉丝点击