首页 > 编程语言 >为什么 JavaScript 中 0.1 0.2 不等于 0.3 ?

为什么 JavaScript 中 0.1 0.2 不等于 0.3 ?

时间:2023-04-04 13:42:04浏览次数:51  
标签:舍入 0.1 0.3 0.2 尾数 位为


vivo互联网技术 微信公众号 
作者:刘洋

在 js 中进行数学的运算时,会出现0.1+0.2=0.300000000000000004的结果,一开始认为是浮点数的二进制存储导致的精度问题,但这似乎不能很好的解释为什么在同样的存储方式下0.3+0.4=0.7可以得到正确的结果。本文主要通过浮点数的二进制存储及运算,和IEEE754下的舍入规则,解释为何会出现这种情况。

一、浮点数的二进制存储

JavaScript遵循IEEE754标准,在64位中存储一个数据的有效数字形式。

为什么 JavaScript 中 0.1 0.2 不等于 0.3 ?_浮点数

其中,第0位为符号位,0表示正数1表示负数;第1到11位存储指数部分;第12到63位存小数部分(尾数部分)(即有效数字)。由于二进制的有效数字总是表示为 1.xxx…的形式,尾数部分在规约形式下的第一位默认为1,故存储时第一位省略不写,尾数部分f存储有效数字小数点后的xxx...,最长52位。因此,JavaScript提供的有效数字最长为53个二进制位(尾数部分52位+被省略的1位)。
以0.1、0.2、0.3、0.4和0.7的二进制形式为例:

0.1->0.0001100110011...(0011无限循环)->0-01111111011-(1 .)1001100110011001100110011001100110011001100110011010(入)
0.2->0.001100110011...(0011无限循环)->0-01111111100-(1 .)1001100110011001100110011001100110011001100110011010(入)
0.3->0.01001100110011...(0011无限循环)->0-01111111101-(1 .)0011001100110011001100110011001100110011001100110011(舍)
0.4->0.01100110011...(0011无限循环)->0-01111111101-(1 .)1001100110011001100110011001100110011001100110011010(入)
0.7->0.101100110011...(0011无限循环)->0-01111111110-(1 .)0110011001100110011001100110011001100110011001100110(舍)

对于52位之后进行舍入运算,此时可看作0舍1入(具体舍入规则在第三部分详细说明),有精度损失。

二、对阶运算

由于指数位数不同,运算时需要进行对阶运算。对阶过程略,0.1+0.2与0.3+0.4的尾数求和结果分别如下:

0.1+0.2->10.0110011001100110011001100110011001100110011001100111
0.3+0.4->10.1100110011001100110011001100110011001100110011001101

求和结果需规格化(有效数字表示),右规导致低位丢失,此时需对丢失的低位进行舍入操作:

0.1+0.2->1.00110011001100110011001100110011001100110011001100111->1.0011001100110011001100110011001100110011001100110100(入)
0.3+0.4->1.01100110011001100110011001100110011001100110011001101->1.0110011001100110011001100110011001100110011001100110(舍)

即:
00111->0100
01101->0110

此处同样有精度损失。在这里我们可以发现,0.3+0.4对阶阶运算且规格化后的运算结果与0.7在二进制中的存储尾数相同(可对照尾数后几位),而0.1+0.2的运算结果与0.3的存储尾数不同,且0.1+0.2转化为十进制时结果为0.300000000000000004。
此时,虽然0.1+0.2与0.3+0.4进行舍入操作的近似位都为1,但一入一舍导致计算结果与“标准答案”的异同。

三、IEEE754标准下的舍入规则

维基百科对最近偶数舍入原则的解释如下:舍入到最接近,在一样接近的情况下偶数优先(Ties To Even,这是默认的舍入方式),即会将结果舍入为最接近(精度损失最小)且可以表示的值,但是当存在两个数一样接近的时候,则取其中的偶数(在二进制中是以0结尾的)。

首先要注意的是,保留小数不是只看后面一位或者两位,而是看保留位后面的所有位。

为什么 JavaScript 中 0.1 0.2 不等于 0.3 ?_IEEE754_02

 

如图,可以看到近似需要看三位,保留位(近似后的最低位)、近似位(保留位的后一位)、粘滞位(sticky bit 近似位后的所有位进行或运算后看作一位)。
当粘滞位为1时,舍入规则可以看作0舍1入,近似位为0舍,近似位为1入(即第一部分小数二进制存储为52位尾数时所进行的舍入操作)。
当粘滞位为0时,若近似位为0则舍去。
当粘滞位为0时,若近似位为1,无论舍入精度损失都相同,故需取舍入两种结果中的偶数:保留位为1时入,保留位为0时舍(即第二部分对阶运算规格化时的舍入操作)。

