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

今天讲一下为什么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. WinForm 公共控件

    一.窗体属性: 1.AcceptButton - 窗体的“接受”按钮.如果设置该属性,每次用户按“Enter”键都相当于“单击”了该按钮. 需要设置哪个键,就在后面选择. 2.CancelButton ...

  2. css3实现逐渐变大的圆填充div背景的效果

    手机端现在的一些应用会运用上这样一个效果,就是duang的一下出现一个圆变大直到填充整个div,动感十足. 想到css3的scale属性,就自己来实现一下. <div id="bcd& ...

  3. Flapper Bird的学习笔记(三)

    因为我有一个超屌的梦想,所以就绝不会做一个孬种的追梦人! 完成音效的添加 单例模式 游戏的状态切换 1. 单例模式 首先呢,说一下单例模式.何为单例?单例模式是一种常用的软件设计模式.在它的核心结构中 ...

  4. iOS_线程和进程的区别与联系

    首先是线程和进程的联系: 线程和进程都是由操作系统所负责的程序运行的基本单元,系统利用该基本单元实现对应用的并发性. 接下来就是线程和进程的区别: 线程和进程最大的区别就是它们是操作系统的两种资源管理 ...

  5. 简单研究下Retrofit

    2015-09-24 15:36:26 第一部分: 1. 什么是Retrofit? (点击图片有惊喜) 以上是来自官网的解释,言简意赅,咳咳,我就不翻译了~ 2. 如何使用Retrofit? 2.1 ...

  6. [翻译]PYTHON中如何使用*ARGS和**KWARGS

    [翻译]Python中如何使用*args和**kwargs 函数定义 函数调用 不知道有没有人翻译了,看到了,很短,顺手一翻 原文地址 入口 或者可以叫做,在Python中如何使用可变长参数列表 函数 ...

  7. Bootstrap学习笔记(二)

    这一节笔记主要记录排版内容笔记,其内容包括标题.文本(包括段落.粗斜体.对齐).列表.表格等. 一.标题 在bootstrap中H1-H6与非框架版的区别不大,需要注意的是<small>标 ...

  8. Qt中常见错误整理(不定期更新)

    (1)error: LNK1104: cannot open file 'libboost_thread-vc120-mt-gd-1_57.lib 编译boost库程序时出现问题 解决方法如下: 1. ...

  9. hdu 1231, dp ,maximum consecutive sum of integers, find the boundaries, possibly all negative, C++ 分类: hdoj 2015-07-12 03:24 87人阅读 评论(0) 收藏

    the algorithm of three version below is essentially the same, namely, Kadane's algorithm, which is o ...

  10. RN的像素及布局

    转自:http://www.cocoachina.com/ios/20150420/11608.html 宽度单位和像素密度 react的宽度不支持百分比,设置宽度时不需要带单位 {width: 10 ...