您的位置 首页 java

「Java」「计算结果的精度问题」

转载请注明,原文地址:

 
  

简单说明

文中有些内容并没有经过测试或找到相关证据证明其正确性的总结都已标注了,如果发现文中那些内容有误,请指出并附上相关知识的链接.

环境

 jdk:1.8
Eclipse:Version: 2019-12 (4.14.0)
  

1.有效数字,和可靠数字和存疑数字

有效数字是指在分析工作中实际能够测量到的数字。能够测量到的是包括最后一位估计的,不确定的数字。 我们把通过直读获得的准确数字叫做可靠数字;把通过估读得到的那部分数字叫做存疑数字。把测量结果中能够反映被测量大小的带有一位存疑数字的全部数字叫有效数字。另外在数学中,有效数字是指在一个数中,从该数的第一个非零数字起,直到末尾数字止的数字称为有效数字,如0.618的有效数字有三个,分别是6,1,8。

2. float 所表示的范围

构成

  浮点数 在机内用指数型式表示,分解为:数符,尾数,指数符,指数四部分。
数符占 1 位 二进制 ,表示数的正负。
指数符占 1 位二进制,表示指数的正负。
尾数表示浮点数有效数字,0.xxxxxxx, 但不存开头的 0 和点。
指数存指数的有效数字。
指数占多少位,尾数占多少位,由计算机系统决定。
  

指数和尾数

「Java」「计算结果的精度问题」

float 在计算机中表示是32位 sign(1bit)+exponent(8)+fraction(23)8位指数( 偏移量 为127) 本来应该可以表示-128~127,但是全0和全1被用来表示特殊状态的指数(也就是被当作数符和指数符,用于表示正负号)所以为-126~127. 这里为什么要使用127来作为偏移量,若使用128 则8位移码表示范围-127~126.由于表示一个大的正数比一个小的负数更加重要所以127作为偏移量比较合适。现在可以计算其表示范围了:

 尾数部分的取值范围[1,2),所以
最小负数-2*2^127 最大负数-1*2^(-126)
最小正数 1*2^(-126) 最大正数2*2^(127)
所以才说float可以表示的数的范围为-3.4*10(-38)~3.4*10(38)也可以写成-3.4*E(-38)~3.4*E(38),E表示的是10。
那么为什么说Float的有效数字是6到7位呢?我们知道有效数字是由尾数来表示的,而尾数的长度是2的23位,转成10进制就是8 388 608。可以看到有效数字的范围是0到8 388 608,大于8 388 608的数字无法表示,也就是说有效数字7位以上的数它表示不完,“所以才会说Float的有效数字是6到7位(这句话是我的猜测,我找不到资料证明这句话是否正确)”。
  

3.测试

1)测试一

         float x = 3.1984f;
        double y = 3.142840395056;
        //测试加减乘除的计算结果精度
        System.out.println("test (x+0.10f):"+(x+0.10f));//3.2984
        System.out.println("test (x+0.1):"+(x+0.1));//3.2984000205993653
        System.out.println("test (x+(float)0.1):"+(x+(float)0.1));//3.2984
        System.out.println("test (x+0.00001f):"+(x+0.00001f));//3.19841
        System.out.println("test (x+0.000001f):"+(x+0.000001f));//3.198401
        System.out.println("test (x+0.0000001f):"+(x+0.0000001f));//3.1984

        System.out.println("test (x+0.10d):"+(x+0.10d));//3.2984000205993653
        System.out.println("test (x+0.00001d):"+(x+0.00001d));//3.1984100205993653
        System.out.println("test (x+0.000001d):"+(x+0.000001d));//3.1984010205993654
        System.out.println("test (x+0.0000001d):"+(x+0.0000001d));//3.198400120599365

        System.out.println("test (1+0.10f):"+(1+0.10f));//1.1
        System.out.println("test (1+0.00001f):"+(1+0.00001f));//1.00001
        System.out.println("test (1+0.000001f):"+(1+0.000001f));//1.000001
        System.out.println("test (1+0.0000001f):"+(1+0.0000001f));//1.0000001
        System.out.println("test (1+0.10d):"+(1+0.10d));//1.1
        System.out.println("test (1+0.00001d):"+(1+0.00001d));//1.00001
        System.out.println("test (1+0.000001d):"+(1+0.000001d));//1.000001
        System.out.println("test (1+0.0000001d):"+(1+0.0000001d));//1.0000001

        System.out.println("test (x-0.10f):"+(x-0.10f));//3.0984
        System.out.println("test (x-0.1):"+(x-0.1));//3.098400020599365
        System.out.println("test (x-(float)0.1):"+(x-(float)0.1));//3.0984
        System.out.println("test (x-0.00001f):"+(x-0.00001f));//3.19839
        System.out.println("test (0.1f-0.0000001f):"+(0.1f-0.0000001f));//0.099999905
        System.out.println("test (0. 1f -0.0999991f):"+(0.1f-0.0999991f));//9.0152025E-7

        System.out.println("test (x*0.10f):"+(x*0.10f));//0.31984
        System.out.println("test (x*0.1):"+(x*0.1));//0.31984000205993657
        System.out.println("test (x*(float)0.1):"+(x*(float)0.1));//0.31984
        System.out.println("test (x*0.001f):"+(x*0.001f));//0.0031984001
        System.out.println("test (x*0.0001f):"+(x*0.0001f));//3.1984E-4


        System.out.println("test (x/0.10f):"+(x/0.10f));//31.984
        System.out.println("test (x/0.1):"+(x/0.1));//31.984000205993652
        System.out.println("test (x/(float)0.1):"+(x/(float)0.1));//31.984
        System.out.println("test (x/100f):"+(x/100f));//0.031984

        System.out.println("test (x/0.1f):"+(x/0.1f));//31.984
        System.out.println("test (x/0.1000f):"+(x/0.1000f));//31.984
        System.out.println("test (x/0.10000f):"+(x/0.10000f));//31.984
  

