一、为什么小数精度会丢失
Javascript采用了IEEE-745浮点数表示法(几乎所有的编程语言都采用),这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024。遗憾的是,我们常用的分数(特别是在金融的计算方面)都是十进制分数1/10,1/100等。二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,上诉代码的中的x和y的值非常接近最终的正确值,这种计算结果可以胜任大多数的计算任务:这个问题也只有在比较两个值是否相等时才会出现。
这个问题并不是只在javascript中才会出现,在任何使用二进制浮点数的编程语言中都会出现这个问题。 所以说,精度丢失并不是语言的问题,而是浮点数存储本身固有的缺陷。只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。
javascript的未来版本或许会支持十进制数字类型以避免这些舍入问题,在这之前,你更愿意使用大整数进行重要的金融计算,例如,要使用整数‘分’而不是使用小数‘元’进行货比单位的运算。
二、如何避免精度丢失
一般常用的有四个方法,第一个是设置一个“能够接受的误差范围”,在这个范围内,可认为没有误差;第二个是使用三方的类库math.js;第三是使用toFixed()方法;第四是封装一个计算类(加、减、乘、除)。
1、ES6在Number对象上面,新增了一个极小的常量Number.EPSILON,它表示1与大于1的最小浮点数之间的差,它是实际上是javascript能够表示的最小精度(可以接受的最小误差范围),误差如果小于这个值,就可以认为已经没有意义了,即不存在误差。
2、math.js是一个广泛应用于JavaScript 和 Node.js的数学库,它的特点是灵活表达式解析器,支持符号计算,内置大量函数与常量,并提供集成解决方案来处理不同的数据类型,如数字,大数字,复数,分数,单位和矩阵。
3、定义:toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
用法:NumberObject.toFixed(num) 其中num是必须的,规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围。如果省略了该参数,将用 0 代替。
然而实际上,并不是完美的,可能你开发时候测试的几个实例恰巧都是你想要的结果,可能在实际上线后遇到大量的数据后发现出问题了,不能正确的计算。一般是在遇到最后一位是5的时候,就不是’四舍五入”,eg:2.55.toFixed(1) // 2.5,而我们齐期望的是2.56。
我有查这个产生误差的原因,有人说是“银行家的舍入规则”,即四舍六入五考虑,这里“四”是指≤4 时舍去,”六”是指≥6时进上。”五”指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:5前为奇数,舍5入1;5前为偶数,舍5不进(0是偶数)。但在某些情况下也是不成立。
由于无法解决这种问题,所以看到有一些是以项目需求为准重写符合要求的函数,在Math.round(x)来扩展解决toFixed()四舍五入不精确的问题。
原本round(x) 方法可把一个数字四舍五入为最接近的整数,其中x是必须的且必须是数字。虽然解决了四舍五入的问题,但却没有直接解决保留小数点后多少位的问题,因而需要重写符合需求的函数。
toFixed的修复
4、封装一个计算类
三、javascript可以存储的 最大数字以及最大安全数字
最大数字是Number.MAX_VALUE、最大安全数字是Number.MAX_SAFE_INTEGER。Number.MAX_VALUE大于Number.MAX_SAFE_INTEGER,我的理解是js可以精确表示最大安全数字以内的数,超过了最大安全数字但没超过最大数字可以表示,但不精确,如果超过了最大数字,则这个数值会自动转换成特殊的Infinity值。
由于内存的限制,ECMAScript并不能保存世界上所有的数值,ECMAScript能够表示的最小数值是Number.MIN_VALUE,能够表示的最大数值是Number.MAX_VALUE。超过数值是正值,则被转成Infinity(正无穷),如果是负值则被转成-Infinity(负无穷)。如果在某次返回了正或负的Infinity值,那么该值将无法继续参与下一次的计算,所以我们需要确定一个数值是不是有穷的,即是不是位于最小和最大的数值之间,可以使用isFinite()函数,如果该函数参数在最小和最大数值之间时会返回true。注意,如果参数类型不是数值,Number.isFinite一律返回false。
JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。