首页 > 其他分享 >Double为什么会失真?

Double为什么会失真?

时间:2022-09-20 18:11:33浏览次数:87  
标签:为什么 0000 Double 浮点数 float 失真 double 127 小数

前言

先来看一个double失真的例子

public class DoubleTest {

    public static void main(String[] args) {
        for (double i = 0; i < 1; i = (i * 10 + 1) / 10) {
            for (double k = 0; k < i; k = (k * 10 + 1) / 10) {
                System.out.println(i + "-" + k + "=" + (i - k));
            }
        }
    }
}

输出:

0.1-0.0=0.1
0.2-0.0=0.2
0.2-0.1=0.1
0.3-0.0=0.3
0.3-0.1=0.19999999999999998
0.3-0.2=0.09999999999999998
0.4-0.0=0.4
0.4-0.1=0.30000000000000004
0.4-0.2=0.2
0.4-0.3=0.10000000000000003
0.5-0.0=0.5
0.5-0.1=0.4
0.5-0.2=0.3
0.5-0.3=0.2
0.5-0.4=0.09999999999999998
0.6-0.0=0.6
0.6-0.1=0.5
0.6-0.2=0.39999999999999997
0.6-0.3=0.3
0.6-0.4=0.19999999999999996
0.6-0.5=0.09999999999999998
0.7-0.0=0.7
0.7-0.1=0.6
0.7-0.2=0.49999999999999994
0.7-0.3=0.39999999999999997
0.7-0.4=0.29999999999999993
0.7-0.5=0.19999999999999996
0.7-0.6=0.09999999999999998
0.8-0.0=0.8
0.8-0.1=0.7000000000000001
0.8-0.2=0.6000000000000001
0.8-0.3=0.5
0.8-0.4=0.4
0.8-0.5=0.30000000000000004
0.8-0.6=0.20000000000000007
0.8-0.7=0.10000000000000009
0.9-0.0=0.9
0.9-0.1=0.8
0.9-0.2=0.7
0.9-0.3=0.6000000000000001
0.9-0.4=0.5
0.9-0.5=0.4
0.9-0.6=0.30000000000000004
0.9-0.7=0.20000000000000007
0.9-0.8=0.09999999999999998

一、 什么是浮点数?

1、小数

一个小数的组成:在我国,小数表示由三部分组成,分别是整数+小数点(分隔符)+小数。
image

2、小数为什么会被称为浮点数

浮点数是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学计数法。

对于浮点数可以这样简单的理解:浮点数就是小数点可以任意浮动的数字。

在计算机的机器语言中,只有二进制,机器语言只能识别0和1。所以,计算机也是不可能存储小数的,所以需要有另一种变通的存储方案。这种方案就是指数方案:
image

通过观察以上的图片不难发现,作为一个小数3.14。如果使用指数表现形式的话(3.14E0),其写法是多种多样的,这样写的话,小数点就可以任意浮动了。

3、Java中浮点数的表示方法

对于float来说,4个字节,32位,0-22位表示尾数,23-30(8位)表示指数,31位表示符号位。

对于double来说,8个字节,64位,0-51表示尾数,52-62(11位)表示指数,63位最高位表示符号位。

二、浮点数在内存中是如何存储的?

我们知道,任何数据在计算机内存中都是用‘0\1’来存储的,浮点数亦是如此。因此十进制浮点数在存储时必定会转换为二进制的浮点数。

在内存中使用二进制的科学计数法来存储,因此分为阶码(即指数)和底数,由于也有正负之分,所以还有一位符号位。
以float为例,float在内存中的存储为:
image

float 符号位(1bit) 指数(8 bit) 尾数(23 bit)

double 符号位(1bit) 指数(11 bit) 尾数(52 bit)

float在内存中占8位,由于阶码实际存储的是指数的移码,假设指数的真值是e,阶码为E,则有E=e+(2^n-1 -1)。其中 2^n-1 -1是IEEE754标准规定的指数偏移量,根据这个公式我们可以得到 2^8 -1=127。于是,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来说,指数位加上127,double位加上1023(这里指的是存储,在比较的时候要分别减去127和1023)

移位存储本质上是为了保证+0和-0的一致性。

以float指数部分的这8位来分析,

那么这8位组成的新的字节,我们来用下面的一串数字表示:0000 0000

