JS浮点数四则运算

本文深入探讨了JavaScript中浮点数运算出现误差的原因,源于JavaScript使用IEEE 754标准,浮点数以双精度存储,导致小数位数超出53位时产生误差。解决方案包括使用数学类库、转换为整数运算以及自定义处理函数。此外,还介绍了浮点数的内存占用和数值范围差异。

一.常见错误

// 加法
   0.1 + 0.2 = 0.30000000000000004
   0.1 + 0.7 = 0.7999999999999999
   0.2 + 0.4 = 0.6000000000000001
 
   // 减法
   0.3 - 0.2 = 0.09999999999999998
   1.5 - 1.2 = 0.30000000000000004
 
   // 乘法
   0.8 * 3 = 2.4000000000000004
   19.9 * 100 = 1989.9999999999998
 
   // 除法
   0.3 / 0.1 = 2.9999999999999996
   0.69 / 10 = 0.06899999999999999
 
   // 比较
   0.1 + 0.2 === 0.3 // false
   (0.3 - 0.2) === (0.2 - 0.1) // false

二.原因分析

JavaScript 内部只有一种数字类型Number,也就是说,JavaScript 语言的底层根本没有整数,所有数字都是以IEEE-754标准格式64位浮点数形式储存,1与1.0是相同的。因为有些小数以二进制表示位数是无穷的。JavaScript会把超出53位之后的二进制舍弃,所以涉及小数的比较和运算要特别小心。 

JS的浮点数实现遵循IEEE 754标准,采用双精度存储(double precision),使用64位固定长度来表示,其中1位用来表示符号位,11位用来表示指数,52位表示尾数。如下图:

 

  • 符号位(sign):第1位是正负数符号位,0代表正数,1代表负数

  • 指数位(Exponent):中间11位存储指数,用来表示次方数

  • 尾数位(mantissa):最后的52位是尾数,超出部分自动进一舍零

浮点数的计算步骤(0.1+0.2)

1)首先,十进制的0.1和0.2会转换成二进制的,但是由于浮点数用二进制表示是无穷的

0.1——>0.0001 1001 1001 1001 ...(1001循环)
0.2——>0.0011 0011 0011 0011 ...(0011循环)

2)IEEE754标准的64位双精度浮点数的小数部分最多支持53位二进制,多余的二进制数字被截断,所以两者相加之后的二进制之和是

0.0100110011001100110011001100110011001100110011001101

3)将截断之后的二进制数字再转换为十进制,就产生了误差 

0.30000000000000004

三.解决方法

1、引用类库

  1. Math.js

  2. decimal.js

  3. big.js

2、将浮点数变为整数进行操作

在知道小数位个数的前提下,可以考虑通过将浮点数放大倍数到整型,再进行运算操作,最后再除以之前放大的倍数,这样就能得到正确的结果了。之前开发过的人人分期和人人理财网站都采用这种方式。

0.1 + 0.2 ==> (0.1 * 10 + 0.2 * 10) / 10      // 0.3
0.8 * 3   ==> ( 0.85 * 100 * 3) / 100         //2.4

3、自定义处理函数

加法(转成字符串处理后再转成数字)

function accAdd(arg1, arg2) {
	var r1, r2, m, c;
      
	try {
      r1 = arg1.toString().split(".")[1].length;	// 获取arg1有多少小数位数
  } catch (e) {
      r1 = 0;
	}
      
	try {
		r2 = arg2.toString().split(".")[1].length;	// 获取arg2有多少小数位数
	} catch (e) {
		r2 = 0;
	}
	
  c = Math.abs(r1 - r2);	// 比较arg1和arg2小数位数相差几位
	m = Math.pow(10, Math.max(r1, r2));	// 获取arg1和arg2统一放大的倍数(10的N次方)

  if (c > 0) {
  	var cm = Math.pow(10, c);	// 获取小数位数少的参数需要放大的倍数(10的c次方)

  	if (r1 > r2) {	// arg1小数位数大于arg2,统一转换成整数
  		arg1 = Number(arg1.toString().replace(".", ""));
  		arg2 = Number(arg2.toString().replace(".", "")) * cm;
  	} else {	// arg1小数位数小于arg2,统一转换成整数
  		arg1 = Number(arg1.toString().replace(".", "")) * cm;
  		arg2 = Number(arg2.toString().replace(".", ""));
  	}
  } else {	// arg1小数位数和arg2一样,去除小数点,统一转换成整数
  	arg1 = Number(arg1.toString().replace(".", ""));
  	arg2 = Number(arg2.toString().replace(".", ""));
  }
  
  return (arg1 + arg2) / m;	// 除以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));	// 获取arg1和arg2统一放大的倍数(10的N次方)
      n = (r1 >= r2) ? r1 : r2;
      return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

乘法

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);
}

除法

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);
	}
}

术语解释

IEEE二进制浮点数算术标准IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零 -0)与反常值(denormal number),一些特殊数值(无穷 Inf 与非数值 NaN),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。

拓展资料

单精度和双精度区别:

1.所占的内存不同

单精度浮点数(float)占用4个字节(32位)存储空间来存储一个浮点数,包括符号位1位,阶码8位,尾数23位。

双精度浮点数(double)占用 8个字节(64位)存储空间来存储一个浮点数,包括符号位1位,阶码11位,尾数52位。

2.所存的数值范围不同

单精度浮点数的数值范围为-3.4E38~3.4E38。

双精度浮点数可以表示的数字的绝对值范围大约是:-2.23E308 ~ 1.79E308。

3.十进制下的位数不同

单精度浮点数最多有7位十进制有效数字,如果某个数的有效数字位数超过7位,当把它定义为单精度变量时,超出的部分会自动四舍五入。

双精度浮点数可以表示十进制的15或16位有效数字,超出的部分也会自动四舍五入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值