转载自逸学堂BigDecimal 的那些坑事儿

最近查看rebate数据时,发现一个bug,主要现象是,当扣款支付宝的账号款项时,返回的是数字的金额为元,而数据库把金额存储为分,这中间要做元与分的转化,这个转化规则很简单,就是*100的,所以一开始代码很简单,如下。

Float f =  Float.valueOf(s);
f =f*100;
Long result = f.longValue();

当s=”9.86”时,杯具出现了,result的结果为985而不是986,float的精度损失导致float(985.99994)转化为整形时,丢掉小数部分成为985,简单的方法,我们可以提高精度使用双精度的double类型,提高精度,比如

Double d =  Double.valueOf(s);
d = d*100;
Long result = d.longValue();

当s=”9.86”时,确实能够得到正确结果,但是当s=”1219.86”时,这时候由于精度问题导致最终的result为121985为不是121986。当时以为使用double解决的问题,其实隐藏更隐蔽的bug。

Double d =  Double.valueOf(s);
d = d*100+0.5;// 注意这里,我们使用的是+0.5的形式。
Long result = d.longValue();

但是,我们使用的java语言,java应该有更优雅的解决方案,那就是BigDecimal。
使用BigDecimal的解决方案成这个样子

Double dd= Double.valueOf(s);
BigDecimal bigD = new BigDecimal(dd);
bigD = bigD.multiply(new BigDecimal(100));
Long result = bigD.longValue();

狂晕,输出结果是985为不是986,打印bigD

System.out.println(bigD.toString());
输出:985.9999999999999431565811391919851303100585937500

不会再加上一个BigDecimal(0.5)吧。我相信在使用过BigDecimal过程中,肯定有那里不对的地方,multiply方法中可以传入精度,那就构造MathContext对象,修改如下。

Double dd= Double.valueOf(s);
BigDecimal bigD = new BigDecimal(dd);
MathContextmc = new MathContext(4,RoundingMode.HALF_UP);
//4表示取四位有效数字,RoundingMode.HALF_UP表示四舍五入
bigD= bigD.multiply(new BigDecimal(100),mc);
Long result = bigD.longValue();

最后结果输出为986,貌似已经找到完成解决方案,其实不然,注意到MathContext中的4了嘛?这是因为我们保留4位有效数字,假如我们输入的数字是大于4的,比如1219.86,最终输出结果是122000,这是因为1219.86保留4位有效数字时,第四位的9四舍五入,除去精确位补零,所以最终结果成了122000。问题就成了,我们必须知道元变分后的最终有效位数,”9.86”,有效位数是4,”19.86”有效位数是5,把字符串s的长度传过去就可以了,那么代码如下

Double dd =Double.valueOf(s);
BigDecimalbigD = new BigDecimal(dd);
MathContextmc = new MathContext(s.length(),RoundingMode.HALF_UP);
//4表示取四位有效数字,RoundingMode.HALF_UP表示四舍五入
bigD= bigD.multiply(new BigDecimal(100),mc);
Long result = bigD.longValue();
1 public BigDecimal(double val) 将double表示形式转换为BigDecimal
2 public BigDecimal(int val) 将int表示形式转换为BigDecimal
3 public BigDecimal(String val) 将字符串表示形式转换为BigDecimal

通过这三个构造函数,可以把double类型,int类型,String类型构造为BigDecimal对象,在BigDecimal对象内通过BigIntegerintVal存储传递对象数字部分,通过int scale;记录小数点位数,通过int precision;记录有效位数(默认为0)。
BigDecimal的加减乘除就成了BigInteger与BigInteger之间的加减乘除,浮点数的计算也转化为整形的计算,可以大大提供性能,并且通过BigInteger可以保存大数字,从而实现真正大十进制的计算,在整个计算过程中,还涉及scale的判断和precision判断从而确定最终输出结果。
我们先看一个例子

BigDecimal d1 = new BigDecimal(0.6);
BigDecimal d2 = new BigDecimal(0.4);
BigDecimal d3 = d1.divide(d2);
System.out.println(d3);

大家猜一下,以上输出结果是?再接着看下面的代码

BigDecimal d1 = new BigDecimal(“0.6”);
BigDecimal d2 = new BigDecimal(“0.4”);
BigDecimal d3 = d1.divide(d2);
System.out.println(d3);

看似相似的代码,其结果完全不同,第一个例子中,抛出异常。第二个例子中,输出打印结果为1.5。造成这种差异的主要原因是第一个例子中的创建BigDecimal时,0.6和0.4是浮动类型的,浮点型放入BigDecimal内,其存储值为

0.59999999999999997779553950749686919152736663818359375
0.40000000000000002220446049250313080847263336181640625

这两个浮点数相除时,由于除不尽,而又没有设置精度和保留小数点位数,导致抛出异常。而第二个例子中0.6和0.4是字符串类型,由于BigDecimal存储特性,通过BigInteger记录BigDecimal的值,所以,0.6和0.4可以非常正确的记录为

0.6
0.4

两者相除得出1.5来。
对于第一个例子,如果我们想得到正确结果,可以这样来

BigDecimal d1 = new BigDecimal(0.6);
BigDecimal d2 = new BigDecimal(0.4);
BigDecimal d3 = d1.divide(d2, 1, BigDecimal.ROUND_HALF_UP);

现在看我们留下的那个问题,使用更优雅的方式解决元转化为分的方式,上一个问题中,我们通过传递s.length()从而获得精度,如果之前的s是double类型的,那边这样的方式就会有问题,通过上面的例子,我们可以调整为一下的通用方式

