原标题:为什么0.1 + 0.2不等于0.3?

0.1 + 0.2不等于0.3这是一个普遍的问题,例如在JS控制台输入将得到0.30000000000000004

在python的控制台也是输出这个数:

在C里面运行以下代码,指定输出小数位为57位:

printf("%.57f", 0.1 + 0.2);

将得到:

0.300000000000000044408920985006261616945266723632812500000

那我们的问题来了,为什么计算机计算的0.1加0.2会不等于0.3?

首先我们来看一下JS能够表示最大数是多少,如下所示,打印Number.MAX_SAFE_INTEGER和Number.MAX_VALUE:

JS能表示的最大整数为9e16,能表示的最大正数为1.79e308,这两个数是怎么得来的呢?先来看一下整数在计算机的存储方式。

我们知道计算机是使用二进制存储数据的,整数也是同样的道理,整数可以分成短整型、基本型、长整型,占用的存储空间分别为16位、32位、64位,如果操作系统是32位的,那么使用长整型将会慢于短整型,因为一个数它需要分两次取,而在64位的操作系统,一次就可以取到8个字节或64位的数据,所以使用长整型不会有性能问题。另外,32位的操作系统内存只能识别到2 ^ 32 = 4G,而现在的电脑内存动不动就是8G、16G,所以现在的电脑基本都是64位的,不比前几年。

32位有符号整型的存储方式如下图所示:

第一位0表示正数,1表示负数,剩下的31位表示数值,所以32位有符号整数最大值为:

2 ^ 31 – 1 = 2147483647

即21亿多,如果要表示全球人口,那么32位整型是不够的。同理,64位有符号整型能表示的最大值为:

2 ^ 63 – 1 = 9223372036854775807

这是一个19位数,mysql数据库的id字段就经常用长整型表示,那为什么JS能表示的最大整数只有16位,而不是19位呢?这个要先说一下浮点型在计算机的存储方式。

现在浮点型的存储实现基本按IEEE754标准,符点数分为单精度和双精度,单精度为32位,双精度为64位。

在十进制里面,一个小数如0.75可以表示成7.5 * 10 ^ -1,同样地在二进制里面,0.75可以表示成:

0.75 = 1.1 * 2 ^ -1

即0.75 = (1 + 1 * 2 ^ -1) * 2 ^ -1,其中幂次方-1用阶码表示,而1.1由于二进制整数部分都是1,所以去掉1留下0.1作为尾数部分(因为都是1点多的形式,所以这个1就没必要存了)。因此0.75在单精度浮点数是这样表示的:

注意阶码要加上一个基数,这个基数为2 ^ (n – 1) – 1,n为阶码的位数,32位的阶码为8位,所以这个基数为127,8位阶码能表示的最小整数为0,最大整数为255,所以能表示的指数范围为:(0 – 127) ~ (255 – 127)即-127~128,上面要表示指数为-1,需要加上基数127,就变成126,如上图所示。

而尾数为0.1,所以尾数的最高位为1,后面的值填充0.

反过来,如果知道一个二进制的存储方式,同样地可以转换成10进制,如上图的计算结果应为:

(1 + 1 * 2 ^ – 1) * 2 ^ (126 – 127) = 1.5 * 2 ^ -1 = 0.75

那么0.1又该如何表示成一个二进制呢?

由于0.75 = 1 * 2 ^ -1 + 1 * 2 ^ -2,刚好可以被二进制精确表示,那0.1呢?没办法了,0.1无法被表示成这种形式,只能是用另外一个数尽可能地接近0.1(同理1/3无法在10进制精确表示,但是可以在3进制精确表示,只是我们习惯了10进制)。

我们可以用一小段来研究一下0.1被存储成什么了,如下代码所示:

voidprintBits(size_tconstsize,voidconst*constptr)

{

unsignedchar*b=(unsignedchar*)ptr;

unsignedcharbyte;

inti,j;

for(i=size-1;i>=0;i--)

{

for(j=7;j>=0;j--)

{

byte=(b[i]>>j)&1;

printf("%u",byte);

}

}

puts("");

}

doublea=0.1;

doubleb=0.1;

printBits(sizeof(a),&a);

printBits(sizeof(b),&b);

因为C可以读取到原始的内存内容,所以可以打印每位的数据是什么。如上代码打印的结果如下:

双精度浮点数用11位表示阶码,52位表示尾数,如下图所示:

所以双精度的阶码基数为2 ^ 10 – 1 = 1023,0.1的阶码为01111111011,等于二进制1019,所以它的指数为-4:

尾数约为0.6:

由于这个精度不够,我们要找一个高精度的计算器,如笔者找的:

有了这个尾数之后,再让它乘以指数,得到结果为:

也就是说0.1的实际存储要比0.1大,大概大了5.5e-17.