四、总结思考

由于IEEE754标准,这样的“bug”不止在JavaScript中会出现,在所有采用该标准的语言中都会存在,实际编程中可以通过设置精度保留位数等方式解决。

vivo 互联网技术 微信公众号

为什么 JavaScript 中 0.1 0.2 不等于 0.3 ?_二进制存储_03


标签:舍入,0.1,0.3,0.2,尾数,位为
From: https://blog.51cto.com/u_14291117/6168590

相关文章

  • Tomcat 9.0.26 高并发场景下DeadLock问题排查与修复
    vivo互联网技术微信公众号 作者:黄卫兵、陈锦霞一、Tomcat容器9.0.26版本Deadlock问题1.1问题现象1.1.1 发生Deadlock的背景某接口/get.do压测,3分钟后,成功事务数TPS由1W骤降至0。1.1.2 Tomcat服务器出现大量的CLOSE_WAIT被压测服务器,出现TCPCLOSE_WAIT状态个数在200~......
  • MySql8.0.30忽略大小写配置
    说明:此文档只是针对已经初始化了的数据库,如果是新安装的数据库直接在/etc/my.cnf文件中新增一行配置:lower_case_table_names=1即可。因为默认配置是0. 步骤1:备份mysql所有数据,并删掉data目录里的所有文件。可能是因为数据库里的表默认设置了lower_case_table_names=0,如果不删......
  • 127.0.0.1、0.0.0.0、localhost、本机IP区别
    1、简洁说明localhost(IP都没有,不到网络层IP也不到链路层MAC)   localhost不会解析成ip,也不会占用网卡、网络资源(到TCP/UDP,但不经过IP)    127.0.0.1(有IP,只到网络层IP走网卡,不到链路层MAC)   127.0.0.1回环地址,不经过[链路层,物理层](网络接口层),在IP层......
  • opencv-python 4.10.3. 直方图3:2D直方图
    介绍我们学习计算并绘制了一维直方图。它之所以被称为一维,是因为我们只考虑一个特征,即像素的灰度强度值。但在二维直方图中,需要考虑两个特征。通常,它用于查找颜色直方图,其中两个特征是每个像素的色调值和饱和度值。OpenCV中的2D直方图它很简单,使用相同的函数cv.calcHist()计......
  • Day 20 20.2 数据库之MySQL基础
    基本概念前面的学习中我们提到,mysql是关系型数据库,所以我们要操作mysql就需要使用SQL(结构化查询语言)。SQL规范1.在数据库管理系统中,SQL语句关键字不区分大小写(但建议用大写),参数区分大小写。建议命令大写,数据库名、数据表名、字段名统一小写,如数据库名、数据表名、字......
  • Day 20 20.3 数据库之Python操作MySQL
    Python操作MySQLimportpymysql#打开数据库连接db=pymysql.connect(host='localhost',user='root',passwd='...',port=3306,datebase='...')print('连接成功!')#使用cursor()方法创建一个游标对象cursorcursor=db.cursor()#......
  • git 报Failed to connect to 127.0.0.1 port 1081: Connection refused
    我遇到这个问题是我用了全局代理。导致了端口被占用了。提示的错误是 Failedtoconnectto127.0.0.1port1081:Connectionrefused解决办法:windows和mac都适用第一步查询是否使用了代理: 输入:gitconfig--globalhttp.proxy  你就会看到被占用的端口和报错的一......
  • macos:用homebrew安装node/安装vue-cli( node v18.15.0/ Homebrew 4.0.10-119)
    一,用brew安装nodeliuhongdi@liuhongdideMacBook-ProHomebrew%brewinstallnode@18==>Downloadinghttps://formulae.brew.sh/api/formula.jws.json#=#=#==>Fetchingnode......
  • homebrew:常用命令(Homebrew 4.0.10-119)
    一,brew常用的命令1,查看brew下载文件的缓存目录liuhongdi@liuhongdideMacBook-ProHomebrew%brew--cache/Users/liuhongdi/Library/Caches/Homebrew2,查看brew的安装目录liuhongdi@liuhongdideMacBook-ProHomebrew%brew--prefix/usr/local也可以:liuhongd......
  • homebrew: 配置国内源(Homebrew 4.0.10-119)
    一,查看brew的安装目录:liuhongdi@liuhongdideMacBook-Propoem%cd"$(brew--repo)"liuhongdi@liuhongdideMacBook-ProHomebrew%pwd/usr/local/Homebrew说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest     对应的源......