美文网首页
ITEM 60: 如果需要精确的结果,不要用 float 或 d

ITEM 60: 如果需要精确的结果,不要用 float 或 d

作者: rabbittttt | 来源:发表于2020-01-19 18:38 被阅读0次

    ITEM 60: AVOID FLOAT AND DOUBLE IF EXACT ANSWERS ARE REQUIRED
      float 和 double 主要用于科学和工程计算。它们执行二进制浮点运算,该运算经过精心设计,能够在大范围内快速提供精确的近似值。但是,它们不能提供准确的结果,并且不应该在需要精确结果的地方使用。float 和 double 类型 特别 不 适合 进行货币计算,因为不可能将0.1(或任何其他 10 的负幂)精确地表示为 float 或 double。
      例如,假设你口袋里有1.03美元,你消费了42美分。你还剩下多少钱?下面是一个简单的程序片段,试图回答这个问题:
    System.out.println(1.03 - 0.42);
      不幸的是,它输出了 0.610000000001。这不是个例。假设你口袋里有一美元,你买了9台洗衣机,每台10美分。你能得到多少零钱?
    System.out.println(1.00 - 9 * 0.10);
      根据这个程序片段,您将得到 $0.09999999999999998。您可能认为只需在打印之前对结果进行舍入即可解决问题,但不幸的是,这种方法并不总是有效。例如,假设你的口袋里有一美元,你看到一个架子上有一排好吃的糖果,它们的价格仅仅是10美分,20美分,30美分,等等,多到一美元。你可以从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 的字符串构造函数,而不是它的双构造函数。这是为了避免在计算中引入不准确的值[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);
    }
    

      如果你运行修改后的程序,你会发现你可以负担得起 4 块糖果,还剩下 0 美元。这是正确答案。然而,使用 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。

    相关文章

      网友评论

          本文标题:ITEM 60: 如果需要精确的结果,不要用 float 或 d

          本文链接:https://www.haomeiwen.com/subject/ystdzctx.html