关于java:Java中浮点数的坑

32次阅读

共计 4355 个字符,预计需要花费 11 分钟才能阅读完成。

根本数据类型



浮点数存在误差

浮点数有一个须要特地留神的点就是浮点数是有误差的,比方以下这段代码你感觉输入的什么后果:

public class Demo {public static void main(String[] args) {System.out.println(0.1+0.2 == 0.3);// 输入 false
    }
}

这段代码输入值是 false,之所以是这个后果那是因为浮点数是存在误差的,也就 yi 是说 0.1 在计算机中存储时不是准确的 0.1,而有可能是 0.1000000001,或者其余数,而 0.2 或 0.3 也是如此,所以 0.1+0.2 和 0.3 在计算机中是不相等的。

因为浮点数存在这个个性,所以咱们在编程两头要尽量避免用浮点数进行比拟。

如果非要用浮点数进行比拟的话,那能够应用上面这个办法:

public class Demo {public static void main(String[] args) {float n = (float)1e-6;// 示意 10 的 - 6 次方
        System.out.println(0.1+0.2 - 0.3 < n);// 输入 true
    }
}

以上代码的输入值是 true,该办法的原理是如果两个数相差足够小,小到能够疏忽不记的话,这里的界线设置是 10 的 - 6 次方,那证实比拟的这两个数能够认为是相等的,此办法只能在所示意的浮点数的小数点后的位数不是很多的时候应用。

接下来咱们再来看一种极其的状况,代码如下:

public class Demo {public static void main(String[] args) {System.out.println(0.30000000000000001 == 0.3);// 输入 true
    }
}

以上的代码输入 true,但其实咱们肉眼能够很直观的看出,这两个数尽管很靠近,但他们相对不相等,像这种极其的数咱们是无奈用下面的办法进行比拟的,所以还是记住这句话:尽量避免对浮点数进行比拟。


BigDecimal 类

咱们既然晓得了浮点数是存在误差的,所以在数据自身须要精确精度存储时,咱们是肯定不会应用 float 和 double 的,比方金钱数额的存储。这时咱们通常应用 BigDecimal 类进行存储,它是一个能够存储精确浮点数的类。


  1. BigDecimal类的定义:
   BigDecimal bd = new BigDecimal("123.456");
  1. BigDecimal应用 scale() 示意小数位数,例如:
   BigDecimal d1 = new BigDecimal("987.65");
   BigDecimal d2 = new BigDecimal("987.6500");
   BigDecimal d3 = new BigDecimal("98765400");
   System.out.println(d1.scale()); // 2, 示意两位小数
   System.out.println(d2.scale()); // 4
   System.out.println(d3.scale()); // 0
  1. BigDecimal中的 stripTrailingZeros() 办法,能够将 BigDecimal 格式化为去掉数值开端 0 的相等的数:
   BigDecimal d1 = new BigDecimal("123.4500");
   BigDecimal d2 = d1.stripTrailingZeros();
   System.out.println(d1+" "+d1.scale()); // 123.4500  4
   System.out.println(d2+" "+d2.scale()); // 123.45  2, 因为去掉了 00
   
   BigDecimal d3 = new BigDecimal("1234500");
   BigDecimal d4 = d3.stripTrailingZeros();
   System.out.println(d3+" "+d3.scale()); // 1234500  0
   System.out.println(d4+" "+d4.scale()); // 1.2345E+6  -2

