float和double 的精度缺失问题以及在Java中的解决办法

来源:互联网 发布:药渡数据 编辑:程序博客网 时间:2024/06/09 21:38

由于数据表示精度问题,浮点数应慎用“==”比较相等,且浮点数的结合律有时不成立。例如:

  1. System.out.println((0.1+ 0.2) + 0.3);                                   //输出0.6000000000000001
    System.out.println(0.1 + (0.2 +0.3));                                  //输出0.6
    System.out.println((0.1+ (0.2 + 0.3)) == ((0.1 + 0.2) + 0.3));   //输出false
  2. 此处的0.3无法用二进制数精确表示,而0.5能用二进制数精确表示.

 

1.十进制小数如何转化为二进制数?
算法是乘以2直到没有了小数为止。举个例子,0.3表示成二进制数

                    0.3*2=0.6   取整数部分  0

                    0.6*2=1.2   取整数部分  1

                    0.2(1.2的小数部分)*2=0.4   取整数部分 0

                    0.4*2=0.8   取整数部分  0

                    0.8*2=1.6    取整数部分 1

                 0.6*2=1.2   取整数部分  1

                             .........     0.3二进制表示为(从上往下):0100110011...... 

          注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 而0.5能用二进制数精确表示0.5*2=1.0时能取整数部分其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。


2.float类型的存储

float类型的二进制存储是4个字节32位

1位符号位   8位阶码位 23位尾数位


(1)将十进制0.5转换为整数部分和小数部分的二进制 0.1

(2)转换为 1.0*2的-1次方

(3)符号位为0

(4)阶码位,e-127=-1,e=126,转换为二进制为01111110

(5)小数位,0

(6)40.125f的二进制表现形式为0011111100000000 00000000 00000000

float a=0.5f;

intb=Float.floatToIntBits(a);//Float类的静态方法,以int类型的方式返回这个小数的二进制形式

System.out.println(Integer.toBinaryString(b));


参考自:http://blog.csdn.net/why_still_confused/article/details/51193904

3.在Java中的解决方法

在Java中,我们使用BigDecimal这个类来解决浮点数的精度问题,这个类经常使用,如JDBC中,如果一个字段的数据库类型是Number, 那么getObject().getClass()的结果是java.math.BigDecimal。 

使用方法:

1.创建:BigDecimal total = new BigDecimal("0");  

里面的参数要求是字符串,因为这个是属于大数类,学过C语言的朋友都知道大数运算是怎么实现的。

2.基本的四则运算

add(BigDecimal augend)、subtract(BigDecimal subtrahend)

multiply(BigDecimal multiplicand)、divideToIntegralValue(BigDecimal divisor)参数类型也是BigDecimal

其中除法运算是最复杂的,因为商的位数还有除不断的情况下末位小数点的处理都是需要考虑的。
  new BigDecimal(-7.5).divide(new BigDecimal(1),0,BigDecimal.ROUND_UP);
  上面的这个运算中divide的第二个参数表示商的小数点位数,最后一个参数指的是近似处理的模式。一共有一下几个模式:
  BigDecimal.ROUND_UP 最后一位如果大于0,则向前进一位,正负数都如此。
  BigDecimal.ROUND_DOWN 最后一位不管是什么都会被舍弃。
  BigDecimal.ROUND_CEILING 如果是正数,按ROUND_UP处理,如果是负数,按照ROUND_DOWN处理。例如7.1->8; -7.1->-7;所以这种近似的结果都会>=实际值。
  BigDecimal.ROUND_FLOOR 跟BigDecimal_ROUND_CEILING相反。例如7.1->7;-7.1->-8。这种处理的结果<=实际值。
  BigDecimal.ROUND_HALF_DOWN 如果最后一位<=5则舍弃,如果>5, 向前进一位。如7.5->7;7.6->8;-7.5->-7
  BigDecimal.ROUND_HALF_UP 如果最后一位<5则舍弃,如果>=5, 向前进一位。反之舍弃。如7.5->8;7.4->7;-7.5->-8
  BigDecimal.ROUND_HALF_EVEN 如果倒数第二位是奇数,按照BigDecimal.ROUND_HALF_UP处理,如果是偶数,按照BigDecimal.ROUND_HALF_DOWN来处理。如7.5->8;8.5->8;7.4->7;-7.5->-8

3.转换为正常的数据类型

由于是Number的子类,所以继承了xxxValue()系列方法,故转换基本数据类型很容易,而且这个方法不会越界,比如就算double放不下,也不会发生错误,他会给你返回一个类似1.0E136的这种数字代表当前值。

还有一个类是BigInteger这个是大整数,两者方法类似,这里就不多做说明, 而且在BigDecimal中有方法可以代替BigInteger的作用 ,因为用处不大,这里就不多做说明了。

0 0
原创粉丝点击