关于JavaScript中Math对象的可变方法

今天,我们正在发布一篇有关JavaScript中数学计算的文章的翻译,这是其作者在WaffleJS上的演讲的书面版本该声明本身是Twitter上对话的延续


数学教育

对数学感兴趣


这一切都是从我的数学教育开始的,后来我收到了一些延误。当我学习计算机科学时,我可以与世界一流的数学老师进行交流,但是我错过了这个机会。我不喜欢数学:所研究的主题与实践相去甚远。此外,我已经因计算机培训的深层次理论程序而失去了平衡。然后,我相信她已经脱离了生活,而且在大多数情况下,我仍然这样认为。

但是现在,在我完成学业的几年后,对数学学习的渴望在我心中激起。即使很小的数学知识应用对我的工作和兴趣也会产生什么影响,使我受到启发。但是我没有一个明确的学习计划。

然后,在2012年,我开始着手简单统计项目,找到了一种学习数学的方法。从那时起,我扩展并支持了这个项目。现在,它包括许多算法的实现;事实证明它是最“ 繁星 ”的数学JavaScript库之一。而且,显然,人们确实在使用该库。

但是我从2012年开始工作。如果我们谈论这段时间技术是如何变化的,那是很久以前的事情了。此后,8个LTS版本的Node.js已被释放。从那时起,JavaSript本身以及使用该语言编写的程序的工作环境发生了很大变化。在2012年,React库尚不存在,因此尚未对Babel项目进行首次提交。


时间的流逝

多年来,我注意到更新Node.js时测试崩溃了。例如,我可能有这样的测试:

t.equal(ss.gamma(11.54), 13098426.039156161);

该测试在Node.js v10中工作正常,但在Node.js v12中失败。在这里,它不是被测试了一些超级复杂的方法:该功能gamma使用标准的JavaScript函数实现的- Math.powMath.sqrtMath.sin

算术


我知道您在这里会想到什么:算术。在Twitter上,由于计算以下表达式的结果,引起的讨论定期展开:

0.1 + 0.2 = 0.30000000000000004

但是,正如我已经写过的那样,所有流行的编程语言都具有这种方式,即使是像Haskell这样的老式又古怪的语言也是如此。浮点算术可能看起来很奇怪,但是它们的行为相同,它们的行为有据可查。即,我们正在谈论IEEE 754标准,其要求严格以编程语言实现。因此,问题就不在算术上:加法,减法,除法和乘法在语言中的实现,编程可以说是“刻板的”。

数学对象


我的问题出在标准的JavaScript对象中Math特别是在此对象的所有方法中

一些技术,如Math.sinMath.cosMath.expMath.powMath.tan-是基本成分几何等计算。当我理解了这一点之后,我开始分别研究Math不同版本的Node.js中对象基本方法的行为变化这里有些例子。

计算方式Math.tanh(0.1)

// Node 4
0.09966799462495590234
// Node 6
0.09966799462495581907

计算方式Math.pow(1/3, 3)

// Node 10
0.03703703703703703498
// Node 12
0.03703703703703702804

更糟糕的是,这个问题不仅在Node.js中显而易见。在浏览器和其他支持JavaScript的环境中也会发生同样的事情。

这导致我们提出以下问题:什么是数学计算?


计算的图形表示

三角法很容易可视化。如果您有一个圆圈并且可以支配几个月的高中,那么您就会知道余弦和正弦是圆圈边缘上某个点的坐标,并且sin和cos函数的图看起来像波浪。实际上,在高中时,他们研究了这些值的推导,但是用于此目的的方法-泰勒级数 -依赖于无限级数,计算机很难解决这些问题。

这是您可以从维基百科中学到的东西关于正弦计算算法:“没有用于计算正弦的标准算法。 IEEE 754-2008是浮点计算最广泛使用的标准,它不会影响正弦等三角函数的计算。”

计算机使用许多不同的近似值和算法来执行计算,例如CORDIC,各种技巧和查找表。所有这些异质性解释了GitHub上许多fastmath库的存在。事实是有很多方法可以实现该方法Math.sin。还有其他功能。例如,如您所知,Quake III Arena使用了更快的速度替换标准反平方根计算方法以加快渲染速度。

结果,数学计算是某些算法实施的结果。在实践中,使用了许多常见的算法及其变体。

JavaScript规范没有指定在语言实现中使用哪种特定算法,而是在数学计算中使用的函数方面为实现提供了很大的回旋余地。

这是标准对此的说明(ECMA-262,第10版,第20.2.2节):

“功能acos,acosh,asin,asinh,atan,atanh,atan2,cbrt,cos,cosh,exp,expm1,hypot,log,log1p,log2,log10,pow,pow,random,sin,sinh,sqrt,tan和tanh的行为除了参数的特定值需要返回某些结果的要求(这是值得注意的边界情况)外,这里没有完整描述。

我不知道负责ECMA-262标准的委员会成员的内部活动是如何组织的,但是我相信他们制定了该标准,因此,如果Intel或AMD发布新的超快速数学指令,就不会在JavaScript中造成兼容性危机。在他们的新处理器中。

由于存在许多广泛使用的JavaScript解释器,由于JavaScript经常在浏览器中使用以及在浏览器之间存在竞争之类的事实,并且甚至流行的JavaScript实现也面临着不断的压力。并被迫迅速发展,以提供最佳性能...正因为如此,我们拥有了自己所拥有的。任何使用JavaScript的人都会经常遇到这样的事实,即在不同的实现中,通过对象的方式执行的数学计算的结果Math是不同的。

在其他解释语言中,这并不重要,因为它们通常具有一些“规范”实现。例如,对于Python解释器,这是正确的。