BigDecimalscale() 返回正数,例如,-2,示意这个数是个整数,并且开端有 2 个 0。以上的 d4 就是如此,去掉 0 后数值没变,只是换了一种示意办法。

  1. BigDecimal能够设置它的scale,如果精度比原始值低,那么依照指定的办法进行四舍五入或者间接截断:
   import java.math.BigDecimal;
   import java.math.RoundingMode;
   
   public class Demo {public static void main(String[] args) {BigDecimal d1 = new BigDecimal("123.456789");
           BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
           BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 间接截断,123.4567
           System.out.println(d2);//123.4568
           System.out.println(d3);//123.4567
       }
   }
  1. BigDecimal的加、减、乘、除:
   import java.math.BigDecimal;
   
   public class Demo {public static void main(String[] args) {BigDecimal d1 = new BigDecimal("124.44");
           BigDecimal d2 = new BigDecimal("12.2");
           System.out.println(d1.add(d2));//d1+d2   136.64
           System.out.println(d1.subtract(d2));//d1-d2  112.24
           System.out.println(d1.multiply(d2));//d1*d2  1518.168
           System.out.println(d1.divide(d2));//d1/d2   10.2
       }
   }
  1. BigDecimal在做加、减、乘时,精度不会失落,然而做除法时,存在无奈除尽的状况,这时就必须指定精度以及如何进行截断:
   import java.math.BigDecimal;
   import java.math.RoundingMode;
   
   public class Demo {public static void main(String[] args) {BigDecimal d1 = new BigDecimal("123.456");
           BigDecimal d2 = new BigDecimal("23.456789");
           BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留 10 位小数并四舍五入
           BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
       }
   }
  1. 能够对 BigDecimal 做除法的同时求其余数:
   import java.math.BigDecimal;
   public class Demo {public static void main(String[] args) {BigDecimal n = new BigDecimal("22.444");
           BigDecimal m = new BigDecimal("0.23");
           BigDecimal[] dr = n.divideAndRemainder(m);
           System.out.println(dr[0]); // 97.0
           System.out.println(dr[1]); // 0.134
       }
   }
  1. 调用 divideAndRemainder() 办法时,返回的数组蕴含两个 BigDecimal,第一个是商,第二个是余数,商总是整数,余数不会大于余数,咱们能够利用该办法判断两个BigDecimal 是否是整数倍数:
   BigDecimal n = new BigDecimal("12.34");
   BigDecimal m = new BigDecimal("0.12");
   BigDecimal[] dr = n.divideAndRemainder(m);
   if (dr[1].signum() == 0) {//signum()会基于此 BigDecimal 返回三个值 -1、1、0,别离对应为该数小于 0,大于 0 和等于 0
       // n 是 m 的整数倍
   }
  1. 比拟两个 BigDecimal 的值是否相等时,要留神的是,应用 equals() 办法岂但要求两个 BigDecimal 的值相等,还要求它们的 scale() 相等:
   BigDecimal d1 = new BigDecimal("123.45");
   BigDecimal d2 = new BigDecimal("123.45000");
   System.out.println(d1.equals(d2)); // false, 因为 scale 不同
   System.out.println(d1.equals(d2.stripTrailingZeros())); // true, 因为 d2 去除尾部 0 后 scale 变为 2, 与 d1 雷同

留神:应用 compareTo() 来比拟两个 BigDecimal 的值,不要用equals()

  1. 应用 compareTo() 办法来比拟两数大小,它依据两个值的大小别离返回 -1、1 和0,别离示意小于、大于和等于。
    import java.math.BigDecimal;
    public class Demo {public static void main(String[] args) {BigDecimal d1 = new BigDecimal("123.45");
            BigDecimal d2 = new BigDecimal("123.45000");
            BigDecimal d3 = new BigDecimal("123.40");
            System.out.println(d1.compareTo(d2)); // 0
            System.out.println(d1.compareTo(d3));//  1
            System.out.println(d3.compareTo(d2));// -1
        }
    }
  1. 查看 BigDecimal 的源码,能够发现一个 BigDecimal 是通过一个 BigInteger 和一个 scale 来示意的,即 BigInteger 示意一个残缺的整数,而 scale 示意小数位数:
    public class BigDecimal extends Number implements Comparable<BigDecimal> {
        private final BigInteger intVal;
        private final int scale;
    }


更多精彩内容敬请关注微信公众号:【平兄聊 Java】

正文完
 0