浮点数精度问题原因以及各种现象的解释

在知乎上看到有人提出类似的问题,我总结表述一下,做一下笔记:

一步步解释:

存储问题

首先要了解,从根本上讲,计算机只识别二进制数,无论我们使用何种编程语言,在何种编译环境下工作,都需要将程序编译成二进制机器码才能让计算机执行
那么这样会导致什么问题呢?会导致小数的十进制转为二进制出现误差,这就是所谓的精度缺失。

为什么会出现误差?这就涉及到了计算机原理的问题:

我们知道数字在计算机中是以二进制存储的。对于整数来说,只要不超过整数的表示范围,一定都可以表示成二进制的形式,比如8是1000,88是1011000,可是小数小数部分就没有那么幸运了,根据二进制小数转换成十进制的规则(把该小数不断乘2,再取所得的整数部份,直至没有小数或足够长度为止)(二进制整数换成十进制规则 除二取余倒记发)。

由于二进制中所有的小数存储的位数是有限,因此我们得知“任何十进制整数都可以精确转换成一个二进制整数,但任何十进制小数却不一定能精确转换成一个二进制小数,只要转换过程中乘积的小数部分满足所需精度即可”。比如对于0.1来说就不能精确转换为一个二进制小数,在16位小数的限制条件下,离它最近的二进制小数是0.0001100110011,也就是十进制的0.0999755859375。所以虽然你写程序的时候写的是0.1开始这个数存储到b这个float变量里的时候就变成了0.0001100110011,也就是0.0999755859375,因此你输出它的时候就会出现精度误差了,这种误差是不可避免的。
这里我有个疑问,为什么我们不把小数也当做整数存储呢?我怀疑是因为存储方式的问题,然后我找到了原码、反码、补码的文章,然后我又想到为什么要用补码?然后找到了加法器,然后就想为什么只有加法器?到这感觉有点不清楚了,我要好好找一下,有情况再回来更新。

再来,根据IEEE754(二进制浮点数算术标准) 来说:

V = (-1)^s × 2^E × M
  (1)(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。

(2)2^E表示指数位。

(3)M表示有效数字,大于等于1,小于2。

IEEE 754规定,有四种精度的浮点数:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。

32位浮点数存储格式

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

64位浮点数存储格式

其实看看上图你就应该明白,还有一种精度丢失就是长度丢失(不同类型存储结构不同),但那种比较简单,就不做讨论。

继续来讲,上图到底是什么意思呢。咱们先慢慢来讲:

正规化(规约形式)

如果浮点数中指数部分的编码值在[图片上传失败...(image-c806de-1554705679571)]之间,且在科学表示法的表示方式下,分数 (fraction) 部分最高有效位(即整数字)是1,那么这个浮点数将被称为规约形式的浮点数。
如上存储格式中所示:

  1. 首先对于第一段sign,就是符号位,0代表正,1代表负。。

  2. 第二段exponent指数位,实际也是有正负的,但是没有单独的符号位,在计算机的世界里,进位都是二进制的,指数表示的也是2的N次幂,8位指数表达的范围是0到255,而对应的实际的指数是-127到128。也就是说实际的指数等于指数位表示的数值减127。这里特殊说明,-127和+128这两个指数数值在IEEE当中是保留的用作多种用途的,
    关于指数位与实际的指数之差叫做移码,移码的值

精度 M(阶/指数数位) 移码 二进制表示
单精度 8 127 0111 1111
双精度 11 1023 011 1111 1111
长双精度 15 16383 011 1111 1111 1111
  1. 第三段fraction尾数位(也叫有效数字),只代表了二进制的小数点后的部分,小数点前的那位被省略了,当指数位全部为0时省略的是0否则省略的是1。

举个栗子(以32位浮点数为例,这样误差比较大):

比如有个浮点数17.35需要保存。
17.35转为二进制:17转为10001(这就不用说了吧)
0.35呢?(乘2取整法)
0.35 * 2 = 0.7 记为 0
0.70 * 2 = 1.4 记为 1
0.40 * 2 = 0.8 记为 0
0.80 * 2 = 1.6 记为 1
.......这么算有结束吗?当然没有...所以就按照能保存的极限位数保存相对精度。  

这样我们17.35就变成了10001.0101100110011001100共预留24位数  
然后把这串数字右移至小数点前只剩1位:  
1.00010101100110011001100---》 右移了4位  
这样得到了指数位和尾数位:  
指数:实际为4,必须加上127(转出的时候,减去127),所以为131。也就是10000011。  
尾数:00010101100100100100100 (1就不用保存了)。  

到此,十进制浮点数就变成了二进制浮点数存储了。

非正规化:0的表示(非规约形式)(-127)

如果浮点数的指数部分的编码值是0,分数部分非零,那么这个浮点数将被称为非规约形式的浮点数。分数部分全为0,就是表示浮点数0

比如说我们要存储0.35这个浮点数,按上述正规化的形式就无法存储,因为不知道指数位写多少,于是就有了约定,约定指数位为0就表明非正规化。
所以,当见到指数部分为0是,尾数部分就不再是1.bbbbb...而是0.bbbbb...了。

无穷大与NAN

上面说了,指数位预留了两个值(-127和128),-127是做非规约形式的,那么128呢?

当指数位达到当前浮点数最大指数值时:

  • 尾数位全为0就表示无穷大。
  • 尾数位不全为0就表示NaN。

舍入规则

还是以32位单精度浮点数为例。
32位单精度浮点数存储23位尾数位,那么就以第24位位判断依据

  • 如果 24位为1 24位之后都是0 :如果23位为0,则舍去不管;如果23位为1,则24位向23位进1,使23位还是0。
  • 如果 24位为1 24位之后不全0 :24位向23位进1。
  • 如果 24位为0 舍

** 因为位数、算法、规则之类的约定,得知计算机浮点数并不是严格存储这就是为什么浮点数运算出现问题的原因 **


有事情要做,先写到这。
待说明问题:

  1. 关于渐进式下溢出的理论与由来。
  2. 关于以下代码出现情况的说明:
System.out.println(0.1 * 1);
System.out.println(0.1 * 2);
System.out.println(0.1 * 3);
System.out.println(0.1 * 4);
System.out.println(0.1 * 5);
System.out.println(0.1 * 6);
System.out.println(0.1 * 7);
System.out.println(0.1 * 8);
System.out.println(0.1 * 9);
System.out.println("=====================");
System.out.println(0.0999999999999999999999999999999999999999999999999);
double f1 = 9.7;
double f2 = 0.7;
double f3 = 9;
System.out.println(f1-f2-f3);
System.out.println(f1-f3-f2);

输出结果:
0.1
0.2
0.30000000000000004
0.4
0.5
0.6000000000000001
0.7000000000000001
0.8
0.9
=====================
0.1
-6.661338147750939E-16

问题:

  1. 为什么有的输出正确,有的不行
  2. 为什么0.0999--的输出为0.1
  3. 为什么f1-f2-f3得到0.0,而f1-f3-f2却得到那样的结果

这些问题我现在也并没有思考清楚,等待后续思考。
当然,有问题可以在评论中讨论指出。

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

推荐阅读更多精彩内容