Learning Objectives
- 使用 Math 物件的常數與方法:PI、max、min、abs、sqrt、pow
- 分辨 floor、ceil、round、trunc 四種取整數的方法,特別是負數的結果
- 用 Math.random 產生指定範圍的亂數整數,並說明亂數是偽隨機
- 用角度換算弧度,使用 sin、cos、atan2
- 用 cos、sin 把元素排成一圈,並用 % 實作環狀佇列
Math:內建的數學工具箱
Math 是 JavaScript 內建的物件,收集了常用的數學常數與函式。它和 Date、Array 不一樣:
不是建構函式,不能 new Math();所有屬性和方法都直接用 Math.名稱 取用。
| 常數/方法 | 意思 | 例子 |
|---|---|---|
Math.PI、Math.E |
圓周率 π、自然常數 e | 3.141592653589793 |
Math.max(a, b, …)、Math.min(…) |
最大、最小值 | Math.max(3, 9, 1) → 9 |
Math.abs(x) |
絕對值 | Math.abs(-8) → 8 |
Math.sqrt(x) |
平方根 | Math.sqrt(81) → 9 |
Math.pow(x, y) |
x 的 y 次方,和 x ** y 相同 |
Math.pow(2, 10) → 1024 |
Math.floor(x) |
往下取整數(往負無限大) | Math.floor(-4.7) → -5 |
Math.ceil(x) |
往上取整數(往正無限大) | Math.ceil(-4.7) → -4 |
Math.round(x) |
四捨五入;剛好 .5 時往正無限大的方向 | Math.round(-2.5) → -2 |
Math.trunc(x) |
直接去掉小數部分(往 0) | Math.trunc(-4.7) → -4 |
Math.max 收的是一個一個的引數,不是陣列:要找陣列的最大值,寫 Math.max(...arr) 先展開。
要顯示到小數第幾位,用數字的 toFixed(位數),它回傳的是字串。
取整數:floor、ceil、round、trunc
每一列依序是:原本的數、floor、ceil、round、trunc。正數時 floor 和 trunc 一樣,看到 -4.7 那一列就不一樣了;也注意 -2.5 四捨五入的結果。
亂數:Math.random
Math.random() 回傳一個 0(含)到 1(不含) 之間的小數。電腦用數學公式算出「看起來隨機」的數字,
這叫偽隨機(pseudorandom);一般的遊戲、抽籤、洗牌夠用,但不能用在密碼、驗證碼這類需要安全性的地方
(那要用 crypto.getRandomValues())。
要得到 min 到 max 之間的整數(兩端都含),步驟是:
Math.random() // 0 ≤ r < 1
Math.random() * (max - min + 1) // 0 ≤ … < 範圍內整數的個數
Math.floor(…) // 0、1、…、max - min
Math.floor(…) + min // min、…、max
用 Math.floor 而不是 Math.round:round 會讓兩端的數字出現機率只有中間的一半。
這一課的逐步執行動畫為了可以重現,亂數使用固定的種子,所以每次播放、每個人看到的結果都一樣。 在真正的瀏覽器裡,
Math.random()每次執行的結果都不同。
骰子、擲硬幣與隨機挑選
randomInt(1, 6) 被呼叫 8 次,每次看右邊框架的回傳值。注意結果一定在 1 到 6 之間:Math.random() * 6 是 0 到 5.99…,floor 後是 0 到 5,再加 1。
想一想:取整數與亂數
下面的程式會印出什麼?
console.log(Math.floor(-3.2), Math.trunc(-3.2), Math.ceil(3.2));
要產生 10 到 20 之間的整數(10 和 20 都可能出現),哪一個寫法正確?
下面的程式會印出什麼?
const scores = [72, 95, 88];
console.log(Math.max(...scores), Math.min(scores));
三角函數:先把角度換成弧度
做動畫、遊戲、畫圖表時,常需要「往某個角度走一段距離」,這時就會用到三角函數。
Math.sin、Math.cos、Math.tan 的參數是弧度(radian),不是角度:
const rad = deg * Math.PI / 180; // 180° 等於 π 弧度
Math.sin(90 * Math.PI / 180); // 1
| 方法 | 意思 |
|---|---|
Math.sin(x)、Math.cos(x)、Math.tan(x) |
正弦、餘弦、正切,x 是弧度 |
Math.asin(x)、Math.acos(x) |
反正弦、反餘弦,x 超出 -1~1 時得到 NaN |
Math.atan(x) |
反正切,結果在 -π/2 到 π/2 之間 |
Math.atan2(y, x) |
從原點到點 (x, y) 的角度(弧度),y 在前、x 在後 |
浮點數有誤差:Math.sin(Math.PI) 不是剛好 0,而是 1.2246467991473532e-16(非常接近 0)。
要顯示結果時用 toFixed 控制位數,比較兩個小數時不要直接用 ===。
常見角度的 sin 與 cos
每一圈先把角度換成弧度,再算 sin 與 cos,用 toFixed(3) 顯示到小數第三位。最後兩行看浮點數誤差與 atan2。
把元素排成一圈
圓心在 (cx, cy)、半徑是 r 的圓上,角度 θ 的那一點座標是:
x = cx + r * Math.cos(θ);
y = cy + r * Math.sin(θ);
要把 N 個點平均排在圓上,第 i 個點的角度是 2 * Math.PI * i / N(一圈是 2π,平均分成 N 份)。
網頁的座標和數學課的座標有一點不同:y 軸往下是正的。所以角度 0 在圓心的右邊,角度增加時是順時針轉。
想讓第一個點在正上方,把每個角度都減掉 Math.PI / 2(90°)。
算出 4 個點的座標
圓心 (150, 150)、半徑 100,四個點的角度依序是 0、π/2、π、3π/2。注意第二個點的 y 是 250:網頁的 y 軸往下。
另一種「環狀」:環狀佇列
投影片的「環狀排列」還介紹了一種資料結構:佇列(queue)是先進先出(FIFO,First In First Out)的集合,
像排隊一樣,只能從後端(rear)加入、從前端(front)取出。用陣列的 push 和 shift 就能做出簡單的佇列:
const queue = [];
queue.push('A'); // 從後端加入
queue.shift(); // 從前端取出 'A'
環狀佇列(circular queue)使用固定大小的陣列,頭尾相連:索引走到最後一格之後回到 0。
做法是每次移動索引時用 % 取餘數:rear = (rear + 1) % size。這樣取出元素之後空出來的格子可以重複使用,不必移動其他元素。
要分辨佇列「空」還是「滿」有兩種常見作法:投影片的作法是永遠空一格(front 與 rear 相同時是空,rear 的下一格是 front 時是滿,
所以最多只能放 size - 1 個);下面的範例另外用 count 記錄個數,四格都能用。
環狀佇列:用 % 繞回開頭
大小為 4 的佇列先放入 A、B、C、D(E 放不進去),取出 A、B 之後再放入 F、G:看第 14 行 rear 從 3 變成 0,F、G 放進了前面空出來的格子。
想一想:三角函數與環狀排列
要計算 60° 的 sin 值,哪一個寫法正確?
下面的程式會印出什麼?
const angle = Math.PI / 2;
const x = Math.round(100 + 50 * Math.cos(angle));
const y = Math.round(100 + 50 * Math.sin(angle));
console.log(x, y);
下面的程式會印出什麼?
const size = 5;
let i = 3;
for (let k = 0; k < 4; k++) {
i = (i + 1) % size;
}
console.log(i);
實作:圓上的點
完成 pointOnCircle(cx, cy, r, deg):給圓心、半徑與角度,回傳物件 { x, y },兩個座標都四捨五入成整數。完成後會印出 0°、90°、180°、270° 四個點。
重點精華回顧
Math是內建物件,不能new;常數Math.PI、Math.E,方法max、min、abs、sqrt、pow直接呼叫。- 取整數:
floor往下、ceil往上、round四捨五入、trunc去掉小數;負數時結果各不相同。 Math.random()是 0(含)到 1(不含)的偽隨機小數;min 到 max 的整數用Math.floor(Math.random() * (max - min + 1)) + min。- 三角函數的參數是弧度:
角度 * Math.PI / 180;浮點數有誤差,顯示時用toFixed。 - 圓上的點:
x = cx + r * cos(θ)、y = cy + r * sin(θ);N 個點平均分配,角度是2π * i / N;網頁的 y 軸往下。 - 佇列是先進先出;環狀佇列用
(index + 1) % size讓索引繞回開頭,重複使用空出來的格子。