在货币计算中应该避免浮点数

float和double数据类型对金融计算(甚至是军事用途)都是有害的,永远不要用它们来进行货币计算。如果精度是您的需求之一,那么使用BigDecimal。

让我们通过一个例子来探讨这个问题:

所有可以表示货币数量(以美元和美分计)的浮点值都不能准确地存储在内存中。因此,如果我们想存储0.1美元(10美分),float/double就不能存储它原来的样子。相反,二进制只能存储更接近的近似值(十进制的0.100000001490116119384765625)。当我们重复地使用这两种数据类型进行算术运算(乘或除)时,这个问题的严重性就变得非常显著(称为显著性损失)。下面,我们将展示这可能是什么样子的。

这里有一个例子,使用双精度损失:

        public class DoubleForCurrency {        
            public static void main(String[] args) {
                double total = 0.2;
                for (int i = 0; i < 100; i++) {
                    total += 0.2;
                }
                System.out.println("total = " + total);
            }
        }

输出如下:

        total = 20.19999999999996

输出应该是20.20(20美元和20美分),但是浮点运算使它变成了20.19999999999996。这是精度的损失(或意义的损失)。

损失的原因

浮点算术

在计算中,浮点运算(FP)是一种使用公式化的实数表示法作为近似来支持范围和精度之间的权衡的算法。

根据维基百科:

有理数是否有终止展开式取决于基数。例如,在base-10中,1/2有一个终止展开(0.5),而1/3没有(0.333…)。在base-2中,只有分母是2的幂(如1/2或3/16)的理性终止。任何分母上除2外有质数因子的有理函数都有无限的二元展开式。这意味着,如果以十进制格式编写的数字看起来很短且精确,那么在转换为二进制浮点数时可能需要近似处理。例如,十进制数0.1不能用任何有限精度的二进制浮点数表示;精确的二进制表示将有一个“1100”序列无休止地继续:

e = −4; s = 1100110011001100110011001100110011…, 如前所述,s是含义e是指数。

当四舍五入到24位时,这就变成了

e = −4; s = 110011001100110011001101, 实际上是十进制的0。100000001490116119384765625。

BigDecimal

BigDecimal表示带相关刻度的带符号精度的十进制数。BigDecimal提供了对精度和舍入值的完全控制。实际上,使用BigDecimal可以计算出小数点后20亿的位置,唯一的限制是可用的物理内存。

这就是为什么在财务计算中我们总是喜欢使用BigDecimal或BigInteger。

特别指出

基本类型:如果不需要十进制精度,int和long对于货币计算也很有用。

我们应该真正避免使用BigDecimal(double value)构造器,而应该更喜欢使用BigDecimal(String),因为BigDecimal(0.1)会导致(0.1000000000000000000051115125125128270211815834041015625)存储在BigDecimal实例中。相比之下,BigDecimal(“0.1”)精确地存储了0.1。

什么是精度和刻度?

精度是实数的位数(或有效位数)的总数。

Scale指定小数点后的位数。例如,12.345的精度为5(总位数),刻度为3(小数点右位数)。

如何格式化BigDecimal值而不获得结果中的求幂并去掉后面的0呢?

如果我们在使用BigDecimal时没有遵循一些最佳实践,我们可能会在计算结果中得到求幂。下面的代码片段显示了处理BigDecimal的有用用法示例。

BigDecimal舍入:

        import java.math.BigDecimal;        
        
        public class BigDecimalForCurrency {
        
            public static void main(String[] args) {
                int scale = 4;
                double value = 0.11111;
                BigDecimal tempBig = new BigDecimal(Double.toString(value));
                tempBig = tempBig.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
                String strValue = tempBig.stripTrailingZeros().toPlainString();
                System.out.println("tempBig = " + strValue);
            }
        }

程序输出:

        tempBig = 0.1111

如何为印度地区(INR货币)打印给定的货币值?

NumberFormat类是专门为此设计的。货币符号&舍入模式使用NumberFormat根据地区自动设置。让我们看看实际情况

NumberFormat实例

        class Scratch {        
            public static String formatRupees(double value) {
                NumberFormat format = NumberFormat.getCurrencyInstance(new Locale("en", "in"));
                format.setMinimumFractionDigits(2);
                format.setMaximumFractionDigits(5);
                format.setRoundingMode(RoundingMode.HALF_EVEN);
                return format.format(value);
            }
        
            public static void main(String[] args) {
                BigDecimal tempBig = new BigDecimal(22.121455);
                System.out.println("tempBig = " + formatRupees(tempBig.doubleValue()));
            }
        }

程序输出:

tempBig = Rs.22.12146

就是这样,一切都是由NumberFormat处理的。

预防措施

BigDecimal(String)构造函数应该总是优于BigDecimal(Double),因为使用BigDecimal(Double)是不可预测的,因为不能将0.1表示为精确的0.1。

如果必须使用double来初始化BigDecimal,那么使用BigDecimal. valueof (double),它使用double . tostring (double)方法将double值转换为String

设置比例尺时应设置舍入模式

剥去所有跟在后面的零

toString()可能使用科学的符号,但是toPlainString()在其结果中永远不会返回指数

你知道吗?

使用Float, Double代替BigDecimal可能会对军事造成致命的影响

1991年2月25日,一枚MIM-104“爱国者”(Patriot)导弹电池失去了重要意义,无法在沙特阿拉伯达兰拦截一枚来犯的飞毛腿导弹,导致美国陆军第14军需分队的28名士兵死亡。

银行家四舍五入模式

自从引入IEEE 754以来,默认方法(四舍五入到最近的小数,与偶数相连,有时被称为银行家四舍五入或RoundingMode.HALF_EVEN)在美国更常用。该方法将算术运算的理想(无限精确)结果四舍五入到最接近的可表示值,并将该表示作为结果给出。

原文发布于微信公众号 - 程序你好(codinghello)

原文发表时间:2018-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小樱的经验随笔

2017广东工业大学程序设计竞赛决赛 题解&源码(A,数学解方程,B,贪心博弈,C,递归,D,水,E,贪心,面试题,F,贪心,枚举,LCA,G,dp,记忆化搜索,H,思维题)

心得: 这比赛真的是不要不要的,pending了一下午,也不知道对错,直接做过去就是了,也没有管太多! Problem A: 两只老虎 Description ...

3276
来自专栏大数据挖掘DT机器学习

【手把手教你做项目】自然语言处理:单词抽取/统计

作者 白宁超 成都信息工程大学硕士。 近期关注数据分析统计学、机器学习。 原文:http://www.cnblogs.com/baiboy/p/zryy1.h...

34013
来自专栏数据结构与算法

2000 楼房重建 2012年

 时间限制: 1 s  空间限制: 256000 KB  题目等级 : 大师 Master 题解  查看运行结果 题目描述 Description   小A的楼...

3637
来自专栏数据结构与算法

1163 访问艺术馆

 时间限制: 1 s  空间限制: 128000 KB  题目等级 : 大师 Master 题解  查看运行结果 题目描述 Description     皮尔...

2837
来自专栏WOLFRAM

九宫格数独游戏

2388
来自专栏小樱的经验随笔

单表代替密码原理及算法实现

    要了解单表替代密码就得先了解替代密码,在这里我就做一下简单的介绍:       替代是古典密码中用到的最基本的处理技巧之一 。       替代密码是指...

5466
来自专栏HansBug's Lab

1627: [Usaco2007 Dec]穿越泥地

1627: [Usaco2007 Dec]穿越泥地 Time Limit: 5 Sec  Memory Limit: 64 MB Submit: 504  So...

2667
来自专栏cs

Mathematica学习笔记

放假了,近来无事,就复习了一下mathematica相关知识点。已经玩了很多东西,不过大概还是很熟悉。 Mathematica(我简称mma),可以通过交互方...

5746
来自专栏菩提树下的杨过

Flash/Flex学习笔记(43):动量守恒与能量守恒

动能公式: ? 动量公式: ? 动量守恒: ? 能量守恒: ? 根据这些规律可以得到下列方程组: ? ? 解该方程组,得到下面的公式: ? ? 把这二个公式相...

1907
来自专栏IT 指南者专栏

Python OJ 从入门到入门基础练习 10 题

OJ 是 Online Judge 系统的简称,用来在线检测程序源代码的正确性 1、天天向上的力量: 一年365天,以第1天的能力值为基数,记为1.0。当好好学...

59711

扫码关注云+社区

领取腾讯云代金券