五一宅家看书,所以接着更新一篇文章。

今天讲一下为什么03-0.2不等于0.1这个问题。

有点标题党的味道,在JavaScript中,当你试着对小数进行加减运算时,有时候会发现某个结果并非我们所想的那样,就比如标题中所说的为什么我用0.3去减0.2却得不到0.1?

当我碰到这个问题的时候我一下子也不知道问题到底出在哪,但他实实在在的就给出了一个0.09999999999999998的结论。

其实这个问题的本质原因就是在于计算机对浮点数的处理上,对于计算机的运算方法和数据的表示处理等内容是很大的一块内容,而且这块内容只要是计算机专业的同学在大学期间都已经接触到了而且很可能在你的期末考试试卷上就有这样的一道相关的题目是检测你这个知识点的。所以今天在朋友圈发了一条动态,内容是不好好学习的下场就是你连为什么03-0.2不等于0.1都不知道。

下面从浅显易懂的角度去讲述这个问题。

1.计算机中如何表示数据?

  计算机是个机器,内部所有数据和指令都是以01为基础来实现。也就是计算机的认知水平仅仅局限于二进制,而我们习惯于十进制计数规则,所以这中间就牵扯到一个各种数制之间的转换的问题。数制有二进制、八进制、十进制和十六进制,当然只要你高兴,搞个三进制五进制也不是不可以,但是这几个进制是我们约定俗成和被广泛使用的。

2.各数制间如何进行转换?

  真的不怎么想写这个问题,因为能回答这个问题的答案在你的课本里边早就有很详细且很全面的讲解,如果你浮躁到只想知道结果而自己不懂却连课本都不想翻的地步,那么这篇文章其实也没必要看了。

  但是为了与下面内容更好的连贯性,这里还是简单讲一下怎么从十进制转换到二进制的过程。

  十进制转换为二进制:

  比如11(10)这个十进制数,我们转换成二进制为11(10)=8+2+1=11=23+21+20=1000+10+1=1011(2);如果看不懂这个转换,那你去翻课本吧,这里就不列那些公式什么的了。 

  二进制转换为十进制:

  二进制转换为十进制也就是十进制转换为二进制的一个逆向过程,还是拿11(10)这个数字举例。1011(2)=1000+10+1=23+21+20=8+2+1=11(10)。

3.0.3-0.2不等于0.1和12条问题有什么关系?

  第一条说了因为我们习惯于非二进制,而计算机只能认知二进制,所以我们在编程语言中所写的那些以十进制表示数值的代码,对于底层的计算机实现来说是按照二进制来进行的。所以这其中肯定就会有一个进制转换的问题。

  当我们在高级语言里用十进制书写代码,到最后得到一个正确的依然是十进制的结果的这个过程,计算机在中间进行了两次数制转换,一次是十进制转二进制,转换完后进行数据操作,操作完成后所得到的二进制数据又逆向从二进制转换为十进制,最后呈现给我们一个我们所预期的结果。

  0.3-0.2不等于0.1的原因就出现在这个过程当中。

4.那么0.3-0.2不等于0.1到底是怎么形成的?

  紧接着上边的表述往下讲,以0.3-0.2为例,在JavaScript语言中完成这一个计算过程。

  首先我们打出0.3-0.2的表达式,本想我们很轻易的得出一个0.1的结果,然而,事实出乎意料,结果却给我们返回一个0.09999999999999998。

  数据在计算机中的分类大体为符号数和无符号数、定点数和浮点数,相互交叉便可以分为无符号定点数、浮点数和有符号定点数、浮点数。0.3(10)转换成二进制数据表示为:0.3(10)=2-3+2-4+2-7+2-8+......=0.001100110011001100110011001100110011001100110011001101(2),在JavaScript可以用对象的toSting方法查看,如0.3.toString(2);然后我们拿到这个转换后的二进制数据再按照单精度float或者双精度double的格式规格化成符号位阶数尾数的浮点数的表示形式,这就完成了从十进制到二进制表示的转换过程。

  再然后计算机就将0.3和0.2的二进制浮点数形式的表示进行运算,很显然,这个结果跟我们所想的0.3-0.2就该等于0.1可能会有些差异,因为我们在运算的过程中要对数据进行对阶操作,即两个数的阶数不同的话要调整到统一大小的阶数,调整过程中一般是右移,右移过程中就会出现精度受损的可能,因此计算完后的结果很有可能就不是我们所想要的那个结果了。

5.0.3-0.2就一定不等于0.1吗?

  首先,只要是小数,或者说是浮点运算,就有可能会出现精度受损的情况,但是只是可能,也不是说就一定会出现精度受损。具体要看操作数在进制转换过程中和二进制数值运算过程中有没有受到精度的影响,你比如0.5-0.25=2-1-2-2=2-2=0.25,这种情况下就不会出现精度受损,所以结果和我们预期的是一样的。

