已阅读5页,还剩10页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
关于JavaScript中计算精度丢失的问题关于JavaScript中计算精度丢失的问题摘要:由于计算机是用二进制来存储和处理数字,不能精确表示浮点数,而JavaScript中没有相应的封装类来处理浮点数运算,直接计算会导致运算精度丢失。为了避免产生精度差异,把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法。关键词:计算精度 四舍五入 四则运算 精度丢失1. 疑惑我们知道,几乎每种编程语言都提供了适合货币计算的类。例如C#提供了decimal,Java提供了BigDecimal,JavaScript提供了Number由于之前用decimal和BigDecimal用得很好,没有产生过精度问题,所以一直没有怀疑过JavaScript的Number类型,以为可以直接使用Number类型进行计算。但是直接使用是有问题的。我们先看看四舍五入的如下代码:alert(Number(0.009).toFixed(2);alert(Number(162.295).toFixed(2);按正常结果,应该分别弹出0.01和162.30。但实际测试结果却是在不同浏览器中得到的是不同的结果:在ie6、7、8下得到0.00和162.30,第一个数截取不正确;在firefox中得到0.01和162.29,第二个数截取不正确;在opera下得到0.01和162.29,第二个数截取不正确我们再来看看四则运算的代码: alert(1/3);/弹出: 0.3333333333333333alert(0.1 + 0.2);/弹出: 0.30000000000000004 alert(-0.09 - 0.01);/弹出: -0.09999999999999999alert(0.012345 * 0.000001);/弹出: 1.2344999999999999e-8alert(0.000001 / 0.0001);/弹出: 0.009999999999999998按正常结果,除第一行外(因为其本身就不能除尽),其他都应该要得到精确的结果,从弹出的结果我们却发现不是我们想要的正确结果。是因为没有转换成Number类型吗?我们转换成Number后再计算看看:alert(Number(1)/Number(3);/弹出: 0.3333333333333333 alert(Number(0.1) + Number(0.2);/弹出: 0.30000000000000004 alert(Number(-0.09) Number(0.01);/弹出: -0.09999999999999999 alert(Number(0.012345) * Number(0.000001);/弹出: 1.2344999999999999e-8 alert(Number(0.000001) / Number(0.0001);/弹出: 0.009999999999999998还是一样的结果,看来javascript默认把数字识别为number类型。为了验证这一点,我们用typeof弹出类型看看:alert(typeof(1);/弹出: numberalert(typeof(1/3);/弹出: numberalert(typeof(-0.09999999);/弹出: number2. 原因为什么会产生这种精度丢失的问题呢?是javascript语言的bug吗?我们回忆一下大学时学过的计算机原理,计算机执行的是二进制算术,当十进制数不能准确转换为二进制数时,这种精度误差就在所难免。再查查javascript的相关资料,我们知道javascript中的数字都是用浮点数表示的,并规定使用IEEE 754标准的双精度浮点数表示:IEEE 754 规定了两种基本浮点格式:单精度和双精度。IEEE单精度格式具有24位有效数字精度(包含符号号),并总共占用32位。IEEE双精度格式具有53位有效数字精度(包含符号号),并总共占用64位。这种结构是一种科学表示法,用符号(正或负)、指数和尾数来表示,底数被确定为2,也就是说是把一个浮点数表示为尾数乘以2的指数次方再加上符号。下面来看一下具体的规格:符号位指数位小数部分指数偏移量单精度浮点数1位(31)8位(30-23)23位(22-00)127双精度浮点数1位(63)11位(62-52)52位(51-00)1023我们以单精度浮点数来说明:指数是8位,可表达的范围是0到255而对应的实际的指数是127到128这里特殊说明,127和128这两个数据在IEEE当中是保留的用作多种用途的127表示的数字是0128和其他位数组合表示多种意义,最典型的就是NAN状态。知道了这些,我们来模拟计算机的进制转换的计算,就找一个简单的0.1+0.2来推演吧:十进制0.1 = 二进制0.00011001100110011(循环0011) =尾数为1.10011001100110011001100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0 = 计算机存储为:0 00000000100 1001100110011001111001 = 因为尾数最多52位,所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001 而十进制0.2 = 二进制0.0011001100110011(循环0011) =尾数为1.10011001100110011001100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0 = 存储为:0 00000000011 1001100110011001111001 因为尾数最多52位,所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011 那么两者相加得: 0.00011001100110011001100110011001100110011001100110011001 + 0.00110011001100110011001100110011001100110011001100110011 = 0.01001100110011001100110011001100110011001100110011001100 转换成10进制之后得到:0.30000000000000004从上述的推演过程我们知道,这种误差是难免的,c#的decimal和Java的BigDecimal之所以没有出现精度差异,只是因为在其内部作了相应处理,把这种精度差异给屏蔽掉了,而javascript是一种弱类型的脚本语言,本身并没有对计算精度做相应的处理,这就需要我们另外想办法处理了。3. 解决办法3.1 升级降级从上文我们已经知道,javascript中产生精度差异的原因是计算机无法精确表示浮点数,连自身都不能精确,运算起来就更加得不到精确的结果了。那么怎么让计算机精确认识要计算的数呢?我们知道十进制的整数和二进制是可以互相进行精确转换的,那么我们把浮点数升级(乘以10的n次幂)成计算机能够精确识别的整数来计算,计算完毕之后再降级(除以10的n次幂),不就得到精确的结果了吗?对,就这么办!我们知道,Math.pow(10,scale)可以得到10的scale次方,那么就把浮点数直接乘以Math.pow(10,scale)就可以了吗?我最初就是这么想的,但后来却发现一些数字运算后实际结果与我们的猜想并不一致。我们来看看这个简单的运算:alert(512.06*100);按常理应该返回51206,但实际结果却是51205.99999999999。奇怪吧?其实也不奇怪,这是因为浮点数不能精确参与乘法运算,即使这个运算很特殊(只是乘以10的scale次方进行升级)。如此我们就不能直接乘以10的scale次方进行升级,那就让我们自己来挪动小数点吧。怎么挪动小数点肯定大家是各有妙招,此处附上我写的几个方法:/* * 左补齐字符串 * * param nSize * 要补齐的长度 * param ch * 要补齐的字符 * return */Stotype.padLeft = function(nSize, ch) var len = 0; var s = this ? this : ; ch = ch ? ch : 0;/ 默认补0 len = s.length; while (len nSize) s = ch + s; len+; return s;/* * 右补齐字符串 * * param nSize * 要补齐的长度 * param ch * 要补齐的字符 * return */Stotype.padRight = function(nSize, ch) var len = 0; var s = this ? this : ; ch = ch ? ch : 0;/ 默认补0 len = s.length; while (len nSize) s = s + ch; len+; return s;/* * 左移小数点位置(用于数学计算,相当于除以Math.pow(10,scale)) * * param scale * 要移位的刻度 * return */Stotype.movePointLeft = function(scale) var s, s1, s2, ch, ps, sign; ch = .; sign = ; s = this ? this : ; if (scale = 0) return s; ps = s.split(.); s1 = ps0 ? ps0 : ; s2 = ps1 ? ps1 : ; if (s1.slice(0, 1) = -) s1 = s1.slice(1); sign = -; if (s1.length = scale) ch = 0.; s1 = s1.padLeft(scale); return sign + s1.slice(0, -scale) + ch + s1.slice(-scale) + s2;/* * 右移小数点位置(用于数学计算,相当于乘以Math.pow(10,scale)) * * param scale * 要移位的刻度 * return */Stotype.movePointRight = function(scale) var s, s1, s2, ch, ps; ch = .; s = this ? this : ; if (scale = 0) return s; ps = s.split(.); s1 = ps0 ? ps0 : ; s2 = ps1 ? ps1 : ; if (s2.length = 0) return this.movePointRight(scale); else return this.movePointLeft(-scale);这样我们升级降级都可以转换成字符串后调用String对象的自定义方法movePoint了,乘以10的scale次方我们传正整数scale,除以10的scale次方我们传负整数-scale。再来看看我们之前升级512.06的代码,采用自定义方法的调用代码变成这样:alert(512.06.toString().movePoint(2); /弹出: 51206这样直接挪动小数点就不怕它不听话出现一长串数字了(*_*)。 当然,movePoint方法得到的结果是字符串,如果要转成Number类型也很方便(怎么转就不再废话了)。3.2 四舍五入好,有了升级降级的基础,我们来看看四舍五入的方法,由于不同浏览器对Number的toFixed方法有不同的支持,我们需要用自己的方法去覆盖浏览器的默认实现。有一个简单的办法是我们自己来判断要截取数据的后一位是否大于等于5,然后进行舍或者入。我们知道Math.ceil方法是取大于等于指定数的最小整数,Math.floor方法是取小于等于指定数的最大整数,于是我们可以利用这两个方法来进行舍入处理,先将要进行舍入的数升级要舍入的位数scale(乘以10的scale次方),进行ceil或floor取整后,再降级要舍入的位数scale(除以10的scale次方)。代码如下:Ntotype.toFixed = function(scale) var s, s1, s2, start; s1 = this + ; start = s1.indexOf(.); s = s1.movePoint(scale); if (start = 0) s2 = Number(s1.substr(start + scale + 1, 1); if (s2 = 5 & this = 0 | s2 5 & this 4) for (var i = a.length - 2; i = 0; i-) ai = parseInt(ai) + 1; if (ai = 10) ai = 0; b = i != 1; else break; s = a.join().replace(new RegExp(d+)(d + scale + )d$), $1.$2); if (b) s = s.substr(1); return (pm + s).replace(/.$/, ); return this + ;经验证,这两个方法都能够进行准确的四舍五入,那么采用哪个方法好呢?实践出真知,我们写一个简单的方法来验证一下两种方式的性能:function testRound() var dt, dtBegin, dtEnd, i; dtBegin = new Date(); for (i=0; i n2 ? n1 : n2; s = Number(s1.movePoint(n) + Number(s2.movePoint(n); s = s.toString().movePoint(-n); return Number(s);这时候再执行之前的加法alert(Number(0.1).add(0.2);/弹出0.3这时候就可以计算出精确的结果了。类似可以写出减法:Ntotype.sub = function(arg) var n, n1, n2, s, s1, s2, ps; s1 = this.toString(); ps = s1.split(.); n1 = ps1 ? ps1.length : 0; s2 = arg.toString(); ps = s2.split(.); n2 = ps1 ? ps1.length : 0; n = n1 n2 ? n1 : n2; s = Number(s1.movePoint(n) - Number(s2.movePoint(n); s = s.toString().movePoint(-n); return Number(s);类似可以写出乘法:Ntotype.mul = function(arg) var n, n1, n2, s, s1, s2, ps; s1 = this.toString(); ps = s1.split(.); n1 = ps1 ? ps1.length : 0; s2 = arg.toString(); ps = s2.split(.); n2 = ps1 ? ps1.length : 0; n = n1 + n2; s = Number(s1.replace(., ) * Number(s2.replace(., ); s = s.to
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 安全第一课:保护自我远离危险小学主题班会课件
- 申请办公设施采购清单函8篇
- 运动健身教练会员满意度与专业技能水平绩效考评表
- 安全意识第一:守护我们的校园小学主题班会课件
- 审查师审查程序应用指导书
- 电子银行系统开发与设计手册
- 高等教育专业课程设置原则解析
- 客户接待礼仪七项核心要点指导书
- 物流与供应链一体化协同管理方案设计与实施指南
- 新型环保家电发展趋势报告
- 2026广东广州市海珠区科学技术协会招聘雇员1人考试备考题库及答案详解
- 2025年邢台市水务发展集团有限公司招聘真题
- 设备购买意向性合同
- 2026年科技局事业单位招聘考试试题及答案
- 林带养护施工方案(3篇)
- 2026年《中华人民共和国保守秘密法》培训课件
- 中国宗法制度论
- 血透室消毒隔离制度
- 常见手术麻醉配合要点
- 钢结构施工工艺流程与现场施工管理
- 包装组管理制度
评论
0/150
提交评论