`Math` 对象

`Math` 对象

本文介绍了 Math 对象。

从基本用法到常见实际模式,以及常见陷阱及其对策,逐步进行讲解。

YouTube Video

Math 对象

JavaScript 的 Math 对象提供了一套内置的数值计算工具。

常量

Math 中定义的常量在处理高精度数值时非常有用。

以下代码展示了一些典型的常量。

1// Show common Math constants
2console.log("Math.PI:", Math.PI);         // 3.141592653589793
3console.log("Math.E:", Math.E);           // Euler's number
4console.log("Math.LN2:", Math.LN2);       // Natural log of 2
5console.log("Math.SQRT2:", Math.SQRT2);   // Square root of 2
  • 这些常量可以直接用于三角函数、对数转换、归一化等场景。例如,Math.PI 用于将角度转换为弧度。

基本取整和绝对值(abs,floor,ceil,round,trunc)

取整和处理符号经常用到,因此要正确理解 Math.floorMath.ceilMath.round 之间的区别非常重要。特别是在处理负数时,结果可能与直觉不同,所以必须小心每种取整规则的工作方式,并合理使用。

以下示例说明了不同取整函数之间的区别。

 1// Examples of rounding and absolute value functions
 2const pos = 3.7;
 3const neg = -3.7;
 4
 5// --- floor ---
 6console.log("Math.floor(3.7):", Math.floor(pos));  // 3
 7console.log("Math.floor(-3.7):", Math.floor(neg)); // -4
 8// (rounds toward smaller value)
 9
10// --- ceil ---
11console.log("Math.ceil(3.7):", Math.ceil(pos));  // 4
12console.log("Math.ceil(-3.7):", Math.ceil(neg)); // -3
13// (rounds toward larger value)
14
15// --- round ---
16console.log("Math.round(3.5):", Math.round(3.5));   // 4
17// (.5 goes up for positive numbers)
18console.log("Math.round(-3.5):", Math.round(-3.5)); // -3
19// (.5 moves toward zero for negative numbers)
20
21// --- trunc ---
22console.log("Math.trunc(3.7):", Math.trunc(pos));  // 3
23console.log("Math.trunc(-3.7):", Math.trunc(neg)); // -3
24// (fraction removed toward zero)
25
26// --- abs ---
27console.log("Math.abs(-3.7):", Math.abs(neg)); // 3.7
  • Math.floor 对负数向更小的方向取整,所以 -3.7 变成了 -4
  • Math.ceil 对负数向更大的方向取整,所以 -3.7 变成了 -3
  • Math.round 对于正数 .5 向上取整,而对于负数则向零取整。
  • 负数时的结果可能与直觉不同,因此要清楚了解各函数取整的方向非常重要。
  • 取整函数应根据具体用途选择。例如,索引计算用 Math.floor,上界取整用 Math.ceil,仅舍弃小数部分时用 Math.trunc

乘方、指数和开方运算(pow,**,sqrt,cbrt,hypot)

幂运算和平方根可以使用 Math.pow** 运算符来完成。Math.hypot 能安全计算平方和的平方根(距离)。

以下是乘方和 hypot 的示例。Math.hypot 可缓解溢出和下溢的影响。

1// Power, root and hypot examples
2console.log("2 ** 10:", 2 ** 10);                // 1024
3console.log("Math.pow(2, 10):", Math.pow(2, 10));// 1024
4console.log("Math.sqrt(16):", Math.sqrt(16));    // 4
5console.log("Math.cbrt(27):", Math.cbrt(27));    // 3
6console.log("Math.hypot(3, 4):", Math.hypot(3, 4));// 5 (3-4-5 triangle)
  • Math.hypot 在处理三角形边长或多维向量长度时很有用。

指数和对数函数(exp,log,log10,log2)

指数和对数常用于概率、指数衰减和缩放中。

下面是 exp 和对数的基本用法示例。根据对数底数选择合适函数。

1// Exponential and logarithm examples
2console.log("Math.exp(1):", Math.exp(1));     // e^1
3console.log("Math.log(Math.E):", Math.log(Math.E)); // natural log, 1
4console.log("Math.log10(100):", Math.log10(100));   // 2
5console.log("Math.log2(8):", Math.log2(8));         // 3
  • 在统计学和微分方程中,自然对数(Math.log)是标准用法。常用对数和以2为底的对数也要根据实际场景选择。