首先,我们假设不使用移位存储技术,而是单单看看这个 8位组成的新字节,到底能表示多少个数: 0000 0000 -1111 1111 即0-255,一共256个数。

但是我们知道这8位数既要表示正数也要表示负数。

所以将左边第一位拿出来表示正负的符号:

第一个区间:

0 000 0000 - 0 111 1111
即+0 到127

第二个区间:

1 000 0000 - 1 111 1111
即 -0到-127

这就是问题的所在:怎么会有两个0,一个正零,一个负零。

这时候使用移位存储:float使用127(0111 1111)

表示0:0+127=127 即 0000 0000 +0111 1111=0111 1111
表示1:1+127=128 即 0000 0001 +0111 1111=1000 0000
表示128:128+127=255 即 1000 0000+0111 1111=1111 1111

最大的正数,再大就要溢出了。

表示-1: -1+127=126=127-1 即 0111 1111-0000 0001=0111 1110
表示-1: -2+127=125=127-2 即 0111 1111-0000 0010=0111 1101
表示-127: -127+127=0 即0111 1111-0111 1111=0000 0000

最小的负数,在校就溢出了。

三、浮点数的进制转换

1、十进制转二进制

主要看看十进制转二进制,整数部分和小数部分分开处理

  • 整数部分:整数除以2,得到一个商和余数,得到的商继续除以2并得到一个商和一个余数,继续除以2操作直至商为0,上述操作得到一系列余数,从最后一个余数开始直至第一个余数,这一系列0\1即为转换后的二进制数。

  • 小数部分:乘以2,然后取出整数部分,将剩下的小数部分继续乘以2,然后再取整数部分,一直取到小数部分为零为止。如果永远不为零,则按要求保留足够位数的小数,最后一位做0舍1入。将取出的整数顺序排列。

从以上转换过程可以看出,并不是任何一个十进制小数都可以用二进制精确表示出来。一个在0到1之间的小数P可用如下形式表示:
image

从这个式子中我们也可看出二进制表示出的小数是分段的,这也是为什么在Java中浮点数很多时候并不是十分精确的表示十进制小数的根本原因。

public static void main(String[] args) {
    float f1=20f;
    float f2=20.3f;
    float f3=20.5f;

    double d1=20;
    double d2=20.3;
    double d3=20.5;

    System.out.println(f1==d1);
    System.out.println(f2==d2);
    System.out.println(f3==d3);
}

true
false
true

以20.3举例:
20转换后变为 10100
0.3 要转换二进制,需要乘2, 乘完之后 取整数部分,然后用乘的结果减去整数部分, 然后 接着乘2, 直至最后没有小数或者小数出现循环, 即乘完.

0.3 * 2 = 0.6 (0)
0.6 * 2 = 1.2 (1)
0.2 * 2 = 0.4 (0)
0.4 * 2 = 0.8 (0)
0.8 * 2 = 1.6 (1)

计算到这里, 将再出现0.6,进入循环了,所以,结果
0.3 = 0.010011001…1001
所以20.3 = 10100.010011001…1001 (二进制).

2、二进制的科学记数法表示

20.3 = 10100.010011001…1001 (二进制)=1.01000100110011E10…..(十进制科学计数)=1.01000100110011E100…..(二进制科学计数)

这里使用移位存储,对于float来说,指数位加上127,double位加上1023(这里指的是存储,在比较的时候要分别减去127和1023)

同时要注意一点,以float为例,最高位表示的是整个数的符号位,指数位一共8位,最高位表示的是指数位的正负,因为有可能是E-100这样的情况,所以虽然有8位,最高位只是符号位,剩下7位才是表示真正的数值,这也是使用移位存储的原因。

对于一个数字,只要不超过和float的范围,同时小数部分不是无限小数,就可以和对应的double类型相等。

3、浮点数舍入规则

以52位尾数的双精度浮点数为例,舍入时需要重点参考第53位。

若第53位为1,而其后的位数都是0,此时就要使第52位为0;若第52位为0则不用再进行其他操作,若第52位为1,则第53位就要向52位进一位。

若第53位为1,但其后的位数不全为0,则第53为就要向第52位进一位。

若不是以上两种情况,也即53位为0,那么就直接舍弃不进位,称为下舍入。

浮点数舍入规则也就证明了为何在上文中提到的浮点数舍入中,相对舍入误差不能大于机器ε的一半。

对于java来说,一般float类型小数点后保留7位,而double类型小数点后保留15位。

