侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

java中的BigDecimal

Java  /  管理员 发布于 5年前   524

在项目开发过程中出现精度丢失问题,查资料用BigDecimal解决,并发现如下这篇BigDecimal的解决问题的思路和方法很值得学习,特转载。

        原文地址:http://blog.csdn.net/ugg/article/details/8213666

 

一.问题分析处理

        由于数据库存储的金额是以分为单位,而应用程序和前台是元,这中间需要要做元与分的转化,这个转化规则很简单,就是*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。
        针对这样的问题,如果使用C/C++语言,那么通用解决方案可以这样。

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();

        狂晕,s=”9.86”时输出结果是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);MathContext mc = 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);BigDecimal bigD = new BigDecimal(dd);MathContext mc = new MathContext(s.length(),RoundingMode.HALF_UP);//s.length()表示取s.length位有效数字,RoundingMode.HALF_UP表示四舍五入bigD= bigD.multiply(new BigDecimal(100),mc);Long result = bigD.longValue();

        至此,已经可以得到一个正确的元转分的代码,但是这里的s.length()终归不让人感觉舒服,接下来,我们探索BigDecimal原理,尝试用更优雅的方法解决这个问题。

 

二.BigDecimal
        BigDecimal,不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度(scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。我们知道BigDecimal有三个主要的构造函数。
        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.599999999999999977795539507496869191527366638183593750.40000000000000002220446049250313080847263336181640625

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

0.60.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(new BigDecimal(100)).divide(new BigDecimal(1), 1, BigDecimal.ROUND_HALF_UP);Long result = bigD.longValue();

        我们通过/1,然后设置保留小数点方式,以及设置数字保留模式,从而得到两个数乘积的小数部分。

PS:BigDecimal有以下模式
        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内。



  • 上一条:
    Spring调SDK包报java.lang.NoSuchFieldError错误
    下一条:
    java中数组与List相互转换的方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在java中实现的脱敏工具类代码示例分享(0个评论)
    • zookeeper安装流程步骤(0个评论)
    • 在java中你背的“八股文”可能已经过时了(2个评论)
    • 在php8.0+版本中使用属性来增加值代码示例(3个评论)
    • java 正则表达式基础,实例学习资料收集大全 原创(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-11
    • 2018-03
    • 2020-03
    • 2023-05
    • 2023-11
    • 2024-01
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客