6.怎么规避这个问题?

  一切问题都可以解决,前提是我们知道了原因。现在我们知道了为什么0.3-0.2不等于0.1,那就反方向考虑解决问题的方法。那就是避免他们在数制转换过程和二进制数值运算过程中精度受损的影响。比如,我有3毛钱,也就是0.3元,然后买了只雪糕花掉我2毛钱,也就是0.2元,问还剩多少钱?如果是在以前,我们肯定不假思索的0.3-0.2=0.1想当然的就得出了结论。但是现在我们已经认识到这样会有问题,那么我们怎么避免他呢?那就是我为什么非要用元作单位而不用毛或者用分作单位?如果我用毛作单位3-2=1,用分作单位30-20=10就一定不会出现问题。因为整数的运算肯定不会有浮点运算的精度问题。所以这就是解决问题的思路。

完!

祝愉快。

深入理解JavaScript系列:为什么03-0.2不等于0.1的更多相关文章

  1. 深入理解JavaScript系列(33):设计模式之策略模式(转)

    介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...

  2. 深入理解JavaScript系列(44):设计模式之桥接模式

    介绍 桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化. 正文 桥接模式最常用在事件监控上,先看一段代码: addEvent(element, 'click', getBe ...

  3. 深入理解javascript系列(4):立即调用的函数表达式

    本文来自汤姆大叔 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法 ...

  4. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...

  5. [JS]深入理解JavaScript系列(4):立即调用的函数表达式

    转自:汤姆大叔的博客 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.在详细了解这个之前,我们来谈了解一下"自执行"这个叫法 ...

  6. 深入理解JavaScript系列(49):Function模式(上篇)

    介绍 本篇主要是介绍Function方面使用的一些技巧(上篇),利用Function特性可以编写出很多非常有意思的代码,本篇主要包括:回调模式.配置对象.返回函数.分布程序.柯里化(Currying) ...

  7. 深入理解JavaScript系列(47):对象创建模式(上篇)

    介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度. ...

  8. 深入理解JavaScript系列(48):对象创建模式(下篇)

    介绍 本篇主要是介绍创建对象方面的模式的下篇,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式6:函数语法糖 函数语法糖是为一个对象快速添加方法(函数)的扩展,这个主要是利用pro ...

  9. 深入理解JavaScript系列(46):代码复用模式(推荐篇)

    介绍 本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用. 模式1:原型继承 原型继承是让父对象作为子对象的原型,从而达到继承的目的: function object(o) { fun ...

随机推荐

  1. Guava----Function

    1. Function接口,提供两个方法: apply方法: 可以自定义自己想实现的功能 @Nullable T apply(@Nullable F input); 1. 实例: import com ...

  2. iframe自动高度

    <script> //设置iframe自动高度 function setIframe(id){ var fn = function(){ try{ var iframe = typeof ...

  3. 关于如何在cenos7.0上实现mysql数据库远程连接

    设置mysql允许别的客户机控制的权限 mysql -uroot -p #此处为本地linux帐号密码 select user,host from mysql.user; #查看mysql表对应use ...

  4. ACCESS导入CSV文件出现乱码解决办法

    在ACCESS或Excel中导入CSV文件时常常出现乱码,这是因为简体中文版的windows操作系统及其应用软件默认都是ANSI/GBK编码,而导入的文件使用的编码与操作系统默认的编码不相符.出现这种 ...

  5. C++语法-指针 (1)

    <C++程序设计> 谭浩强  清华大学出版社 2016-08-03 1.P167 一般的C++编译系统为每个指针变量分配4个字节的存储单元,用来存放变量的地址. 2.P169 .cpp文件 ...

  6. Visual Studio Code中文文档(一)-快速入门

    Visual Studio Code是一个轻量级但是十分强大的源代码编辑器,重要的是它在Windows, OS X 和Linux操作系统的桌面上均可运行.Visual Studio Code内置了对J ...

  7. python中引用

    python中的可变类型与不可变类型: 在讲python深浅拷贝之前,我们首先应该明白python中元素的类型:主要分为可变类型和不可变类型. 可变类型,就是值可以改变的类型有两个: 列表:list ...

  8. 用c++写一个广告系统

    用到的基础类库 1.sstream <sstream> 库定义了三种类:istringstream.ostringstream和stringstream,分别用来进行流的输入.输出和输入输 ...

  9. JSP自定义标签/自定义标签打包

    有这样一个业务需求: 当我们在编辑某个用户时,需要设置该用户的角色,在转到编辑页面时,就需要自动勾选上该用户已经选择的角色,如下图: 当我们点击编辑时,会查询用户详细信息,以及角色集合传到编辑页面. ...

  10. WCF初探文章列表

    WCF初探-1:认识WCF WCF初探-6:WCF服务配置 WCF初探-2:手动实现WCF程序 WCF初探-7:WCF服务配置工具使用 WCF初探-3:WCF消息交换模式之单向模式 WCF初探-8:W ...