冷門函式之Math.hypot

語言: CN / TW / HK

最近我在做資料視覺化和圖形影象渲染相關的技術研究,而圖形影象涉及到許多幾何圖形的向量運算,我們求一個向量的模,需要計算每個座標分量的平方和,然後開根號。

一般來說,我們使用類似如下的程式碼:

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 中討論。