计算在哪里进行?


现在,让我们仔细看看计算的确切位置。对于JavaScript,在三个区域中执行基本数学计算:

  1. 中央处理器。
  2. 语言解释器(特定JavaScript实现的C ++和C代码)。
  3. JavaScript代码,例如,专用库的代码。

▍1。中央处理器


当我想到进行计算的地方时,我想到的第一个想法是,计算是在处理器中执行的。我建议由于处理器执行算术计算,因此它们还可以执行一些更复杂的计算。事实证明,处理器具有执行三角函数和其他计算的指令,但是很少使用这些指令。例如,在具有x86架构的处理器中执行正弦计算并不十分普及,因为这种实现不一定比软件实现(使用处理器算术运算的实现)要快。另外,它不一定比软件实现更准确。

英特尔也因其非常强大而蒙羞在文档中夸大了三角运算准确性。由于与程序不同的是,微芯片无法修补,因此这种错误尤其可悲。

▍2。语言翻译


这就是计算子系统在大多数JavaScript实现中的工作方式。他们以各种方式实现这些子系统。

  • V8和SpiderMonkey引擎使用fdlibm库端口进行计算,这略有不同。该库最初由Sun Microsystems编写,已代代相传。
  • JavaScriptCore(Safari)使用cmath库执行大多数操作。
  • Internet Explorer使用cmath和一些用汇编器编写的代码块如果浏览器是为具有类似指令的处理器编译的,则此处甚至使用三角函数的处理器。

由于历史原因,用于在不同JS引擎中执行计算的工具已更改。因此,V8使用自己的解决方案进行计算,然后使用了fdlibm JavaScript端口,然后使用了C版本的fdlibm。

▍为什么会有问题?


这里的要点是,所有这些都会降低JavaScript在解决涉及数学计算的任何问题时产生统一结果的能力。这对数据科学尤其困难。我希望JavaScript更适合在浏览器中进行数据科学计算。此外,无法产生统一的结果意味着加剧了重现性危机,这是所有科学的特征。更不用说其他JavaScript问题,例如键入数字的功能以及缺少用于处理数据框架的广泛使用的库。

▍3。使用专门的库


我们有一种可靠的方式来使用JavaScript进行计算。它包括使用专门的库。因此,stdlib仅使用算术运算来实现高级计算。规范中对算术计算进行了全面描述,它们是标准的,因此stdlib返回的结果为我们提供了完全一致的结果,而不管执行代码的平台如何。

这是以复杂性和决策速度为代价的。 stdlib方法没有内置方法快。另外,为了“只计算正弦”,您需要将整个库连接到项目。

但是,如果您更广泛地考虑,这是完全正常的。例如,WebAssembly平台不给程序员任何执行高级数学计算的方法。在它的文档中,建议您在自己的模块中独立包括相应机制的实现:

“ WebAssembly不包括其自己的数学函数实现-例如sin,cos,exp,pow等。 WebAssembly针对此类功能的策略是允许开发人员将其作为WebAssembly平台本身中的库工具来实现(请注意,x86平台的sin和cos指令运行缓慢且不准确,因此,无论如何,尽量不要使用)。”

这就是编译语言一直以来的工作方式:当编译用C编写的程序时,从中导入的方法math.h将包含在编译程序中。

使用epsilon值


如果某人不想在他的JavaScript项目中包括stdlib库来执行计算,但是需要测试执行一些复杂计算的代码,那么他可能应该采用简单统计信息库中已经使用的方法。它与使用epsilon定义边界的值有关,在该边界内不考虑数字差异。如果我们考虑在数学中使用epsilon符号选项,那么可以说我所说的是“任意小的正值”。简单统计使用 epsilon值equal 0.0001

如果需要找出两个数字是否相等,则检查表格的条件Math.abs(result — expected) < epsilon如果这个条件成立,那么我们可以说数字之间的差落在给定范围内,并认为它们相等。

加法


▍精度


Twitter上的评论员指出,示例中获得的结果变化超出了浮点数有效位数范围从技术角度来看,这是正确的,这意味着与使用值相比,您可以找到一种更准确的数字比较方式epsilon但实际上,这里的故事是一样的-数字末尾的数字会影响结果,并在最终结果中引入不准确之处。此外,此处给出的示例并不详尽。事实是,在不脱离规范的情况下,JavaScript解释器的实现功能会导致大多数数值结果出现差异。

▍JavaScript


我不想批评JavaScript。我认为,考虑到未来的不确定性以及创建语言实现的平台数量,JavaScript做出了合理的妥协。我必须说,直接比较JavaScript和其他语言非常困难。关键是JavaScript生态系统。同时有许多同一种语言的口译员对于其他语言来说是完全不典型的。另外,这是JavaScript的主要优势之一。此外,不能不说这些是与该语言本身所发生的计划完全不同的现象。随着时间的流逝,JavaScript发生了变化,其中出现了很多好东西

dStdlib或epsilon?


我认为,在大多数情况下,在实践中值得使用暗示使用epsilon值的方法。stdlib库是一个很棒的强大工具,但是在项目中包括一个额外的用于数学计算的库的代价可能会很高。而且在大多数情况下,计算结果中的微小差异对于应用而言并不重要。

摘要


在这里,我想从以上内容得出结论并分享一些想法。

  1. , , , . . — « ». , , , Math.sin , , , . , , , , , . , , , .
  2. . , , Node.js, , , simply-statistics. , , , , . — .
  3. , . V8, , . , . , , , .

亲爱的读者们!升级到新版本的Node.js时,您是否遇到过有关计算结果更改的问题?


All Articles