我们在使用Float,Double进行运算时,大多数情况下计算的结果是准确的,但是偶尔会出现一些并不正确的近似结果。其实不光是Java,在其它很多编程语言中也有这样的问题。这个问题相当严重!

正文

我们直接看一个例子:

//double的精确计算误差
	@Test
	public void testDouble() {
		double a = 0.1;
		double b = 0.2;
		double c = 0.3;
		
		System.out.println(a + b);		//0.30000000000000004
		System.out.println(c - a);		//0.19999999999999998
		System.out.println(a * b);		//0.020000000000000004
		System.out.println(c / a);		//2.9999999999999996
	}

很明显,上面的代码是绝对不可以用于银行内核心计算的,不然简直乱了套。为什么会出现这样的结果?在解决问题之前,我们先弄明白问题出在哪里。

归根结底,这个问题涉及到二进制与十进制的转换!!

我们知道计算机进行的是二进制运算,我们输入的十进制数字会先转换成二进制,进行运算后再转换为十进制输出。

而N进制可以理解为:数值×基数的幂,例如我们熟悉的十进制数123.4 = 1×102+2×101+3×100+4×10(-1);其它进制的也是同理,例如二进制数11.01 = 1×21+1×20+0+1×2(-2) = 十进制的3.25。

Double类型的数值占用64bit,即64个二进制数,除去最高位表示正负符号的位,在最低位上一定会与实际数据存在误差(除非实际数据恰好是2的n次方)。

如果这里不好理解,那我们再举个例子:如要用4bit来表示一个十进制小数3.26,从高到低位依次对应2的1,0,-1,-2次幂,根据最上面的分析,计算机内应当在二进制数11.01(对应十进制的3.25)和11.10(对应十进制的3.5)之间选择。显然这个区间里面除了我们需要的3.26,还有很多其他的数字。

再回到Double的64bit上来,同理:我们给出的数值,在大多数情况下需要比64bit更多的位数才能准确表示出来(甚至是需要无穷多位的,比如1/3),而Double类型的数值只有64bit,后面舍去的位数就一定会带来误差,无法得到“数学上精确”的结果。

你明白了吗?

小结

Float和Double类型为我们提供了快速运算,然而问题在于转换为二进制的时候,有些数字不能完全转换,只能无限接近于原本的值,这就导致了在后来的运算会出现不正确结果的情况。

可是编程中却不可避免的要对小数进行运算,精度问题如何处理?不用担心,Java为数学计算提供了精确的BigDecimal类,