java中基本浮点类型运算丢失精度解决方法

起因:

这其实是计算机内部机制导致的问题,具体就是计算机中存储浮点数的机制,导致浮点数如果参与了运算,那么就可能会丧失精度,产生预期之外的结果,当然这里只是可能,也有可能运算确实会恰巧复合预期结果。

如果想让基本浮点类型运算十分精确,java中有一个java.math.BigDecimal类,这个大数类可以完成精准的运算,我们可以把需要运算的基本浮点类转换成大数类,然后再调用BigDecimal中的方法运算,就可以保证结果的准确。

public static double add(double a1, double b1) { 
           BigDecimal a2 = new BigDecimal(Double.toString(a1)); 
           BigDecimal b2 = new BigDecimal(Double.toString(b1)); 
           return a2.add(b2).doubleValue(); 
          }

为什么要使用Double.toString(double value1)作为形参,而不是直接使用double value1作为形参,相加的结果为什么会有这么大的不同呢?直接在java API中找原因。

首先我们看double类的toString方法

toString

public static String toString(double d)

Returns a string representation of the double argument. All characters mentioned below are ASCII characters. 

 

通过说明,可以看出Double.toString(double value)方法返回的类型是String,而不是double类型了。

其次,既然形参的类型确定了,就要看调用BigDecimal构造方法有什么不同了,这里主要涉及到了BigDecimal(double val)和BigDecimal(String val)两种,区别如下。

 

BigDecimal(String val)

Translates the string representation of a BigDecimal into a BigDecimal.

 

将会把String型转换成BigDecimal.

 

BigDecimal(double val)

Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value.

 

将会把double型二进制浮点型值精确的转换成十进制的BigDecimal.(好难理解哦!)

API文档总结如下:

1.这个构造函数的结果有点难以预测。你可能认为java中用new BigDecimal(0.1)创建的BigDecimal应该等于0.1(一个是1的无精度的值,一个是有精度的值),但实际上精确的是等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1不能被double精确的表示(作为任意限定长度的二进制小数,实在不明白什么意思)。因此,传入构造函数的值不是精确的等于0.1。

2.而String参数的构造函数就能够得到很好的预测:如同我们认为的那样,new BigDecimal("0.1")完全等于0.1.因此,建议优先使用BigDecimal(String val)构造函数。
3.如果必须将double型传入BigDecimal,要注意该构造函数是一个精确的转换,它无法得到与先调用Double.toString(double)方法将double转换成String,再使用BigDecimal(String)构造函数一样的结果。如果要达到这种结果,要使用静态valueOf(double)方法。

即也可以使用静态valueOf(double)方法:

public static double add(double a1,double b1) {
        BigDecimal a2 = BigDecimal.valueOf(a1);
        BigDecimal b2 = BigDecimal.valueOf(b1);
        return a2.add(b2).doubleValue();
    }

 

总结:

要想将double转换成BigDecimal,要先用toString方法将double转换成String,再转换成BigDecimal,再进行运算,运算完成再转换回double返回:

public static double add(double a1, double b1) { 
           BigDecimal a2 = new BigDecimal(Double.toString(a1)); 
           BigDecimal b2 = new BigDecimal(Double.toString(b1)); 
           return a2.add(b2).doubleValue(); 
 }

 

或者使用BigDecimal类中静态valueOf(double)方法:

public static double add(double a1,double b1) {
        BigDecimal a2 = BigDecimal.valueOf(a1);
        BigDecimal b2 = BigDecimal.valueOf(b1);
        return a2.add(b2).doubleValue();
    }

 

以下是摘抄的BigDecimal方法:

public class DoubleUtil implements Serializable {
    private static final long serialVersionUID = -3345205828566485102L;
    // 默认除法运算精度
    private static final Integer DEF_DIV_SCALE = 2;

    /**
     * 提供精确的加法运算。
     *
     * @param value1 被加数
     * @param value2 加数
     * @return 两个参数的和
     */
    public static Double add(Double value1, Double value2) {
        BigDecimal b1 = new BigDecimal(Double.toString(value1));
        BigDecimal b2 = new BigDecimal(Double.toString(value2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精确的减法运算。
     *
     * @param value1 被减数
     * @param value2 减数
     * @return 两个参数的差
     */
    public static double sub(Double value1, Double value2) {
        BigDecimal b1 = new BigDecimal(Double.toString(value1));
        BigDecimal b2 = new BigDecimal(Double.toString(value2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精确的乘法运算。
     *
     * @param value1 被乘数
     * @param value2 乘数
     * @return 两个参数的积
     */
    public static Double mul(Double value1, Double value2) {
        BigDecimal b1 = new BigDecimal(Double.toString(value1));
        BigDecimal b2 = new BigDecimal(Double.toString(value2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时, 精确到小数点以后10位,以后的数字四舍五入。
     *
     * @param dividend 被除数
     * @param divisor  除数
     * @return 两个参数的商
     */
    public static Double divide(Double dividend, Double divisor) {
        return divide(dividend, divisor, DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
     *
     * @param dividend 被除数
     * @param divisor  除数
     * @param scale    表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static Double divide(Double dividend, Double divisor, Integer scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(dividend));
        BigDecimal b2 = new BigDecimal(Double.toString(divisor));
        return b1.divide(b2, scale,RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * 提供指定数值的(精确)小数位四舍五入处理。
     *
     * @param value 需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double value,int scale){
        if(scale<0){
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(value));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one,scale, RoundingMode.HALF_UP).doubleValue();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值