总结:从上面的加减乘除例子可以看出(1)两个float类型的数字进行加减乘除运算后得出的结果,如果小数点后面的数字大于等于7位,那么得出的结果的有效数位和我们日常中的计算是不一样的

  如上例子:
 float x=3.1984;
 System.out.println("test (x+0.000001f):"+(x+0.000001f));//3.198401
 System.out.println("test (x+0.0000001f):"+(x+0.0000001f));//3.1984,按理应该得3.1984001
 和
 System.out.println("test (x*(float)0.1):"+(x*(float)0.1));//0.31984
 System.out.println("test (x*0.001f):"+(x*0.001f));//0.0031984001,按理应该得0.0031984
 System.out.println("test (x*0.0001f):"+(x*0.0001f));//3.1984E-4.0.00031984
  

(2)两个float类型的数字进行加减乘除运算后得出的结果,如果小数点后面的数字小于7位,那么得出的结果和我们日常得出的结果是一致的(3)而一个int类型与float或 double 类型进行相加或相减(前题是相加相减的结果没有超出范围),不管小数点后面的位数数字是否小于还是大于7位,得出的结果和我们日常得出的结果是一致的.

这是因为计算机都是进行二进制计算的,整形可以用二进制很明确的表示出来,而小数有时候是无法确切的表示出来的.例如
0.6的二进制表示(乘2取整,顺序表示)
.1001 1001 1001 1001 1001 1001 1001 … 无限循环下去。
int类型和float类型进行相加,计算过程是二进制计算,但是结果却是一float的精度进行四舍五入得出结果,正如1+0.00001f=1.00001
而两个float类型相加,如果他们两个都不能明确的用二进制表示出来,那么他们虽然显示的是一个有限的小数,但是在计算机里二进制是表示不出来的,因为在二进制里他是无限循环或者无限不循环的,所以计算机返回的结果就只是一个不精确的数值

2)测试二

         double a = 0.01;
        double b = 0.09;
        System.out.println("a+b:"+(a+b));//0.09999999999999999
        float e = 0.01f;
        float f = 0.09f;
        System.out.println("e+f:"+(e+f));//0.1
        float g = 0.01f;
        float h = 0.04f;
        System.out.println("g+h:"+(g+h));//0.049999997

         BigDecimal  bd_1 = new BigDecimal(x+0.10f);
        System.out.println("BigDecimal(x+0.10f):"+bd_1);//3.29839992523193359375
        BigDecimal bd_2 = new BigDecimal(x+0.1);
        System.out.println("BigDecimal(x+0.1):"+bd_2);//3.298400020599365323192841970012523233890533447265625
        //上面的两个相加操作会导致输出结果不是我们想要的,x=3.1984f,x+0.10f结果等于3.2984才是我们想要的结果
        //即使我使用BigDecimal的add方法进行相加,也是会多出很多位
        BigDecimal bd_4 = new BigDecimal(x);
        BigDecimal bd_5 = new BigDecimal(0.10f);
        System.out.println("bd_4.add(bd_5):"+bd_4.add(bd_5));//3.298400022089481353759765625
        //只有先将x和0.10f转为String,再转为BigDecimal,使用它的add方法计算才能得到3.2984
        BigDecimal bd_6 = new BigDecimal(Float. toString (x));
        BigDecimal bd_7 = new BigDecimal(Float.toString(0.10f));
        System.out.println("bd_6.add(bd_7):"+bd_6.add(bd_7));//3.2984
  

总结:

(1)如果要获取精确的值,可以现将浮点数转为String,再转为BigDecimal,使用它的add等方法进行计算,得到的结果是准确的,但是由于BigDecimal的进度太高,理论上计算耗费的时间会比float和double大

(2)对于0.1这样的数值,它默认是double类型,和它进行加减乘除得出的结果都会保留到小数点后16位

(3)当然对于float类型,如果计算的结果小数点后没有大于或等于7位,得到的结果应该还是可信的((这里我没有进行大量的实验证明,也没有翻阅到相关的知识证明).

文章来源:智云一二三科技

文章标题:「Java」「计算结果的精度问题」

文章地址:https://www.zhihuclub.com/178536.shtml

关于作者: 智云科技

热门文章

网站地图