注意到,0.2和0.1的区别在于0.2比0.1的阶码大了1,其它的完全一样。所以,0.2也是偏大了:

两个数相加的结果为:

但是注意到0.1 + 0.2并不是上面的结果,要比上面的大:

这又是为什么呢?因为浮点数相加,需要先比较阶码是否一致,如果一致则尾数直接相加,如果不一致,需要先对阶,小阶往大阶看齐,即把小阶的指数调成和大阶的一样大,然后把它的尾数向右移相应的位数。如上面的0.1是小阶,需要对它进行处理,如下:

需要把0.1的小数点向右移一位变成:

向右移一位导致尾数需要进行截断,由于最后一位刚好是0,所以这里直接舍弃,如果是1,那么尾数加1,类似于十进制的四舍五入,避免误差累积。现在0.1和0.2的阶码一样了,尾数可以进行相加减了,如下把它们俩的尾数相加:

可以看到,发生了进位,变成了53位,已经超过了尾数52位的范围,所以需要把阶码进一位,即指数加1,两数和的尾数右移一位,即除以2,由于尾数的最后一位是1,进行“四舍五入”,即舍弃最后一位后再加上1,最后尾数变成了如下图所示:

而指数加1,变成了-2,所以最后的计算结果为:

这个就和控制台的输出一致了,并且和C的输出完全一致。到此,我们就回答了为什么0.1加0.2不等于0.3了。上面还提出了两个问题,其中一个是:为什么JS的最大正数是1.79e308呢?这个数其实就是双精度浮点数所能表示最大正值,如下使用python的输出:

那为什么JS的最大正整数不是正常的64位的长整型所能表示的19位呢?因为JS的正整数是用的尾数的长度表示的,由于尾数是52位,加上整数的一位,它所能表示的最大的整数为:

为什么JS要用这种方式呢?因为JS的整型和浮点型在计算过程中可以随时自动切换,应该是考虑到了这个原因,所以才拿浮点型的大小限制来做整型大小的限制。

由于后端的数据库的ID字段可能会大于这个值,如果传来了一个很大的数,在调JSON.parse的时候将会丢失精度,ID就不对了,所以如果出现这种情况,应该让后端把ID当成字符串的方式传给你。

另外需要注意的是,双精度符点数的可靠位数为15位,也就是说从第16位开始可能是不对的,如0.1 + 0.2 = 0.30000000000000004,最后面的04这两位是不可靠的。

但是会有一种情况精度要求很高,15位精度会不够用,例如计算天体运算。那怎么办呢?有一种绝对精准的方式就是用分数表示,例如0.1 + 0.2 = 3/10,计算的过程和最后的结果都用分数表示,分数的结果,你需要精确到多少位都可以取到。这个在matlab/maple等科学计算软件都有实现。

最后怎么判断两个小数是否相等呢?用等号肯定是不行的了,判断两个小数是否相等要用它们的差值和一个很小的小数进行比较,如果小于这个小数,则认为两者相等,ES6新增了一个Math.EPSILON属性,如比较0.1 + 0.2是否等0.3应该这么操作:

作者:会编程的银猪

原文:http://www.renfed.com/2017/05/13/float-number/

---- 广告 ----

掘金是一个高质量的技术社区,从ECMA6到Vue.js,性能优化到开源类库,让你不错过前端开发的每一个技术干货。点击链接即可访问掘金官网,或到各大应用市场搜索「掘金」下载APP,技术干货尽在掌握中返回搜狐,查看更多

责任编辑:

