float 和double 主要用于科学计算和工程计算。它们执行二进制浮点算法,该算法经过精心设计,能够在很大范围内快速提供精确的近似值。但是,它们不能提供准确的结果,也不应该在需要精确结果的地方使用。float和double类型特别不适合进行货币计算因为作为float和double不可能代表0.1(或者任何其他10的负幂)。
例如,假设你口袋里有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美分的那颗开始,直到你买不起货架上的下一颗糖。你买了多少糖果,换了多少零钱?这里有一个简单的程序设计来解决这个问题:
如果你运行这个程序,你会发现你可以买得起三块糖,你还有0.399999999999999999美元。这是错误的答案!解决这个问题的正确方法是使用BigDecimal、int或long进行货币计算。
这里是前一个程序的一个简单转换,使用BigDecimal类型代替double。注意,使用BigDecimal的字符串构造函数而不是它的double构造函数。这是为了避免在计算中引入不准确的值[Bloch05, Puzzle 2]:
image.png
如果你运行修改后的程序,你会发现你可以用剩下的0美元买四颗糖。这是正确答案。
然而,使用BigDecimal有两个缺点:它比使用原始算术类型要不方便得多,而且速度要慢得多。如果你只解决一个简单的问题,后一种缺点是无关紧要的,但前者可能会让你烦恼。
使用BigDecimal的另一种方法是使用int或long,这取决于涉及的数量,并自己跟踪小数点。在这个例子中,最明显的方法是用美分而不是美元来计算。下面是一个采用这种方法的简单转换:
image.png
总之,对于任何需要精确答案的计算,不要使用float或double。如果希望系统跟踪小数点,并且不介意不使用基本类型带来的不便和成本,请使用BigDecimal。
使用BigDecimal的另一个好处是,它可以完全控制舍入,当执行需要舍入的操作时,可以从八种舍入模式中进行选择。如果您使用合法的舍入行为执行业务计算,这将非常方便。如果性能是最重要的,那么您不介意自己跟踪小数点,而且数量不是太大,可以使用int或long。如果数量不超过9位小数,可以使用int;如果不超过18位,可以使用long。如果数量可能超过18位,则使用BigDecimal。
本文写于2019.7.18,历时1天
网友评论