当前位置: 代码迷 >> Web前端 >> 拿什么来拯救你 - 运算精度有关问题
  详细解决方案

拿什么来拯救你 - 运算精度有关问题

热度:111   发布时间:2012-11-23 22:54:33.0
拿什么来拯救你 -- 运算精度问题

一 如何做到精确的round运算?

今天在做一个demo的时候,因为生成随机数的关系,需要round一下数值,结果遇到了计算精度的问题:有些round结果是类似32.000004的值。以前在项目里曾经fix过类似的bug,所以今天花了一点功夫来研究如何做到精确的round,特别是在EMCA Script下。

借股沟之能,发现基本上就两种解决思路:

  1. 使用乘除法

这种做法基本思路就是如下代码:

?

function roundNumber(num, precision) {
    var base = Math.pow(10, precision);
    return Math.round(num * base ) / base ;
}

?这种做法的优点是非常简洁;然而缺点也是显而易见的:不是100%准确。比如下面下面这种运算:

?

alert(3.27691*100/100); //display 3.2769099999999995

改进?

也许有人会想到这种不精确是由于除法,设计一个不用除法的算法也许就行了?的确,除法很多时候是精度问题的根源,但只是根源之一。比如下面这个例子:

?

alert(4.935*100); // still not inaccurate

?看起来涉及到字符运算的,统统都是不可靠的。因此很多人想出了下面这个做法:

?2. 字符串运算

这种做法基本思路就是将数值先转换到字符串,然后通过小数点拆分数字做比较。这种做法实现比较复杂,比如下面两个link:

?

  • http://www.mediacollege.com/internet/javascript/number/round.html
  • http://phpjs.org/functions/round:505

这种做法,貌似是解决精度问题了。其实它也不能解决,比如:

?

alert(4.72352337135978971111);  // display 4.723523371359789

?如果你正好要round到15位(就是7897111),字符串运算也帮不了你,因为后面的711111统统被“自然”过滤了。

?

3. Number.toFixed()

这种方法比较好,特别是你需要显示数值的时候。不如还是有不少小缺点:

1. 需要人工在每个需要转换的地方写上parseInt(num.toFixed(3)).

2. 仍然不能避免如下的错误:

var num = 4.72352337135978971111;
alert(num.toFixed(15));
?

二 问题在哪里?

到这里,各位看官应该明白,在native的primitive number下似乎是不可能做到100%的round的。这可以推广到几乎所有运算。问题根源在哪里?其实很简单,就是数值在计算机中的表示形式。EMCA Script的number,在内部是用IEEE754的浮点数来表示的。这种格式无法避免精度问题,甚至连literal value都不能保证精度,更别提运算结果了。

?

三 出路?

其实精度问题也没必要杞人忧天,一般的数字,即使用第一种方法,你也不太会遇到精度问题。我之所以遇到,是因为用了很多随机数(通常小数点后都比较长)。不过你的需求里如果可能涉及到比较复杂的数字计算,就要好好考虑一下精度问题了。

?

  1. 优化计算过程。比如a/2+b/2+c/2,应该改成(a+b+c)/2。
  2. 明确精度需求。比如网上商城,一般也就是精确到小数点后2位,一般不太会遇到这种问题。但是小数点比较长的,就要特别注意,要澄清误差允许范围。
  3. 避免在前台计算。在设计的时候,可以考虑在后台计算。Java/C#之类的都内置了BigNumber,可以很好解决精度问题。
  4. 使用/实现前台的BigNumber。这个是极其费力的做法了,一方面你要寻找或者自己实现BigNumber,另外所有用到的数值,都必须用BigNumber封装。
  相关解决方案