python0.1+0.2不等于0.3_为什么0.1 + 0.2不等于0.3?相关推荐

  1. android 相机拍照返回,Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题...

    版权声明:本文为博主原创文章,未经博主允许不得转载. 正常情况下调用系统相机拍照: 如果拍照后点击的是"确定"图标,返回的resultCode = -1(Activity.RESU ...

  2. 给出3个参数,N,M,K,怪兽有N滴血,等着英雄来砍自己,英雄每一次打击,都会让怪兽流失[0~M]的血量,到底流失多少?每一次在[0~M]上等概率的获取一个值,求K次打击之后,英雄把怪兽砍死的概率

    问题描述:给出3个参数,N,M,K,怪兽有N滴血,等着英雄来砍自己,英雄每一次打击,都会让怪兽流失[0~M]的血量,到底流失多少?每一次在[0~M]上等概率的获取一个值,求K次打击之后,英雄把怪兽砍死 ...

  3. 每天一道LeetCode-----给定一个矩阵,如果某个元素是0,就将所在行所在列上所有元素否置0

    Set Matrix Zeroes 原题链接Set Matrix Zeroes 给定一个m × n矩阵,如果矩阵中某个元素是0,那么就将它所在的行,所在的列上的所有元素都变成0.要求空间复杂度在O(1 ...

  4. 【C++代码】约瑟夫环问题:0,1,……,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

    问题描述:0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 这是力扣上的一道题.我的思路: ①首先想到的是用循环链表,每次向后遍历 ...

  5. 【MySQL 8.0 OCP 1Z0-908认证考试】题库精讲--第三讲mysql8.0安装配置升级(下)

    此专题题目较多,因此分为上中下三部分来讲,此为下篇. 完整版题库请到我的资源中下载,此为传送门.https://download.csdn.net/download/kanon_lgt/8501041 ...

  6. DXBBS V8.0 BBS论坛系统 简体中文正式版 [ 开发语言:ASP.NET 2.0 (C#) ]

    DXBBS V8.0 BBS论坛系统 简体中文正式版 [ 开发语言:ASP.NET 2.0 (C#) ] 程序版本:DXBBS V8.0 ACCESS/MSSQL版 开发语言:ASP.NET 2.0 ...

  7. vs 2022 下载了.net7.0预览版 还是提示当前.net sdk不支持将.net7.0设置为目标

    文章目录 官网下载.net 7.0预览版 运行项目提示错误 解决方法 官网下载.net 7.0预览版 链接:https://dotnet.microsoft.com/zh-cn/download/do ...

  8. usb3.0 ssd 测试软件,SSD打造的移动硬盘,顺便测下USB2.0、USB3.0、SATA2.0、SATA3.0传输速度...

    本帖最后由 神经病的春天 于 2014-9-18 15:36 编辑 近来升级了一下主机,这样一来,终于能够拥有USB3.0和SATA3.0接口了,不过本人目前还木有SSD,所以尚无法完全发挥出这些接口 ...

  9. 标题|服务器标题|服务器名称|服务器IP|服务器端口|是否自动展开(0不展开,1自动展开)|微端IP|微端端口(0表示不使用微端)|安全盾防火墙端口(0表示不使用防火墙)|防火墙类型,0=安全盾防火墙

    [Server] ; 标题|服务器标题|服务器名称|服务器IP|服务器端口|是否自动展开(0不展开,1自动展开)|微端IP|微端端口(0表示不使用微端)|安全盾防火墙端口(0表示不使用防火墙)|防火墙 ...

  10. 算法训练 - 反置数 一个整数的“反置数”指的是把该整数的每一位数字的顺序颠倒过来所得到的另一个整数。如果一个整数的末尾是以0结尾,那么在它的反置数当中,这些0就被省略掉了。比如说,124

    问题描述 一个整数的"反置数"指的是把该整数的每一位数字的顺序颠倒过来所得到的另一个整数.如果一个整数的末尾是以0结尾,那么在它的反置数当中,这些0就被省略掉了.比如说,1245的 ...

最新文章

  1. Matlab编程与数据类型 -- 文本M文件
  2. ORB-SLAM2地图存储加载系统
  3. 转:Oracle 应用服务器 MapViewer 10.1.2截图
  4. MATLAB的xlsread无法读入数据问题
  5. linux磁盘iops限制,linux – 我需要多少IOPS?我的工作量瓶颈是存储
  6. java for新循环_java新特性-新式for循环(For_Each)
  7. springMVC ---- 异步调用
  8. daily news新闻阅读客户端应用源码(兼容iPhone和iPad)
  9. 详解解决CAS机制中ABA问题的AtomicStampedReference
  10. 【测试】15.质量管理体系
  11. 小米 MIUI 主题制作
  12. CentOs 虚拟LINUX系统安装与虚拟环境配置
  13. android不同sdk版本控制,闲谈Android SDK开发
  14. 海思Hi3519A开发(5.梳理海思文档与运行sample代码)
  15. 【转】Linux编译程序报错 undefined reference to error 的解决方法
  16. PE+windows系统+苹果网站整理
  17. linux下回收站无法清空 解决
  18. Android 沉浸式(透明)状态栏细研-超级细还附 Demo
  19. 竞赛——【蓝桥杯】2022年11月第十四届蓝桥杯模拟赛第一期Python
  20. hdr_beg(host) 主机名开始

热门文章

  1. 蓝牙BLE4.0的LL层数据和L2CAP层数据的区分与理解
  2. 【AT2434】JOI 公園 (JOI Park) 最短路+贪心
  3. 什么是同源策略及限制
  4. mysql查看修改字符集
  5. epoll 的accept , read, write
  6. server正式的环境性能测试nginx-php 指着寻求突破的表现
  7. 关于开发简易搜索引擎的一些总结和思考
  8. 【BZOJ1572】【usaco 2009 open】工作安排job
  9. 20.二叉树怎么存储
  10. 世界机器人大会特种机器人报到高难度的高危险的活让它们来!