三角函数(sin,cos,tan,asin,acos,atan2)

三角函数在角度计算、旋转、坐标变换中必不可少。Math.atan2(y, x) 可正确考虑象限,方便计算角度。

下面是 atan2 的基本示例和用法。角度必须以弧度处理。

 1// Trigonometry examples (angles in radians)
 2const degToRad = deg => deg * (Math.PI / 180);
 3const radToDeg = rad => rad * (180 / Math.PI);
 4
 5console.log("Math.sin(PI/2):", Math.sin(Math.PI / 2));          // 1
 6console.log("Math.cos(0):", Math.cos(0));                      // 1
 7console.log("Math.tan(Math.PI / 4):", Math.tan(Math.PI / 4));  // ~1
 8
 9// Using atan2 to compute angle of vector (x, y)
10const x = -1, y = 1;
11const angle = Math.atan2(y, x); // returns angle in radians taking quadrant into account
12console.log("atan2(1, -1) in degrees:", radToDeg(angle));      // 135
  • atan2 能安全判断向量角度,适用于旋转和朝向计算。

随机数生成(Math.random 及实际用法)

Math.random() 返回范围在 [0, 1) 的均匀分布随机数。可以根据需要将其转换或扩展到整数区间或正态分布等。

以下是随机数工具函数的示例。如果需要密码级安全的随机数,请使用 crypto.getRandomValues

 1// Random utilities using Math.random
 2
 3// Get integer in [min, max] inclusive
 4function randomInt(min, max) {
 5  // Returns integer between min and max (inclusive)
 6  return Math.floor(Math.random() * (max - min + 1)) + min;
 7}
 8console.log("randomInt(1, 6):", randomInt(1, 6)); // dice roll example
 9
10// Get float in [min, max)
11function randomFloat(min, max) {
12  return Math.random() * (max - min) + min;
13}
14console.log("randomFloat(0, 5):", randomFloat(0, 5));
15
16// Fisher-Yates shuffle
17function shuffle(array) {
18  for (let i = array.length - 1; i > 0; i--) {
19    const j = Math.floor(Math.random() * (i + 1));
20    // swap array[i] and array[j]
21    [array[i], array[j]] = [array[j], array[i]];
22  }
23  return array;
24}
25console.log("shuffle([1,2,3,4,5]):", shuffle([1,2,3,4,5]));
  • Math.random 适合一般用途,但用于游戏回放或加密时需考虑可复现性和安全性措施。

实用辅助函数(clamp,lerp,mapRange,角度归一化)

编写常用的小型数学工具函数非常有帮助。

下面是常用工具函数的实现示例。

 1// Utility functions: clamp, lerp, mapRange, normalizeAngle
 2
 3// Clamp value between min and max
 4function clamp(v, min, max) {
 5  return Math.max(min, Math.min(max, v));
 6}
 7console.log("clamp(10, 0, 5):", clamp(10, 0, 5)); // 5
 8
 9// Linear interpolation: t in [0,1]
10function lerp(a, b, t) {
11  return a + (b - a) * t;
12}
13console.log("lerp(0, 10, 0.25):", lerp(0, 10, 0.25)); // 2.5
14
15// Map value from one range to another
16function mapRange(v, inMin, inMax, outMin, outMax) {
17  const t = (v - inMin) / (inMax - inMin);
18  return lerp(outMin, outMax, t);
19}
20console.log("mapRange(5, 0, 10, 0, 100):", mapRange(5, 0, 10, 0, 100));
21// -> 50
22
23// Normalize angle to [-PI, PI)
24function normalizeAngle(rad) {
25  return Math.atan2(Math.sin(rad), Math.cos(rad));
26}
27console.log("normalizeAngle(3*PI):", normalizeAngle(3 * Math.PI));
  • 这些代码收集了常用于数值计算的小型工具函数,如限制数值、插值、区间映射和角度归一化。clamp 将数值限制在指定范围,lerpmapRange 可进行平滑插值或区间转换。此外,normalizeAngle 总是将角度归一到 [-π, π) 区间,以稳定旋转计算。
  • 这些辅助函数在图形、游戏和交互编程中经常使用。normalizeAngle 对于比较角度差异和插值很重要。

