在java中设置double = 0.1,然后打印出来,仍然是0.1.但是按照2进制浮点数的表示法,应该是不能精确表示0.1的,只能精确表示类似0.5,0.25这样的数.我想知道JAVA是怎么能够在某些情况下精确表示浮点数的.
再举个例子,0.4/4=0.1,而0.4-0.3=0.10000000000000003.
回答
恰好自己有过一点研究。
首先可以肯定,出现这种现象的原因并不是发生在二进制数的表示阶段,0.1确实无法用二进制浮点数精确表示,这个毋庸置疑。
之所以会出现本不能精确表示的浮点数在打印出来后反而是精确的,是因为在将其转换为字符串的过程中,Java耍了点小聪明。它会将看起来疑似是“整”的浮点数进行特殊处理,转换为那个“整”的十进制小数形式。
举个例子,比如0.1,如果按内部表示直接转换过来可能应该是0.1000…0011,这种情况下,最后两位的数值太小,Java就会认为是精度不够而产生的,于是转成字符串的时候会将其转为0.1。
而0.4/4和0.4-0.3,由于参与计算的时候,都至少有一个是不能精确表示的数,所以结果可能会有一定的误差。按照前面的例子,可能0.4/4的结果为0.1000…0010,最后一位与真正的0.1不同,而0.4-0.3的结果为0.1000…0300,这个误差就比较大了。
注:以上仅仅是我臆想的值,并不是真实的数据。能够说明问题即可。
由于两者误差不同,所以0.4/4的结果仍然会被认为是“整”的小数0.1,但0.4-0.3却由于累计的误差较大而被认为不是0.1,而是原样转换为0.1000…03。
@代码宇宙 已经回答了一部分.我再加上我自己的一些理解.
在声明double=0.1时,其实理论上不能这么声明的,因为浮点数中没有0.1.但是总不能编译器就直接报错吧,所以编译器将0.1转化为最接近0.1的一个浮点数.
在打印java小数时,我们可以发现一个现象,就是打印出来的精确度是一样的,都是16位.例如:
public static void main(String[] args) {
System.out.println(1d / 1212311);
System.out.println(2d / 3322);
System.out.println(3d / 3123);
}
输出
8.248708458473115E-7
6.020469596628537E-4
9.606147934678194E-4
至于为什么是16位,我猜测两点原因:1. 总得有个精度来对应除不尽的情况. 2. 因为16位精度的十进制数刚好能够区分双精度的浮点数,因为双精度浮点数的尾数是52位,2的52次方大小也是16位,这样可以避免大小不同的浮点数打印出一样的值.
然后解释为什么0.1和0.4/4打印出来是0.1,也就是为什么有时候能”精确”表示浮点数.这点@代码宇宙 已经解释了一部分.其实这只是java打印时只打印16位精度的数,所以打印时会打印出最接近该浮点数的16位精度的十进制数.所以0.1和0.4/4打印出来0.1是因为0.1是最接近该浮点数的结果.而在java内存中,0.1仍然是一个浮点数,并不等于十进制的0.1.
总结一下: java并不能吧0.1用double精确表示,而是某些情况下在打印的时候将浮点数转化成对应的十进制数时用了近似的值,看上去是精确的.
就算是十进制也有不能精确表示的浮点数啊,比如说1/3,你用是进制怎么表示?但是诸如1/10,3/10,1/100等等都能够用十进制精确表示的,所以,像1/2,1/4,1/8,3/8等等都是可以用二进制精确表示的。
Java通过Decimal和BigDecimal类来精确表示浮点数。
具体double和float怎么存储的,可以参考IEEE 754标准。