Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

冷门函数之Math.hypot #3

Open
akira-cn opened this issue Jul 23, 2019 · 5 comments
Open

冷门函数之Math.hypot #3

akira-cn opened this issue Jul 23, 2019 · 5 comments

Comments

@akira-cn
Copy link
Owner

akira-cn commented Jul 23, 2019

最近我在做数据可视化和图形图像渲染相关的技术研究,而图形图像涉及到许多几何图形的向量运算,我们求一个向量的模,需要计算每个坐标分量的平方和,然后开根号。

一般来说,我们使用类似如下的代码:

function length(v) {
  const dimension = v.length;
  let ret = 0;
  for(let i = 0; i < dimension; i++) {
    ret += v[i] ** 2;
  }
  return Math.sqrt(ret);
}

const v1 = [3, 4];
console.log(length(v1)); // 5

但是,在ES2015中,提供了Math.hypot方法,来计算参数的平方根。

const v1 = [3, 4];
console.log(Math.hypot(...v1)); // 5

目前除了IE外,其他的浏览器都支持这个方法。

👉🏻 冷知识 —— Math.hypot的坑:你可能会认为Math.hypot是内置函数,它的性能应该要比用Math.sqrt好,但是实际上并不是这样。我们写一段测试脚本:

function length(v) {
  const dimension = v.length;
  let ret = 0;
  for(let i = 0; i < dimension; i++) {
    ret += v[i] ** 2;
  }
  return Math.sqrt(ret);
}
// Math.hypot
const v1 = Array(10).fill(0).map(() => Math.random());
const result = Math.hypot(...v1);
//Math.sqrt
const v1 = Array(10).fill(0).map(() => Math.random());
const result = length(v1);

用jsperf进行测试,结果令人惊讶,在我的Chrome 75下,使用Math.hypot竟然比使用Math.sqrt慢40%左右。

也许是我使用的姿势不对?但是kangax大神早在14年的时候就写过更多case来测试:

结果。。。

所以一个ES2015开始支持的数学函数,又是很常用的运算,却没有多少人使用,也没有多少人介绍它,也是有理由的 —— 性能捉急。

不过hypot性能不如sqrt,也并不意味着它完全不可用。

在某些情况下,使用hypot能得到sqrt无法得到的结果。比如特别大的数或者特别小的数值下:

Math.hypot(2e200, 3e200); // 3.6055512754639894e+200
Math.sqrt(2e200 ** 2 + 3e200 ** 2); // Infinity

Math.hypot(2e-200, 3e-200); // 3.6055512754639893e-200
Math.sqrt(2e-200 ** 2 + 3e-200 ** 2); // 0

所以,如果我们要处理很大的数或很小的数时,可以使用Math.hypot,在一般情况下,还是使用Math.sqrt为好。例如:

const result = Math.sqrt(x ** 2 + y ** 2);
if(!Number.isFinite(result)) result = Math.hypot(x, y);

关于Math.hypot还有什么问题,可以在issue中讨论。

@akira-cn akira-cn changed the title 冷门Math函数之hypot 冷门函数之Math.hypot Jul 23, 2019
@liuhanqu
Copy link

It is jsperf, not jspref.

@akira-cn
Copy link
Owner Author

是的,我改一下

@hax
Copy link

hax commented Jul 26, 2019

建议hypot先做个名词解释。『弦』?

hypot 性能相对差是各个语言普遍存在的情况。我觉得可以把hypot的motivation再描述的清楚一点(即防止overflow/underflow)。这也能解释为什么尽管性能差,所有语言还都是要加上它。

我建议这里还可以实际写一个polyfill。可以用wikipeida上的算法(还挺简单的,一看就懂),或用mdn上的。mdn上有两个,一个是普通naive的sqrt,一个是确保不会overflow/underflow的算法。我们可以测一下后者的性能。

所以,如果我们要处理很大的数或很小的数时,可以使用Math.hypot,在一般情况下,还是使用Math.sqrt为好。

这一点我不太赞同,我认为应该反过来。在一般情况下(你不清楚你要处理的数字的range),应该用 Math.hypot,只有你确切的知道这里不可能有 overflow/underflow 或我们可以容忍一定的精度丢失,才用 Math.sqrt。

这里还有一点是,尽管现在 Math.hypot 的性能比较差,但这是引擎可以优化的。(比如好像ff里的性能就不是特别差。)在其他语言里我们也看到会在新的版本里改算法以提升性能。js引擎当然也可以。比如从其他语言移植算法实现。有空的话,可以拉几个算法用纯js实现下看看说不定比现在原生的还快。😜

@akira-cn
Copy link
Owner Author

我认为应该反过来。在一般情况下(你不清楚你要处理的数字的range),应该用 Math.hypot,只有你确切的知道这里不可能有 overflow/underflow 或我们可以容忍一定的精度丢失,才用 Math.sqrt。

嗯,这个要看场景的,比如我做基于webgl的renderer,要处理很多向量和几何图形,中间有大量求向量模或求距离的运算,还是很care这个性能的。这里倒可以容忍精度丢失。不过如果说要考虑精度,的确是可以自己实现一下看看

@q269384828
Copy link

哎呀,我现在才知道** 2是平方的意思。。。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants