前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在货币计算中应该避免浮点数

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

作者头像
程序你好
发布2018-09-29 11:04:30
2.4K0
发布2018-09-29 11:04:30
举报
文章被收录于专栏:程序你好

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

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

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

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

代码语言:javascript
复制
        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);
            }
        }

输出如下:

代码语言:javascript
复制
        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舍入:

代码语言:javascript
复制
        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);
            }
        }

程序输出:

代码语言:javascript
复制
        tempBig = 0.1111

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

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

NumberFormat实例

代码语言:javascript
复制
        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()));
            }
        }

程序输出:

代码语言:javascript
复制
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)在美国更常用。该方法将算术运算的理想(无限精确)结果四舍五入到最接近的可表示值,并将该表示作为结果给出。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序你好 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 损失的原因
    • 浮点算术
      • BigDecimal
        • 特别指出
          • 什么是精度和刻度?
            • 如何格式化BigDecimal值而不获得结果中的求幂并去掉后面的0呢?
              • 如何为印度地区(INR货币)打印给定的货币值?
                • 预防措施
                • 你知道吗?
                  • 使用Float, Double代替BigDecimal可能会对军事造成致命的影响
                    • 银行家四舍五入模式
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档