浮点与取整的注意事项(精度与比较)

JavaScript 的数字采用 IEEE-754 双精度浮点数(64 位),因此比较和取整时需要注意。例如,0.1 + 0.2 !== 0.3

下面是处理取整误差的示例。应采用能容忍微小误差的比较方法,或使用合适的取整函数。

1// Floating point precision example and safe equality
2console.log("0.1 + 0.2 === 0.3 :", 0.1 + 0.2 === 0.3); // false
3
4function nearlyEqual(a, b, eps = 1e-12) {
5  return Math.abs(a - b) <= eps;
6}
7console.log("nearlyEqual(0.1+0.2, 0.3):", nearlyEqual(0.1 + 0.2, 0.3));
8// -> true
  • 要进行更稳健的数值相等性检查,请使用绝对误差(eps)或相对误差。

性能提示

Math 的各类函数已经过原生优化,通常比手写的等价逻辑更快。如果某个计算(如 Math.PI / 180)在循环中反复执行,建议提前赋值给变量以减少多余开销。

下面是通过在循环外部创建常量实现角度到弧度转换的示例。

1// Cache conversion factor for performance
2const DEG_TO_RAD = Math.PI / 180;
3const RAD_TO_DEG = 180 / Math.PI;
4
5for (let deg = 0; deg < 360; deg += 10) {
6  // Use cached constant instead of computing each time
7  const rad = deg * DEG_TO_RAD;
8  // ... do work with rad
9}
  • 在做优化前最好先用性能分析工具确认真正的热点。
  • 频繁使用的常量建议缓存,提高效率。

兼容性与 polyfill

Math 对象存在已久,但某些函数如 Math.cbrtMath.log10Math.log2Math.hypot 在旧环境下可能不受支持。如有需要请准备一个简单的 polyfill。

以下是 Math.log2 的简单 polyfill 示例。

1// Polyfill for Math.log2 if needed
2if (!Math.log2) {
3  Math.log2 = function(x) {
4    return Math.log(x) / Math.LN2;
5  };
6}
7console.log("Math.log2(8):", Math.log2(8));

许多环境已经支持这些函数,但需要时请检查兼容性。

根据项目目标环境检查浏览器的支持情况。

实际案例:简单物理模拟中的定步长更新

最后,以下是结合多个 Math 函数的实际例子。这是一个用于更新位置、速度和角度控制的简单示例。

这是物理引擎极度简化的模型。

 1// Clamp value between min and max
 2function clamp(v, min, max) {
 3  return Math.max(min, Math.min(max, v));
 4}
 5
 6// Normalize angle to [-PI, PI)
 7function normalizeAngle(rad) {
 8  return Math.atan2(Math.sin(rad), Math.cos(rad));
 9}
10
11// Simple physics step example using Math utilities
12function step(state, dt) {
13  // state: { x, y, vx, vy, angle, angularVelocity }
14  // dt: time delta in seconds
15  // Integrate linear motion
16  state.x += state.vx * dt;
17  state.y += state.vy * dt;
18
19  // Integrate angular motion and normalize angle
20  state.angle = normalizeAngle(state.angle + state.angularVelocity * dt);
21
22  // Keep position within bounds [0, width] x [0, height]
23  state.x = clamp(state.x, 0, state.width);
24  state.y = clamp(state.y, 0, state.height);
25  return state;
26}
27
28const state = { x: 10, y: 10, vx: 2, vy: 0.5, angle: 0, angularVelocity: 0.1, width: 100, height: 100 };
29console.log("before:", state);
30step(state, 0.016); // simulate ~1 frame at 60fps
31console.log("after:", state);
  • 通过这样组合使用 Math 辅助函数,可以让仿真和动画逻辑更简洁。

总结

  • Math 覆盖了从常量、基础函数到高级函数,为数值处理提供了基础。
  • 根据用途选择相应函数,如随机数、三角函数、指数/对数或取整,并为实际应用编写如 clamplerp 等小工具函数。
  • 注意浮点精度及目标环境的支持情况,如有必要要准备误差容忍机制或 polyfill。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video