Double为什么会失真?

前言

先来看一个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、小数

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


图片.png

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

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

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

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


图片.png

通过观察以上的图片不难发现,作为一个小数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在内存中的存储为:


图片.png

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可用如下形式表示:


图片.png

从这个式子中我们也可看出二进制表示出的小数是分段的,这也是为什么在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相乘,就会出现表示不精确的情况。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容