JavaScript进阶(二十七):JavaScript 浮点数精度计算_js 浮点数子算(1)

最后

四轮技术面+一轮hr面结束,学习到了不少,面试也是一个学习检测自己的过程,面试前大概复习了 一周的时间,把以前的代码看了一下,字节跳动比较注重算法,面试前刷了下leetcode和剑指offer, 也刷了些在牛客网上的面经。大概就说这些了,写代码去了~

祝大家都能收获大厂offer~

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

篇幅有限,仅展示部分内容

	- * [4.1.1 Math.js](#411_Mathjs_164)
		* [4.1.2 decimal.js](#412_decimaljs_172)
		* [4.1.3 big.js](#413_bigjs_179)
+ [五、延伸阅读](#_194)
+ - [5.1 IEEE 754 标准](#51_IEEE_754__195)
	- [5.2 toFixed()](#52_toFixed_208)
+ [六、拓展阅读](#_236)

一、前言

JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的。即所有数字都是以 64 位浮点数形式储存,即便整数也是如此。 所以我们在打印 1.00 这样的浮点数的结果是 1 而非 1.00 。浮点数的精度问题也不只是JavaScript特有,因为有些小数以二进制表示位数是无穷的。

十进制           二进制
0.1              0.0001 1001 1001 1001 ...
0.2              0.0011 0011 0011 0011 ...
0.3              0.0100 1100 1100 1100 ...
0.4              0.0110 0110 0110 0110 ...
0.5              0.1
0.6              0.1001 1001 1001 1001 ...

比如 1.1,其程序实际上无法真正的表示 ‘1.1’,而只能做到一定程度上的准确,这是无法避免的精度丢失:1.09999999999999999

二、浮点数精度问题

其实,在JavaScript中浮点数精度问题还要复杂些,这里只给一些在Chrome中测试数据:

console.log(1.0-0.9 == 0.1)    //false
console.log(1.0-0.8 == 0.2)    //false
console.log(1.0-0.7 == 0.3)    //false
console.log(1.0-0.6 == 0.4)    //true
console.log(1.0-0.5 == 0.5)    //true
console.log(1.0-0.4 == 0.6)    //true
console.log(1.0-0.3 == 0.7)    //true
console.log(1.0-0.2 == 0.8)    //true
console.log(1.0-0.1 == 0.9)    //true

那如何来避免这类 1.0-0.9 != 0.1 的非bug型问题发生呢?下面给出一种目前用的比较多的解决方案, 在判断浮点运算结果前对计算结果进行精度缩小,因为在精度缩小的过程总会自动四舍五入:

(1.0-0.9).toFixed(digits)  // toFixed() 精度参数digits须在0与20之间
console.log(parseFloat((1.0-0.9).toFixed(10)) === 0.1)   //true
console.log(parseFloat((1.0-0.8).toFixed(10)) === 0.2)    //true
console.log(parseFloat((1.0-0.7).toFixed(10)) === 0.3)    //true
console.log(parseFloat((11.0-11.8).toFixed(10)) === -0.8)   //true

写成一个方法:

//通过isEqual工具方法判断数值是否相等
function isEqual(number1, number2, digits){
  digits = digits == undefined? 10: digits; // 默认精度为10
  return number1.toFixed(digits) === number2.toFixed(digits);
}
console.log(isEqual(1.0-0.7, 0.3));  //true
//原型扩展方式,更喜欢面向对象的风格
Number.prototype.isEqual = function(number, digits){
  digits = digits == undefined? 10: digits; // 默认精度为10
  return this.toFixed(digits) === number.toFixed(digits);
}
console.log((1.0-0.7).isEqual(0.3)); //true

接下来,再来试试浮点数的运算,

console.log(1.79+0.12)  //1.9100000000000001
console.log(2.01-0.12)   //1.8899999999999997
console.log(1.01\*1.3)    //1.3130000000000002
console.log(0.69/10)     //0.06899999999999999

解决方案 - 先升幂再降幂,同时辅以精度控制

//加法函数,用来得到精确的加法结果
//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
//调用:accAdd(arg1,arg2)
//返回值:arg1加上arg2的精确结果
function accAdd(arg1,arg2){
  var r1,r2,m;
  try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
  try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
  m=Math.pow(10,Math.max(r1,r2))
  return (arg1\*m+arg2\*m)/m
}
//给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.add = function (arg){
  return accAdd(arg,this);
}
 
//减法函数,用来得到精确的减法结果
//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的减法结果。
//调用:accSub(arg1,arg2)
//返回值:arg1减去arg2的精确结果
function accSub(arg1,arg2){
  var r1,r2,m,n;
  try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
  try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
  m=Math.pow(10,Math.max(r1,r2));
  //last modify by deeka
  //动态控制精度长度
  n=(r1>=r2)?r1:r2;
  return ((arg1\*m-arg2\*m)/m).toFixed(n);
}
 
//除法函数,用来得到精确的除法结果
//说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
//调用:accDiv(arg1,arg2)
//返回值:arg1除以arg2的精确结果
function accDiv(arg1,arg2){
  var t1=0,t2=0,r1,r2;
  try{t1=arg1.toString().split(".")[1].length}catch(e){}
  try{t2=arg2.toString().split(".")[1].length}catch(e){}
  with(Math){
    r1=Number(arg1.toString().replace(".",""))
    r2=Number(arg2.toString().replace(".",""))
    return (r1/r2)\*pow(10,t2-t1);
  }
}
//给Number类型增加一个div方法,调用起来更加方便。
Number.prototype.div = function (arg){
  return accDiv(this, arg);
}
 
//乘法函数,用来得到精确的乘法结果
//说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
//调用:accMul(arg1,arg2)
//返回值:arg1乘以arg2的精确结果
function accMul(arg1,arg2) {
  var m=0,s1=arg1.toString(),s2=arg2.toString();
  try{m+=s1.split(".")[1].length}catch(e){}
  try{m+=s2.split(".")[1].length}catch(e){}
  return  Number(s1.replace(".",""))\*Number(s2.replace(".",""))/Math.pow(10,m)
}
//给Number类型增加一个mul方法,调用起来更加方便。
Number.prototype.mul = function (arg){
  return accMul(arg, this);
}

结果如下:

console.log(accAdd(1.79, 0.12));  //1.91
console.log(accSub(2.01, 0.12));  //1.89
console.log(accDiv(0.69, 10));    //0.069<br>console.log(accMul(1.01, 1.3)); //1.313 

改造之后,可以愉快地进行浮点数加减乘除操作了~

三、整数精度问题

Javascript 中,整数精度同样存在问题,先来看看问题:

JavaScript 代码:

console.log(19571992547450991); //=> 19571992547450990
console.log(19571992547450991===19571992547450992); //=> true

同样的原因,在 JavaScriptNumber类型统一按浮点数处理,整数是按最大54位来算最大(253 - 1,Number.MAX_SAFE_INTEGER,9007199254740991) 和最小(-(253 - 1),Number.MIN_SAFE_INTEGER,-9007199254740991) 安全整数范围的。所以只要超过这个范围,就会存在被舍去的精度问题。

当然这个问题并不只是在 Javascript 中才会出现,几乎所有的编程语言都采用了 IEEE-745 浮点数表示法,任何使用二进制浮点数的编程语言都会有这个问题,只不过在很多其他语言中已经封装好了方法来避免精度的问题,而 JavaScript 作为一门弱类型语言,从设计思想上就没有对浮点数有严格的数据类型,所以精度误差的问题就显得格外突出。

四、解决方案

4.1 类库

文末

篇幅有限没有列举更多的前端面试题,小编把整理的前端大厂面试题PDF分享出来,一共有269页

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值