Java中对数字的处理,如四舍五入,如加减乘除,貌似是一个很基础很简单的知识点,但是如果你没有对他进行充分了解,很容易掉进它的陷阱里。
1、浮点数运算
先来看一个对浮点数作运算的例子,请问会输出什么:
System.out.println(4.015*100);
结果可能会让你大跌眼镜,不是401.5,而是401.49999999999994,这就要涉及到浮点数的一些知识点,Java中,浮点类型是依据IEEE754标准,IEEE754定义了32位和64位双精度两种浮点二进制小数标准,而采用二进制表示double,float浮点数是不准确的(可点击阅读原文了解详情),如果想获得准确的结果,可以使用BigDecimal类:
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")).doubleValue());
System.out.println(BigDecimal.valueOf(4.015).multiply(BigDecimal.valueOf(100)).doubleValue());
以上输出结果都是401.5,如果你觉得到此已经避开了精度缺失的陷阱,那就再看下面这个例子:
System.out.println(new BigDecimal(0.1));
//result:0.1000000000000000055511151231257827021181583404541015625
这个构造器BigDecimal(double val)的结果是不可预知的,这里,0.1无法准确地表示为 double类型(一个有限长度的二进制小数),传入到构造方法的参数值并不完全等于 0.1。而BigDecimal(String val)的结果则是可预知的,所以我们一般优先使用BigDecimal(String val)型的构造函数。
2、四舍五入
再来瞅瞅四舍五入,或许你觉得以下代码貌似可行:
DecimalFormat df = new DecimalFormat("#.000");
System.out.println(df.format(203.0675));
//203.068
我们换一个数字看看:
DecimalFormat df = new DecimalFormat("#.000");
System.out.println(df.format(203.0665));
//203.066
奇怪不,四舍五入的规则换一个数字怎么就失效了呢,如果你研究过java中RoundingMode,你就会猜到它默认使用的是RoundingMode.HALF_EVEN,即如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同RoundingMode.HALF_DOWN,这是银行家舍入法,在美国比较流行。那我们重新设置下模式就可以了。
DecimalFormat df = new DecimalFormat("#.000");
df.setRoundingMode(RoundingMode.HALF_UP);
System.out.println(df.format(203.0665));
//203.067
关于四舍五入,还有很多方法,请思考以下方法。
(1)、System.out.println(String.format("%.3f", 203.6665));
(2)、System.out.println(new BigDecimal(String.valueOf(203.6665)).setScale(3, BigDecimal.ROUND_HALF_UP));
(3)、System.out.println(Math.round(203.6665 * 1000) / 1000.0d);
3、包装器类型
最糟糕的是碰到值相等但对象不是同一个,或者为null的情况,前者比较结果都是错的,后者会报NullPointerException。
Integer a = new Integer(33);
Integer b= new Integer(33);
if(a==b){
System.out.println("a与b相等");
}else{
System.out.println("a与b不相等");
}
//a与b不相等
由于a,b指向的是不同的实例,最后比较结果是不相等,这和我们期望的比较结果往往是不同的,如果把赋值为null,程序便会报错。
所以我们需要优先使用基本数据类型,在一些特别场合可以使用包装器类型,如使用集合类时对元素的操作,使用泛型时设置类型参数等等,在这些场景中,基本数据类型不允许被使用,正是包装器类型上场的时候。