From 8fcd09499120879947d707df028d9f55888d7b46 Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Wed, 20 Mar 2019 14:49:59 +0800 Subject: [PATCH] =?UTF-8?q?60.=20=E8=8B=A5=E9=9C=80=E8=A6=81=E7=B2=BE?= =?UTF-8?q?=E7=A1=AE=E7=AD=94=E6=A1=88=E5=B0=B1=E5=BA=94=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20float=20=E5=92=8C=20double=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...精确答案就应避免使用 float 和 double 类型.md | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/notes/60. 若需要精确答案就应避免使用 float 和 double 类型.md diff --git a/docs/notes/60. 若需要精确答案就应避免使用 float 和 double 类型.md b/docs/notes/60. 若需要精确答案就应避免使用 float 和 double 类型.md new file mode 100644 index 0000000..4343fad --- /dev/null +++ b/docs/notes/60. 若需要精确答案就应避免使用 float 和 double 类型.md @@ -0,0 +1,72 @@ +# 60. 若需要精确答案就应避免使用 float 和 double 类型 + +  float 和 double 类型主要用于科学计算和工程计算。它们执行二进制浮点运算,该算法经过精心设计,能够在很大范围内快速提供精确的近似值。但是,它们不能提供准确的结果,也不应该在需要精确结果的地方使用。**float 和 double 类型特别不适合进行货币计算**,因为不可能将 0.1(或 10 的任意负次幂)精确地表示为 float 或 double。 + +  例如,假设你口袋里有 1.03 美元,你消费了 42 美分。你还剩下多少钱?下面是一个简单的程序片段,试图回答这个问题: + +```java +System.out.println(1.03 - 0.42); +``` + +  不幸的是,它输出了 0.6100000000000001。这不是一个特例。假设你口袋里有一美元,你买了 9 台洗衣机,每台 10 美分。你能得到多少零钱? + +```java +System.out.println(1.00 - 9 * 0.10); +``` + +  根据这个程序片段,可以得到 0.0999999999999999998 美元。 + +  你可能认为,只需在打印之前将结果四舍五入就可以解决这个问题,但不幸的是,这种方法并不总是有效。例如,假设你口袋里有一美元,你看到一个架子上有一排好吃的糖果,它们的价格仅仅是 10 美分,20 美分,30 美分,以此类推,直到 1 美元。你每买一颗糖,从 10 美分的那颗开始,直到你买不起货架上的下一颗糖。你买了多少糖果,换了多少零钱?这里有一个简单的程序来解决这个问题: + +```java +// 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.399999999999999999 美元。这是错误的答案!解决这个问题的正确方法是 **使用 BigDecimal、int 或 long 进行货币计算。** + +  这里是前一个程序的一个简单改版,使用 BigDecimal 类型代替 double。注意,使用 BigDecimal 的 String 构造函数而不是它的 double 构造函数。这是为了避免在计算中引入不准确的值 [Bloch05, Puzzle 2]: + +```java +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 美元。这是正确答案。 + +  然而,使用 BigDecimal 有两个缺点:它与原始算术类型相比很不方便,而且速度要慢得多。如果你只解决一个简单的问题,后一种缺点是无关紧要的,但前者可能会让你烦恼。 + +  除了使用 BigDecimal,另一种方法是使用 int 或 long,这取决于涉及的数值大小,还要自己处理十进制小数点。在这个例子中,最明显的方法是用美分而不是美元来计算。下面是一个采用这种方法的简单改版: + +```java +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。 \ No newline at end of file