java梳理-浮点类型计算为什么不准确

 本文属于java基础梳理系列:
原问题:62、浮点类型为什么有时候不精确,详细说出来,怎么解决
先来看个demo:

import java.math.BigDecimal;

/**
*
* @author zhangliang
*
* 2016年3月30日 下午5:58:39
*/
public class FloatTest {

public static void main(String[] args) {
float a = 2.0f -1.1f;
System.out.println(a);
double b = 2.0d -1.1d;
System.out.println(b);
double c = sub(2.0d,1.1d);
System.out.println(c);
float unit = Float.valueOf(1000);//取出换算单位1000.0
float svt = ((20000000f+1))/unit;//加0.001
System.out.println(String.valueOf(svt));//精度丢失
double unit1 = Float.valueOf(1000);//取出换算单位1000.0
double svt1 = ((20000000d+1))/unit1;//加0.001
System.out.println(String.valueOf(svt1));//ok
System.out.println(Long.toBinaryString( Double.doubleToLongBits(20000001d)));
System.out.println(Integer.toBinaryString(Float.floatToIntBits(20000001)));
System.out.println(Integer.toBinaryString(Float.floatToIntBits(16777215)));
System.out.println(Integer.toBinaryString(Float.floatToIntBits(16777216)));
System.out.println(Integer.toBinaryString(20000001));
float f = 20000001;
System.out.println(f);
double d =20000001;
System.out.println(d);
}
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
}


输出为:
java梳理-浮点类型计算为什么不准确 - bohu83 - bohu83的博客
 
 
那么久来看下java关于这块的有关知识
一 内存结构

Java 语言支持两种基本的浮点类型: float 和 double java 的浮点类型都依据 IEEE 754 标准。IEEE 754 定义了32 位和 64 位双精度两种浮点二进制小数标准。

IEEE 754 用科学记数法以底数为 的小数来表示浮点数。32 位浮点数用 位表示数字的符号,用 位来表示指数,用 23 位来表示尾数,即小数部分。作为有符号整数的指数可以有正负之分。小数部分用二进制(底数 2 )小数来表示。对于64 位双精度浮点数,用 位表示数字的符号,用 11 位表示指数,52 位表示尾数。

无论是单精度还是双精度在存储中都分为三个部分:

  1. 符号位(sign bit):1为负数,0为正数
  2. 指数(exponent biasing):使用的是biased exponent, 也被称为移码。移码就是将数值加上某个偏移量(单精度是127,双精度是加上1023)。为什么用移码,wiki解释:使用二进制的补码来存储,不方便于比较。使用移码,负值变为正值[-126,127]变为[1,254],方便对阶。
  3. 尾数(小数):最高一位省略. 当1 < exponent < 2e ? 2,这个省略的一位是1,此时我们称之为规范化的数(Normalized numbers)。如果指数为0,小数部分不为0,这个省略的一位是0,我们称之为Denormalized numbers。当指数和小数部分都是0时,这个省略位也为0,根据符号位的不同,表示正负0。其他就(Infinities和NaNs)不作介绍。
   参见下图
            float(32位):
  java梳理-浮点类型计算为什么不准确 - bohu83 - bohu83的博客
            double(64位):
  java梳理-浮点类型计算为什么不准确 - bohu83 - bohu83的博客
  于是,float的指数范围为-128~+127,而double的指数范围为-1024~+1023,并且指数位是按补码的形式来划分的。

其中负指数决定了浮点数所能表达的绝对值最小的非零数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围。

float的范围为-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308。

二 精度
  float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
只说概念不容易理解,我自己也是对10进制更熟悉,还是要转换到计算机的二进制上来,结合上面的demo来理解。
比如20000001,10进制就是2.0000001*10^7
对应的二进制就是: 1001100010010110100000001
然后normalized,就是 1.001100010010110100000001*2^24 得到指数为24+127=151 (10010111),
小数为 .001100010010110100000001 (最高位省略),符号数为0。因此其单精度浮点数为:
10010111       001100010010110100000001
我们可以对比下日志输出的结果,发现尾数比实际输出多1, 其小数部分为24位,而单精度的浮点数尾数位只有23位。
这就是看出超出单精度的表示范围, IEEE754的Rounding alorithms。会 用一个估计值(二进制准确表示的值)来代替原有值,最常用的就是找一个最接近的二进制值来表示。也就是我们看到的(没有输出符号位,前面补0就是32位)
10010111        00110001001011010000000
分析结果如下:
10000010111 0011000100101101000000010000000000000000000000000000  // 20000001d
10010111 00110001001011010000000   // 20000001f
10010110 11111111111111111111111   //16777215
10010111 00000000000000000000000  //16777216
1001100010010110100000001 // 20000001二进制
也就是说 20000001虽然是在float的表示范围之内,但 在 IEEE 754 的 float 表示法精度长度没有办法表示出 20000001 ,而只能通过四舍五入得到一个近似值。从上面输出可以看出,小于2^24(16777216)的正整数,单精度浮点数可以准确表示。
支持也就是明白了float超精度,而double可以正常显示的原因了。
下面再来看看解决浮点不准确的解决: BigDecimal
demo 有简单示例
实际跟财务有关的计算,单位可以结合实际以分或者厘为单位。
***********************************************
参考:
http://blog.csdn.net/firecoder/article/details/5816237
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值