`

java中的BigDecimal

阅读更多

        在项目开发过程中出现精度丢失问题,查资料用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而不是986float的精度损失导致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.60.4是浮动类型的,浮点型放入BigDecimal内,其存储值为:

0.59999999999999997779553950749686919152736663818359375
0.40000000000000002220446049250313080847263336181640625

        这两个浮点数相除时,由于除不尽,而又没有设置精度和保留小数点位数,导致抛出异常。而第二个例子中0.60.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(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类型,有可能话,尽量使用intString类型。
        2.做乘除计算时,一定要设置精度和保留小数点位数。
        3.BigDecimal计算时,单独放到try catch内。

分享到:
评论

相关推荐

    java中BigDecimal的操作方法

    在Java编程语言中,BigDecimal是用于处理高精度和可配置精度的十进制数的类。在进行商业计算时,由于浮点数(double和float)存在精度问题,不能保证准确的结果,因此通常推荐使用BigDecimal来确保计算的精确性。本文...

    Java中BigDecimal的加减乘除、比较大小与使用注意事项

    在Java编程中,当涉及到需要精确数值计算的场景时,我们通常会使用`BigDecimal`类。这是因为`float`和`double`类型虽然适用于科学计算和工程计算,但它们基于二进制浮点运算,不能保证完全精确的结果。而`BigDecimal...

    关于java中BigDecimal的简介(csdn)————程序.pdf

    Java中的`BigDecimal`类是用于表示和操作高精度浮点数的重要工具,尤其适用于需要进行精确计算的场景,如财务和货币计算。由于基本数据类型`double`和`float`在进行大数值或高精度计算时可能会导致精度丢失,因此`...

    Java中BigDecimal类的使用详解

    Java中BigDecimal类的使用详解 Java中BigDecimal类是Java.math包中提供的一个API类,用于对超过16位有效位的数进行精确的运算。由于浮点数的精度问题,Java中浮点数的计算会失去一定的精确度。因此,使用BigDecimal...

    Java中BigDecimal的基本运算(详解)

    Java中BigDecimal的基本运算详解 Java中的BigDecimal是一种高精度的数据类型,它可以用来表示非常大的整数和小数,提供了丰富的数学运算功能。下面我们将对Java中BigDecimal的基本运算进行详细的介绍。 构造方法 ...

    Java中BigDecimal类的简单用法

    总之,Java中的BigDecimal类是处理精确数值计算的重要工具,尤其是在金融、会计等对精度要求极高的领域。通过使用其提供的各种构造方法和运算方法,开发者可以有效地控制计算过程中的精度,确保计算结果的正确性。...

    浅谈java中BigDecimal类的简单用法

    在Java编程语言中,`BigDecimal` 类位于 `java.math` 包中,是用于执行高精度和任意精度的十进制算术运算的关键类。它主要用于处理需要精确数值计算的场景,例如金融或会计领域,因为传统的 `float` 和 `double` ...

    Java对BigDecimal常用方法的归类(加减乘除).doc

    Java 中 BigDecimal 的常用方法归类(加减乘除) Java 中的 BigDecimal 类提供了对浮点数的精确运算,包括加减乘除和四舍五入等操作。在 Java 中,简单类型不能够精确地对浮点数进行运算,因此需要使用 BigDecimal ...

    java BigDecimal操作

    在Java编程语言中,BigDecimal类是用于处理高精度、大范围浮点数的工具,它在需要精确计算的场景下非常关键。BigDecimal提供了避免浮点数运算中的精度损失的方法,适用于金融、会计等对精度有严格要求的领域。这篇...

    Java SE程序 BigDecimal类

    Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE程序 BigDecimal类Java SE...

    BigDecimal开n次方根

    复杂的BigDecimal计算,需要开方的式子,可输入结果精确位数

    java中BigDecimal进行加减乘除的基本用法

    在Java编程语言中,`BigDecimal` 类位于 `java.math` 包下,它提供了一种进行高精度和任意精度的十进制算术运算的方法。`BigDecimal` 是为那些需要精确数值计算的场景设计的,比如金融计算或会计应用,因为普通的 `...

    Java中BigDecimal精度和相等比较的坑

    在Java编程中,`BigDecimal` 类是用来处理高精度、任意精度的十进制数的,尤其适用于财务和货币计算,因为它能确保计算结果的精确性。`float` 和 `double` 类型虽然在科学计算和工程计算中广泛使用,但它们无法提供...

    Java使用BigDecimal进行运算封装的实际案例

    今天,我们将讨论Java中使用BigDecimal进行运算封装的实际案例。BigDecimal是Java中一个用于处理精确数字计算的类,它提供了多种数学运算方法,如加法、减法、乘法、除法等。 在实际项目中,经常需要对金额进行计算...

    Java编程BigDecimal用法实例分享

    Java中的BigDecimal类是Java标准库中的一部分,用于处理高精度的数字计算,特别是在商业计算中对数字精度要求较高的场景中。BigDecimal类提供了对大数字的操作,可以精确地计算货币值等。 BigDecimal类的实现是基于...

    java实现大数加法(BigDecimal)的实例代码

    在Java编程语言中,处理大数(大数据量的整数)加法时,通常会使用`java.math.BigDecimal`类。这个类提供了精确的浮点数运算,特别适合于需要高精度计算的情况,如金融计算或者复杂的数学运算。下面将详细讨论`...

    浅谈java中BigDecimal的equals与compareTo的区别

    在Java编程语言中,BigDecimal类用于表示任意精度的十进制浮点数,它提供了对大数值计算的精确控制。本文将深入探讨BigDecimal的`equals`方法和`compareTo`方法的区别,这对于处理财务、金融等领域中对精度要求极高...

    详谈Java中BigDecimal的一个除法异常

    在Java编程中,`BigDecimal` 类是用于进行高精度浮点数运算的,它解决了`float`和`double`类型在处理大数或精确计算时存在的精度问题。然而,在使用`BigDecimal`进行除法操作时,如果不进行特殊处理,可能会遇到`...

    Java中BigDecimal类与int、Integer使用总结

    总结起来,Java的`BigDecimal`类是解决浮点数精度问题的理想选择,尤其在商业计算中。它提供了丰富的API用于精确的数学运算,但使用时需注意构造器的选择和类型转换的正确操作。同时,应避免在不必要的场合使用...

    java-BigInteger-BigDecimal类源码

    在Java编程语言中,`BigInteger`和`BigDecimal`是两个重要的类,它们分别用于处理大整数和高精度浮点数。这两个类位于`java.math`包下,为开发者提供了超越基本数据类型(如int、long和double)的计算能力。在深入...

Global site tag (gtag.js) - Google Analytics