浮点数简介

BigDecimal & Java基本数据类型

Posted by zhidaliao on October 23, 2012
介绍

浮点是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数

v = (-1)^s *  m * 2^e
  • (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
  • M表示有效数字,大于等于1,小于2。
  • 2^E表示指数位。

利用浮点进行运算,称为浮点计算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入。

适用场合:

浮点数牺牲精度提供更大取值范围,整型牺牲取值范围提供一致精度。

不适用场合:

不应该使用浮点数的常用场合只有一个,那就是:当你需要让一个变量精确的对应到现实中某个十进制小数时,不应当使用浮点小数来保存这个变量。当十进制小数与二进制浮点小数的转换不可避免,你又必须要求精度时,不应当使用浮点。

比方说 1.1 元钱,因为这个变量必须精确的等于十进制的 1.1,而变量本身是二进制,此时进制转换是必须的,所以你用浮点小数保存是错误的,你可以考虑用精确到分的 110 来表示这个数字,避免了小数。类似的,如果是计算器的话,如果用户是用十进制的格式输入,那么直接转换成二进制用计算机进行运算,最后再转换成十进制显示,这就不可避免的会有误差。

如果没有进制转换的需求,那么用浮点没有任何问题,甚至不会有精确性方面的问题。

精度不准确的原因

二进制小数不能用有限的数字表示 1/5,就如同十进制小数不能用有限的数字表示 1/3 一样

十进制小数 转 二进制:

0.8125的二进制
0.8125*2=1.625   取整是1

0.625*2=1.25     取整是1

0.25*2=0.5       取整是0

0.5*2=1.0        取整是1

即0.8125的二进制是0.1101

十进制小数 0.1 转 二进制 , 我们可以看到是无限循环的,又因为浮点在内存中的位数是固定的,导致精度没有足够的位数表示,多次的累加就会出现极大的误差。理论往下看。

0.1 * 2 = 0.2  取整是0 
0.2 * 2 = 0.4  取整是0
0.4 * 2 = 0.8  取整是0
0.8 * 2 = 1.6  取整是1
0.6 * 2 = 1.2  取整是1
0.2 * 2 = 0.4  取整是0
... 
底层原理

int num=9; 上面这条命令,声明了一个整数变量,类型为int,值为9(二进制写法为1001)。普通的32位计算机,用4个字节表示int变量,所以9就被保存为00000000 00000000 00000000 00001001,写成16进制就是0x00000009

v = (-1)^s *  m * 2^e

举例来说,十进制的5.0,写成二进制是101.0,相当于1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。

十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。

IEEE 754规定,对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。

image

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

IEEE 754对有效数字M和指数E,还有一些特别规定。

前面说过,1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

指数E

首先,E为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,E的真实值必须再减去一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。

比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

指数E的三种情况:

  • E不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。

  • E全为0。这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

  • E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。

例子

请问浮点数9.0,如何用二进制表示?还原成十进制又是多少?

首先,浮点数9.0等于二进制的1001.0,即1.001×2^3。

那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010。

所以,写成二进制形式,应该是s+E+M,即0 10000010 001 0000 0000 0000 0000 0000。这个32位的二进制数,还原成十进制,正是1091567616。

BigDecimal
BigDecimal d1 = new BigDecimal(0.6);  
BigDecimal d2 = new BigDecimal(0.4);  
BigDecimal d3 = d1.divide(d2);  
System.out.println(d3); 

会抛出异常,0.6和0.4是浮动类型的,浮点型放入BigDecimal内,其存储值为 0.59999999999999997779553950749686919152736663818359375 0.40000000000000002220446049250313080847263336181640625 ,这两个浮点数相除时,由于除不尽,而又没有设置精度和保留小数点位数,导致抛出异常,修改:

BigDecimal d3 = d1.divide(d2, 1, BigDecimal.ROUND_HALF_UP);  

原则:

  • 尽量避免传递double类型,有可能话,尽量使用int和String类型。

  • 做乘除计算时,一定要设置精度和保留小数点位数。

  • BigDecimal计算时,单独放到try catch内。

  • BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,由于创建对象会引起开销,因此它们不适合于大量的数学运算,应尽量使用long、float、double等基本类型做科学计算或者工程计算。设计BigInteger 与BigDecimal的目的是用来精确地表示大整数和小数,常用于商业计算中。

  • BigDecimal够造方法的参数类型有4种,其中的两个用BigInteger构造,另一个是用double构造,还有一个使用String构造。应该避免使用double构造BigDecimal,因为:有些数字用double根本无法精确表示,传给BigDecimal构造方法时就已经不精确了。 比如,new BigDecimal(0.1)得到的值是0.1000000000000000055511151231257827021181583404541015625。使用new BigDecimal(“0.1”) 得到的值是0.1。因此,如果需要精确计算,用String构造BigDecimal,避免用double构造,尽管它看起来更简单!

  • equals()方法认为0.1和0.1是相等的,返回true,而认为0.10和0.1是不等的,结果返回false。方法compareTo()则认为0.1与0.1相等,0.10与0.1也相等。所以在从数值上比较两个BigDecimal值时,应该使用compareTo()而不是 equals()。

  • 另外还有一些情形,任意精度的小数运算仍不能表示精确结果。例如,1除以9会产生无限循环的小数 .111111…。出于这个原因,在进行除法运算时,BigDecimal可以让您显式地控制舍入

参考网站

浮点数的二进制表示

十进制小数转二进制小数方法

浮点数

比较好的文章:

主题:再议浮点数

BigDecimal 的那些坑事儿

深入理解Java基本数据类型

byte 1 char 2 short 2 int 4 long 8 float 4 double 8 boolean int四字节,32位, 最大值应该是 2^31 - 1 ,为什么指数不是32,因为需要预留一位符号位 ,最小值 -2^31

浮点数

实数

有理数

无理数

虚数