java浮点数精度损失原理和解决

原理

浮点数会有精度损失,如下代码所示

public class NumTest {
    public static void main(String[] args) {
        double a =1;
        double b =0.99;

        System.out.println(a-b);
    }
}

这段代码运行结果很简单,不是0.01么?!

  在这一天之前如果问我结果是什么我也会毫不犹豫的答道0.01,然而真实的结果是:0.010 00000 00000 00009。

  虽然运算结果不太对,但是这个结果和0.01相差不大会产生影响么?那看下面这一段代码:

public class NumTest {
    public static void main(String[] args) {
        double a =1;
        double b =0.99;

        System.out.println(a-b);
        if((a-b) == 0.01){
            System.out.println("1 - 0.99 == 0.01");
        }else{
            System.out.println("1 - 0.99 != 0.01");
        }
    }
}

输出的结果也在意料之中:1 - 0.99 != 0.01

  如果在程序中直接使用double会造成精度损失,极有可能对造成一些莫名奇妙的bug。

  但是所有的浮点数都会有精度损失么?
  

package sort;

public class NumTest {
    public static void main(String[] args) {
        double a = 0.5;
        double b = 0.25;

        System.out.println(a-b);
        if((a-b) == 0.25){
            System.out.println("no problem");
        }else{
            System.out.println("has problem");
        }

    }
}

输出的结果是:no problem,也就是说double类型的0.5和0.25在运算的时候没有出现精度损失。

  关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用01010形式表示的,浮点数也不例外。

    比如11,11除以2等于5余1

         5除以2等于2余1

         2除以2等于1余0

         1除以2等于0余1

  所以11二进制表示为:1011.

  double类型占8个字节,64位,第1位为符号位,后面11位是指数部分,剩余部分是有效数字。

  正整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可。

  举个例子:0.99用的有效数字部分,

        0.99 * 2 = 1+0.98 –> 1

        0.98 * 2 = 1+ 0.96 –> 1

        0.96 * 2 = 1+0.92 – >1

        0.92 * 2 = 1+0.84 – >1

          ……………

  这样周而复始是没法有尽头的,而double有效数字有限,所以必定会有损失,所以二进制无法准确表示0.99,就像十进制无法准确表示1/3一样。
  

解决方案

一般遇到这种需要用到浮点数运算的地方都可以使用java.math.BigDecimal。

  首先需要注意的是,直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在数据库中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。
  

package sort;

import java.math.BigDecimal;

public class NumTest {
    public static void main(String[] args) {
        String a = "301353.0499999999883584678173065185546875";
        double c = 301353.0499999999883584678173065185546875d;
        BigDecimal sa = new BigDecimal(a);
        BigDecimal sc = new BigDecimal(String.valueOf(c));
        BigDecimal dc = new BigDecimal(Double.toString(c));

        System.out.println("sa : "+ sa);
        System.out.println("sc : "+ sc);
        System.out.println("dc : "+ dc);

    }
}

上述代码的输出结果是:

  sa : 301353.0499999999883584678173065185546875

  sc : 301353.05

  dc : 301353.05

所以最好的方法是完全抛弃double,用string和java.math.BigDecimal。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
唔,其实里面就是一个工具类,加减乘除、保留两位小数。一共5个方法。。。emmmm.....为啥分这么高呢。因为宝宝想分想疯了。 附代码,有土豪就打赏打赏,没土豪的直接拿去使吧。 package cn.cisdom.base.utils; import java.math.BigDecimal; import java.text.DecimalFormat; public class Calculation { public static final DecimalFormat df = new DecimalFormat("######0.00"); /** * @methodName format2point * @desc 保留两位小数点 * @param value * @return java.lang.String * @author xm * @create 2018/6/7 12:03 **/ public static String format2point(Number value) { return df.format(value); } public static Double add(Number value1, Number value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.add(b2).doubleValue(); } /** * 提供精确的减法运算。 * * @param value1 * 减数 * @param value2 * 被减数 * @return 两个参数的差 */ public static Double sub(Number value1, Number value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.subtract(b2).doubleValue(); } /** * 提供精确的乘法运算。 * * @param value1 * 被乘数 * @param value2 * 乘数 * @return 两个参数的积 */ public static Double mul(Number value1, Number value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.multiply(b2).doubleValue(); } /** * 提供精确的除法运算。 * * @param value1 * 除数 * @param value2 * 被除数 * @return 除数/被除数 */ public static Double div(Number value1, Number value2) { //MathContext mc = new MathContext(2, RoundingMode.HALF_DOWN);//精度为2,舍入模式为大于0.5进1,否则舍弃 BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue())); BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue())); return b1.divide(b2).doubleValue(); } public static void main(String[] args) { Double aDouble=Calculation.add(56.9, 1.67); System.out.println(aDouble); Double bDouble=Calculation.sub(99.2,aDouble); System.out.println(bDouble); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值