首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
第1章 JavaScript重点概念
1.1 JavaScript的基本数据类型介绍
1.2 Number类型详解
1.2.1 Number类型介绍
1.2.2 Number类型转换
1.2.3 isNaN()函数与Number.isNaN()函数对比
1.2.4 浮点型运算
1.3 String类型详解
1.3.1 String类型的定义与调用
1.3.2 String类型常见算法
1.4 运算符
当前位置:
首页>>
技术小册>>
Javascript重点难点实例精讲(一)
小册名称:Javascript重点难点实例精讲(一)
在JavaScript中,整数和浮点数都属于Number类型,它们都统一采用64位浮点数进行存储。 虽然它们存储数据的方式是一致的,但是在进行数值运算时,却会表现出明显的差异性。整数参与运算时,得到的结果往往会和我们所想的一样,例如下面的代码。 ``` // 加法 1 + 2 = 3 7 + 1 = 8 // 减法 15 ``` 12 = 3 3 ``` 2 = 1 // 乘法 7 * 180 = 1260 97 * 100 = 9700 // 除法 3 / 1 = 3 69 / 10 = 6.9 ``` 而对于浮点型运算,有时却会出现一些意想不到的结果,如下面的代码所示。 ``` // 加法 0.1 + 0.2 = 0.30000000000000004 0.7 + 0.1 = 0.7999999999999999 // 减法 1.5 ``` 1.2 = 0.30000000000000004 0.3 ``` 0.2 = 0.09999999999999998 // 乘法 0.7 * 180 = 125.99999999999999 9.7 * 100 = 969.9999999999999 // 除法 0.3 / 0.1 = 2.9999999999999996 0.69 / 10 = 0.06899999999999999 ``` 得到这样的结果,大家是不是觉得很奇怪呢?0.1 + 0.2为什么不是等于0.3,而是等于0.30000000000000004呢?接下来我们一探究竟。 1. 问题原因 首先我们来看看一个浮点型数在计算机中的表示,它总共长度是64位,其中最高位为符号位,接下来的11位为指数位,最后的52位为小数位,即有效数字的部分。 · 第0位:符号位sign表示数的正负,0表示正数,1表示负数。 · 第1位到第11位:存储指数部分,用e表示。 · 第12位到第63位:存储小数部分(即有效数字),用f表示 因为浮点型数使用64位存储时,最多只能存储52位的小数位,对于一些存在无限循环的小数位浮点数,会截取前52位,从而丢失精度,所以会出现上面实例中的结果。 2. 计算过程 接下来以0.1 + 0.2 = 0.30000000000000004的运算为例,看看为什么会得到这个计算结果。 首先将各个浮点数的小数位按照“乘2取整,顺序排列”的方法转换成二进制表示。 具体做法是用2乘以十进制小数,得到积,将积的整数部分取出;然后再用2乘以余下的小数部分,又得到一个积;再将积的整数部分取出,如此推进,直到积中的小数部分为零为止。 然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位,得到最终结果。 0.1转换为二进制表示的计算过程如下。 ``` 0.1 * 2 = 0.2 //取出整数部分0 0.2 * 2 = 0.4 //取出整数部分0 0.4 * 2 = 0.8 //取出整数部分0 0.8 * 2 = 1.6 //取出整数部分1 0.6 * 2 = 1.2 //取出整数部分1 0.2 * 2 = 0.4 //取出整数部分0 0.4 * 2 = 0.8 //取出整数部分0 0.8 * 2 = 1.6 //取出整数部分1 0.6 * 2 = 1.2 //取出整数部分1 ``` 1.2取出整数部分1后,剩余小数为0.2,与这一轮运算的第一位相同,表示这将是一个无限循环的计算过程。 ``` 0.2 * 2 = 0.4 //取出整数部分0 0.4 * 2 = 0.8 //取出整数部分0 0.8 * 2 = 1.6 //取出整数部分1 0.6 * 2 = 1.2 //取出整数部分1 ... ``` 因此0.1转换成二进制表示为0.0 0011 0011 0011 0011 0011 0011……(无限循环)。 同理对0.2进行二进制的转换,计算过程与上面类似,直接从0.2开始,相比于0.1,少了第一位的0,其余位数完全相同,结果为0.0011 0011 0011 0011 0011 0011……(无限循环)。 将0.1与0.2相加,然后转换成52位精度的浮点型表示。 - 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 (0.1) + 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 (0.2) = 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 - 得到的结果为0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100,转换成十进制值为0.30000000000000004。 3. 解决方案 通过上面详细的讲解,相信大家已经了解了对浮点数进行运算时会存在的问题,那么我们该如何解决呢? 这里提供一种方法,主要思路是将浮点数先乘以一定的数值转换为整数,通过整数进行运算,然后将结果除以相同的数值转换成浮点数后返回。 下面提供一套用于做浮点数加减乘除运算的代码。 ``` const operationObj = { /** * 处理传入的参数,不管传入的是数组还是以逗号分隔的参数都处理为数组 * @param args * @returns {*} */ getParam(args) { return Array.prototype.concat.apply([], args); }, /** * 获取每个数的乘数因子,根据小数位数计算 * 1.首先判断是否有小数点,如果没有,则返回1; * 2.有小数点时,将小数位数的长度作为Math.pow()函数的参数进行计算 * 例如2的乘数因子为1,2.01的乘数因子为100 * @param x * @returns {number} */ multiplier(x) { let parts = x.toString().split('.'); return parts.length < 2 ? 1 : Math.pow(10, parts[1].length); }, /** * 获取多个数据中最大的乘数因子 * 例如1.3的乘数因子为10,2.13的乘数因子为100 * 则1.3和2.13的最大乘数因子为100 * @returns {*} */ correctionFactor() { let args = Array.prototype.slice.call(arguments); let argArr = this.getParam(args); return argArr.reduce((accum, next) => { let num = this.multiplier(next); return Math.max(accum, num); }, 1); }, /** * 加法运算 * @param args * @returns {number} */ add(...args) { let calArr = this.getParam(args); // 获取参与运算值的最大乘数因子 let corrFactor = this.correctionFactor(calArr); let sum = calArr.reduce((accum, curr) => { // 将浮点数乘以最大乘数因子,转换为整数参与运算 return accum + Math.round(curr * corrFactor); }, 0); // 除以最大乘数因子 return sum / corrFactor; }, /** * 减法运算 * @param args * @returns {number} */ subtract(...args) { let calArr = this.getParam(args); let corrFactor = this.correctionFactor(calArr); let diff = calArr.reduce((accum, curr, curIndex) => { // reduce()函数在未传入初始值时,curIndex从1开始,第一位参与运算的值需要 // 乘以最大乘数因子 if (curIndex === 1) { return Math.round(accum * corrFactor) Math.round(curr * corrFactor); } // accum作为上一次运算的结果,就无须再乘以最大因子 return Math.round(accum) Math.round(curr * corrFactor); }); // 除以最大乘数因子 return diff / corrFactor; }, /** * 乘法运算 * @param args * @returns {*} */ multiply(...args) { let calArr = this.getParam(args); let corrFactor = this.correctionFactor(calArr); calArr = calArr.map((item) => { // 乘以最大乘数因子 return item * corrFactor; }); let multi = calArr.reduce((accum, curr) => { return Math.round(accum) * Math.round(curr); }, 1); // 除以最大乘数因子 return multi / Math.pow(corrFactor, calArr.length); }, /** * 除法运算 * @param args * @returns {*} */ divide(...args) { let calArr = this.getParam(args); let quotient = calArr.reduce((accum, curr) => { let corrFactor = this.correctionFactor(accum, curr); // 同时转换为整数参与运算 return Math.round(accum * corrFactor) / Math.round(curr * corrFactor); }); return quotient; } }; ``` 接下来我们通过以下这些代码对加减乘除运算分别做测试,运算结果和我们期望的一致。 ``` console.log(operationObj.add(0.1, 0.7)); // 0.8 console.log(operationObj.subtract(0.3, 0.2)); // 0.1 console.log(operationObj.multiply(0.7, 180)); // 126 console.log(operationObj.divide(0.3, 0.1)); // 3 ```
上一篇:
1.2.3 isNaN()函数与Number.isNaN()函数对比
下一篇:
1.3 String类型详解
该分类下的相关小册推荐:
Javascript-ES6与异步编程
深入学习前端重构知识体系
WebSocket入门与案例实战
web前端开发性能优化实战
剑指javascript
KnockoutJS入门指南
ES6入门指南
Node.js 开发实战
npm script实战构建前端工作流
javascript设计模式原理与实战
经典设计模式Javascript版
JavaScript面试指南