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

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

  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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Javafloatdouble都是用二进制浮点数表示的,它们的精度是有限的。因为二进制浮点数无法精确表示某些十进制数,如0.1,所以在进行精确计算时需要特别注意。 在进行浮点数计算时,可以使用BigDecimal类来实现精确计算。BigDecimal类可以表示任意精度的十进制数,而且支持加、减、乘、除等基本的数学运算。 下面是一个使用BigDecimal类进行浮点数计算的例子: ``` import java.math.BigDecimal; public class FloatDoublePrecision { public static void main(String[] args) { float f1 = 0.1f; float f2 = 0.2f; double d1 = 0.1; double d2 = 0.2; BigDecimal b1 = new BigDecimal(Float.toString(f1)); BigDecimal b2 = new BigDecimal(Float.toString(f2)); BigDecimal b3 = new BigDecimal(Double.toString(d1)); BigDecimal b4 = new BigDecimal(Double.toString(d2)); BigDecimal result1 = b1.add(b2); BigDecimal result2 = b3.add(b4); System.out.println("Float计算结果:" + result1); System.out.println("Double计算结果:" + result2); } } ``` 输出结果如下: ``` Float计算结果:0.300000011920928955078125 Double计算结果:0.3000000000000000444089209850062616169452667236328125 ``` 可以看到,使用floatdouble进行计算得到的结果都存在精度问题。而使用BigDecimal类进行计算可以得到精确的结果。 需要注意的是,使用BigDecimal类进行计算时需要使用字符串形式的构造方法,而不能直接使用浮点数进行构造,否则仍然会存在精度问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值