Javascript初学者的困惑——神奇的浮点数

来源:互联网 发布:深圳市软件企业协会 编辑:程序博客网 时间:2024/05/01 20:42

谁动了我的浮点数

  • 下面是一段很简单的javascript代码,我们把10个0.1累加起来,并且每次都输出中间的结果:
function sumFloat(){
var sum = 0;
for(var i=0; i<10; i++){
sum += 0.1;
alert(sum);
}
}
  • 输出的sum应该从0.1到1.0,每次增长0.1,然而出乎意料的是,我们看到的是这样的输出:
0.1
0.2
0.3000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
  • 谁动了我的浮点数?


幕后黑手

  • 现象很灵异,然而原因很简单——0.1这个浮点数在二进制中无法精确表示。
  • 我们知道,计算机内部的所有数值计算都是基于二进制的,十进制的0.1对应的二进制是0.00001111……,后面的1无限循环,而javascript中浮点数是64位(IEEE 754标准),对位数的截断带来了精度的丢失,因此使得累加的结果超出了我们的预料。


权宜之计

  • 那么,既然这个问题来自于进制表示,我们就没有办法解决它了吗?
  • 目前我能想到的只有一个权宜之计——乘一个足够大的系数让浮点数变成整数,累加完成后,再除以它得到实际结果。
  • 为解释清楚,还是看修改后的javascript代码:
function sumFloat(){
var sum = 0;
var ratio = 10;
for(var i=0; i<10; i++){
sum += 0.1 * ratio ;
alert(sum / ratio);
}
sum /= ratio;
}
  • 在这个简单的例子里,系数为10便足以解决浮点数不精确的问题,但在更复杂的情况下,系数的有效选取是十分困难甚至不可能的问题,目前还没有想到很好的办法。
  • 另外,若有其它的解决之道,也欢迎补充。
  • [我补充一下,最最彻底理想的解决方案:使用字符串表示浮点数,并且编写相应的计算函数,前提是不要求性能。]


不止伸向javascript的黑手

  • 可以在java中尝试如下代码:
double sum = 0.0;
for(int i=0; i<10; i++){
sum += 0.1;
System.out.println(sum);
}
  • 是否输出与上面的javascript代码一样呢?
  • 我们解释这个问题的时候并没有涉及到javascript的实现,而是将原因归咎于进制问题,因此不出意外,我们将在所有具有原生浮点类型的编程语言中遇到这个精度丢失的现象,大家可以用各种语言实验类似的代码。
  • 另外,把java代码中的double换成float(0.0、0.1改为0.0f、0.1f),看看结果有什么不一样?