惊闻 0.1 + 0.2 !== 0.3
前几天在学习JS语言,在做一个计算器时发现了一个神奇的东西,0.1+0.2不等于0.3,也就是(0.1 + 0.2 == 0.3)的值为false,先来看一下在JS中它给出的和是多少
<script type="text/javascript">
var flag=(0.1+0.2==0.3);
alert(flag); //弹框为true
alert(0.1+0.2);
</script>
当然这不是一个特例,在js中只要小数参加了四则运算它的精度都会发生丢失
<script type="text/javascript" language="javascript">
alert(1/3);//弹出: 0.3333333333333333
alert(0.09999999 + 0.00000001);//弹出: 0.09999999999999999
alert(-0.09999999 - 0.00000001);//弹出: -0.09999999999999999
alert(0.012345 * 0.000001);//弹出: 1.2344999999999999e-8
alert(0.000001 / 0.0001);//弹出: 0.009999999999999998
</script>
为什么?
- 个人理解
由于数字编码方式的局限性,因此在其进行十进制与二进制准换时,会存在位数上的变化,所以计算机是不能准确的表达出一个小数的,只能是无限的去逼近。
如果要比较两个浮点型的数据是否相等,那我们也可以采用无限逼近的方式来做
if(fabs(0.3-(0.1+0.2)) < 1E-10)
- 专业一点的
JavaScript的number类型按照ECMA的JavaScript标准,它的Number类型就是IEEE 754的双精度数值,相当于java的double类型。IEEE 754标准《二进制浮点数算法》(www.ieee.org)就是一个对实数进行计算机编码的标准。因此精度问题不止JS这门语言独有。
无论是用纸张记录数值,还是用计算机记录数值,都必须用某种编码方案来表达数值。必须理解的是,用编码表达的数值不是数值本身,而只是数值的一种人类或计算机可理解的描述。任何编码方案都有其局限,要么是表达范围(精度)方面的限制,要么是其他复杂性方面的制约。
绝对完美的数值编码方案是不存在的,为了处理方便,这个标准引入了大量的折衷和妥协,建立在这种表达方式上的算法(例如除法运算)也一样。由于数值表达方式存在“缺陷”,运算结果不可避免地堆聚起越来越多的误差。
参考资料
如何解决
1. 利用四舍五入来限制精度
var v=(0.1+0.3).toFixed(3)//保留小数点后3位
alert(v)//弹0.300
这种方法虽然可以解决问题,但也存在一定的局限性,比如说我想要计算5位小数,那它不就歇菜了,没关系我们再说一种刚刚学到的操作】
2. 小数转正数进行计算
既然精度问题是由于小数转换时的位数变化引起的,那么我们就直接将小数化为整数进行运算,算完之后再将它转回去,不就可以了
- 加法
//加法
Number.prototype.add = function(arg) {
var r1, r2, m;
try {
r1 = this.toString().split(".")[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg.toString().split(".")[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return (this * m + arg * m) / m
}
- 减法
//减法
Number.prototype.sub = function(arg) {
return this.add(-arg);
}
- 乘法
//乘法
Number.prototype.mul = function(arg) {
var m = 0,
s1 = this.toString(),
s2 = arg.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.prototype.div = function(arg) {
var t1 = 0,
t2 = 0,
r1, r2;
try {
t1 = this.toString().split(".")[1].length
} catch (e) {}
try {
t2 = arg.toString().split(".")[1].length
} catch (e) {}
with(Math) {
r1 = Number(this.toString().replace(".", ""))
r2 = Number(arg.toString().replace(".", ""))
return (r1 / r2) * pow(10, t2 - t1);
}
}
// 5674.784 t1 3 r1*1000 5674784
// 0.323342 t2 6 r2*1000000 0323342
这个算法还是比较好理解的,整理下来方便以后使用
这篇博客从数据结构上给出了分析,可以参考