这个原因也是因为尾数的数据宽度限制

对于float型来说,因为2^23 = 8388608

同时最左一位默认省略了,故实际能表示2^24 = 16777216个数,最多能表示8位,但绝对精确的只能表示7位。

而对于double型来说,2^52 = 4503599627370496,共16位。加上省略的一位,能表示2^53 = 9007199254740992。故double型最多能表示16位,而绝对精确的只能表示15位。

4、机器ε

机器ε表示1与大于1的最小浮点数之差。不同精度定义的机器ε不同。以双精度为例,

双精度表示1是

1.000......0000(52个0) × 2^0

而比1大的最小的双精度是(其实还能表示更小的范围,后文中会提到,但并不影响这里的机器ε)

1.000......0001 × 2^0

也即

2^-52 ≈ 2.220446049250313e-16。所以它就是双精度浮点数的机器ε。

在舍入中,相对舍入误差不能大于机器ε的一半。

对于双精度浮点数来说,这个值为0.00000005960464477539。

所以在Java中double类型中连续8个0.1相乘,就会出现表示不精确的情况。

参考:
https://baijiahao.baidu.com/s?id=1618173300159774003&wfr=spider&for=pc
https://www.cnblogs.com/Vicebery/p/9997636.html
https://blog.csdn.net/Return_head/article/details/88623060
https://blog.csdn.net/u011277123/article/details/95774544
https://blog.csdn.net/endlessseaofcrow/article/details/81269079

标签:为什么,0000,Double,浮点数,float,失真,double,127,小数
From: https://www.cnblogs.com/leepandar/p/16712011.html

相关文章

  • 为什么阿里规定需要在事务注解@Transactional中指定rollbackFor?
    阿里巴巴Java规范:方法【edit】需要在Transactional注解指定rollbackFor或者在方法中显示的rollback。异常的分类Throwable:有两个重要的子类:Exception(异常)和Error(错......
  • 大数据面试每日一题-Kafka为什么这么快?
    Kafka为什么这么快?1.kafka是基于partition 分区技术实现的,分布式提高Kafka的并发量2.KafKa的稀疏索引机制,kafka每4kb插入一个索引,索引在查询时候,可以比较高的效率查到到......
  • Kafka为什么性能这么快?4大核心原因详解
    Kafka的性能快这是大厂Java面试经常问的一个话题,下面我就重点讲解Kafka为什么性能这么快的4大核心原因@mikechen1、页缓存技术Kafka是基于操作系统的页缓存(pagecach......
  • self-attention为什么要除以根号d_k
    参考文章:https://blog.csdn.net/tailonh/article/details/120544719正如上文所说,原因之一在于:1、首先要除以一个数,防止输入softmax的值过大,导致偏导数趋近于0;2、选......
  • [LeetCode] 954. Array of Doubled Pairs
    Givenanintegerarrayofevenlength arr,return true ifitispossibletoreorder arr suchthat arr[2*i+1]=2*arr[2*i] forevery 0<=i<le......
  • 为什么文件系统要格式化
    磁盘为什么要格式化? 我们知道,硬盘和软盘都必须格式化后才能使用,这是因为各种操作系统都必须按照一定的方式来管理磁盘,而只有格式化才能使磁盘的结构能被操作系统认识。......
  • DNS解析为什么不生效?DNS解析不生效原因分析
    网站页面为什么打不开?刚修改过域名解析,为什么不生效?如何查看解析是否生效?很多企业在网站的实际运营中,经常会遇到以上DNS解析问题,给网站的运营管理人员造成诸多困扰,接下来中......
  • 问:为什么硬件测试如此重要???
     硬件测试是电子产品开发过程很重要一环,产品在设计阶段很多潜在的问题只看表面是看不出来的,各模块电路必须有针对性的测试才能将问题扼杀在摇篮里。因此,硬件测试工作显得......
  • 为什么swarm节点中运行容器的镜像,无法查看到的tag信息?
    最近今天,在研究dockerswarm中服务的部署,发现一个非常奇怪的现象······ 通过dockerservicecreate命令创建service,比如: dockerservicecreate\--with-......
  • 提示的艺术:为什么“提示工程”是未来的技能
    提示的艺术:为什么“提示工程”是未来的技能虽然在AI机器人中输入文字似乎不是一项技能,但摄影的历史表明它确实如此——而且它将在未来几十年内出现。Animagecreated......