LePhone 3GC101 穿越 SSH  • • •  A Little HTTP       all posts in Archive

Java Floating-Point Operation

先看这个小程序会打印出什么值:

package net.villim.www;
public class TestFloat {
	public static void main(String[] args) {
		System.out.println(2.0 - 1.1);
	}
}

当然不会那么简单,绝对不可能是 0.9!!

实际打印出的值是 0.8999999999999999 确实让人大跌眼镜。发什么什么事情?

———————有兴趣,请自己先想想——————————

##为什么不能正确打印 0.9 ??

这里的猫腻当然就是 System.out.println(2.0 - 1.1); 简简单单的 “2.0”和 “1.1” 有什么问题呢?

原因就是计算机无法准确的描述 “1.1” 这个值! 我们知道计算机最终是用2进制来运算的。所以我们需要将 1.1 转化为二进制。

###十进制整数转二进制整数:

采用”除2取余,逆序排列”法。具体做法是:用2去除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为一时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。

###十进制小数转换成二进制小数:

采用”乘2取整,顺序排列”法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的整数部分为零,或者整数部分为1,此时1位二进制的最后一位。或者达到所要求的精度为止。

所以,假如我们用 8 位来表示 “1.1” 可以得到 “0000 0001 . 00011001” . 我们再将二进制转为十进制,得到的是 “1.09765625” !! 就是说计算机只能得到最接近 1.1 的值。更准确的描述应该是,不是所有的小数都能精确地用二进制浮点数表示

所以当我们需要精确使用小数的时候,怎么办呢? 应该使用 int,long 或者 BigDecimal

使用 BigDecimal 的时候也要格外注意,应该使用 BigDecimal(String) 而不是BigDecimal(double)!

假如尝试打印 BigDecimal(0.1) 你将得到  0.1000000000000000055511151231257827021181583404541015625!!  Orz ~~

###快速转二进制为十进制

比如 0.635 ,如何判断需要多少位才能精确地表示它呢?

根据前面的转换规则其实我们可以推断出简单的方法: 就用那个十进制乘以2的n次方,看小数部分是否为零,是的话就能用n位精确表示,否的话就不能。 比如,0.635256=162.56。因为小数部分不为0,所以8位二进制小数无法精确表示。再看0.63565536=41615.36。小数部分仍不为0,所以16位也不能精确表示。

如何快速转换呢? 比如 52.63 , 如果用8位表示小数部分,把0.63*256也就是2的8次方得到161.23,23不要,把161化成二进制表示为 10100001,再连上52的,就成了00110010.10100001。

###这个问题还有一个引申的关注点,计算机是如何做这个减法的呢?

这里有三个老概念,原码,补码和反码

以下用 X1 = + 1010 110 和 X2 = -1001010 来举例。

原码 (True Form):其符号位用0表示正号,用1表示负号,数值一般用二进制形式表示。

[X1]原=[+1010110]原=01010110  [X2]原=[-1001010]原=11001010

在原码表示法中,对0有两种表示形式:

[+0]原=00000000  [-0] 原=10000000

补码 (2’s complement): 补码可由原码得到。如果机器数是正数,则该机器数的补码与原码一样;如果机器数是负数,则该机器数的补码是对它的原码(除符号位外)各位取反,并在未位加1而得到的。

[X2] 原= 11001010  [X2] 补=10110101+1=10110110

在补码表示法中,0只有一种表示形式:

[+0]补=00000000  [+0]补=11111111+1=00000000(由于受设备字长的限制,最后的进位丢失)

反码 (1’s complement) 反码可由原码得到。如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码是对它的原码(符号位除外)各位取反而得到的。

[X1]反=[X1]原=01010110   [X2]反=10110101

在计算机系统里,数值一律用补码来表示(存储)。 主要原因:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补 码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。