Double dd= Double.valueOf(s);
BigDecimal bigD = new BigDecimal(dd);
bigD = bigD.multiply(newBigDecimal(100)). divide(1, 1, BigDecimal.ROUND_HALF_UP);
Long result = bigD.longValue();

我们通过/1,然后设置保留小数点方式,以及设置数字保留模式,从而得到两个数乘积的小数部分。还有以下模式
枚举常量摘要

ROUND_CEILING   向正无限大方向舍入的舍入模式。
ROUND_DOWN   向零方向舍入的舍入模式。
ROUND_FLOOR   向负无限大方向舍入的舍入模式。
ROUND_HALF_DOWN   向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。
ROUND_HALF_EVEN   向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
ROUND_HALF_UP   向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。
ROUND_UNNECESSARY   用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。(默认模式)
ROUND_UP   远离零方向舍入的舍入模式。

总结:
1:尽量避免传递double类型,有可能话,尽量使用int和String类型。
2:做乘除计算时,一定要设置精度和保留小数点位数。
3:BigDecimal计算时,单独放到try catch内。

BigDecimal使用2--保留小数点位数相关推荐

  1. 少儿编程100讲轻松学python(十一)-python如何保留小数点位数

    前言 python保留小数点位数的方法:首先新建py文件,输入[a=('%.2f'%a)]即可保留2位小数:然后如果输入[a=('%.4f'%a)],就保留4位小数:最后也可以输入[a=format( ...

  2. Python:使用f-string保留小数点位数

    Python:使用f-string保留小数点位数 格式 f"{num:xxx}" 其中xxx的格式如下 格式 说明 width 整数width指定宽度 0width 整数width ...

  3. matlab读取excel,求导、函数、注释,保留小数点位数等

    0.安装 2016a 破解版教程: https://jingyan.baidu.com/article/72ee561a19688be16138df3b.html 1.matlab大段注释的方法: 注 ...

  4. C++知识精讲5——printf()函数保留小数点位数方法及实战运用基本方式

    本文我们来讲C++知识精讲的第5篇,printf函数以及实战运用,此专栏会讲许多,各种各样的类型,如果喜欢此专栏请订阅持续关注,感谢大家的支持.接下来,进入今天的知识精讲. printf用来干什么的? ...

  5. c++ 四舍五入保留两位小数_Excel中保留小数点位数

    今天老板让我把带有小数点的表格进行整理,老板说把数据整理成保留两位,默认为四舍五入,老板还要整理成一种不四舍五入的数据.好的老板马上整理,这次老板的要求正好是我会的,真棒!我们一起来学一下吧. 首先我 ...

  6. mysql小数点后保留两位_不会保留小数点位数,做出来的表格难看,巧用ROUND函数解决...

    先提问一下,在Excel中,利用公式计算时,计算出来的结果有时候会有N多位小数,很多同学是不是就直接选择单元格格式设置"数值"保留两位小数点,就OK了.然鹅,有没有细心的同学发现, ...

  7. Java四舍五入及保留小数点位数

    之前有看过网上有的方法,比如可以利用字符串截取,还有就是运用DecimalFormat类来完成 /*** 格式化double<br>* 对 {@link DecimalFormat} 做封 ...

  8. android设置大小能用小数,Android中关于保留小数点位数的处理

    保留两位小数 方法一: { double c = 3.154215; java.text.DecimalFormat myformat=new java.text.DecimalFormat(&quo ...

  9. 保留小数点位数和格式

    JS 中 1. double运算 c = parseFloat(a) + parseFloat(b)) 2. 四舍五入,保留两位小数 c = c.toFixed(2); 3. 去掉小数点后面多余的0 ...

最新文章

  1. 团队如何实施敏捷开发以及Scrum电子看板工具
  2. Deepfake让罗伯特·德尼罗用流利的德语表演台词!差点忘了他是美国人
  3. 界面之下:还原真实的MV*模式
  4. 【深度学习】新的深度学习优化器探索(协同优化)
  5. 信息传递服务器,AJAX的与服务器之间的信息传递原理(初学)
  6. 旧android 4 平板,如今的安卓平板值不值得买:小米平板4入坑指南
  7. 笔试编程题常用的一些技巧方法
  8. MacOS实现MSDOS格式化为NTFS文件系统
  9. php加速 PHP APC 浅析
  10. php unset引用变量后不会删除值
  11. 驱动总裁 ,解决屏幕亮度无法调节(含泪推荐,屏幕亮度终于可以调整了,发热也不严重了)
  12. 淘淘商城系列——Solr集群搭建
  13. 【JVM】深入理解JVM垃圾回收机制及其垃圾回收算法
  14. 支付宝转账提现相关问题
  15. html如何实现自动登录,Js实现下次自动登录功能
  16. 实验2 格式化输入输出和分支语句
  17. 编程15年,如何才能成不了高手?
  18. 自定义数据集(Pokemon)实战
  19. 解决win10下PPT打不开,显示内容有问题,提示修复但修复不成功问题
  20. matlab模糊度函数,模糊函数 matlab 模糊度

热门文章

  1. 第二章 自动化测试基础(下)
  2. chopper云音乐开发笔记
  3. 【Code】ASCII码表
  4. python定间隔取点(np.linspace)
  5. 未来电子病历系统 以结构化数据为核心
  6. 服务器内文件大小排序,centos 如何查找大文件,文件排序方案大全
  7. Flutter 之 Scaffold
  8. HCI 基础系列:超融合架构的本质是什么(上)
  9. 网络安全(黑客)工具
  10. QGIS基本功 | 13 地球的形状及数学模型