导读:本期聚焦于阳光创作的《JavaScript中如何计算两个数的最小公倍数(LCM)?三种方法详解》,敬请观看详情。为什么JavaScript没有内置的最小公倍数函数?当我们需要处理分数运算、时间周期计算或数学问题时,LCM就成了绕不开的知识点。本文从最大公约数与最小公倍数的关系讲起,介绍辗转相除法求GCD的原理,再推导出LCM的经典计算公式,并用三种不同的实现方式编写可复用的代码,包括原生函数封装、递归写法以及一次计算多个数的最小公倍数的扩展版本。文中还分析了大数相乘可能导致的精度溢出问题,给出先除后乘的优化技巧,并对比各方案的性能与适用场景,帮助你彻底掌握这一常见算法。

做前端开发时,你可能遇到过这样的需求:两个动画周期分别是8秒和12秒,想知道它们多久后会同时回到起点;或者计算分数加法时需要找公分母。这些问题的本质都是求最小公倍数。JavaScript的Math对象提供了很多数学函数,却没有内置LCM方法,所以我们需要自己实现。这篇文章带你从原理到代码,完整掌握最小公倍数的计算方法。

JavaScript中如何计算两个数的最小公倍数(LCM)?三种方法详解

一、理解最小公倍数与最大公约数的关系

在写代码之前,先弄清楚数学原理。两个整数a和b的最小公倍数,是指能同时被a和b整除的最小正整数。比如4和6的公倍数有12、24、36等,其中最小的是12。

最小公倍数有一个非常重要的性质:两个数的乘积等于它们的最大公约数(GCD)与最小公倍数的乘积。用公式表达就是 a × b = GCD(a, b) × LCM(a, b)。由此可以推导出LCM的计算公式:

// LCM(a, b) = (a * b) / GCD(a, b)
function lcm(a, b) {
  return (a * b) / gcd(a, b);
}

这样一来,求LCM的问题就转化成了求GCD的问题。而求GCD有一个经典的算法——辗转相除法(欧几里得算法),它基于这样一个事实:两个数的最大公约数等于其中较小的数与两数相除余数的最大公约数。不断用除数去除被除数取余,直到余数为0,此时的除数就是最大公约数。

举例说明:求48和18的GCD。48除以18余12,接着18除以12余6,再12除以6余0,所以GCD是6。整个算法只用了三次除法,效率远高于穷举法,即使数字很大也能快速得出结果。

二、用辗转相除法实现LCM函数

下面是完整的实现代码。GCD部分使用循环写法,避免递归带来的函数调用开销:

// 求最大公约数(辗转相除法)
function gcd(a, b) {
  while (b !== 0) {
    const temp = b;
    b = a % b;
    a = temp;
  }
  return a;
}

// 求最小公倍数
function lcm(a, b) {
  return (a * b) / gcd(a, b);
}

// 测试
console.log(lcm(4, 6));    // 12
console.log(lcm(8, 12));   // 24
console.log(lcm(7, 13));   // 91(互质的两个数,LCM就是它们的乘积)

这段代码逻辑清晰,但有一个隐藏的坑需要注意:当a和b都很大时,a * b的结果可能超出Number.MAX_SAFE_INTEGER(即2的53次方减1),导致精度丢失。比如计算两个很大的质数的LCM时,乘法运算的结果已经不是精确值了。

解决方法是调整运算顺序,先做除法再做乘法。由于GCD一定能整除a,先除后乘不会产生小数,同时把中间结果控制在较小范围内:

// 优化版:先除后乘,避免大数相乘溢出
function lcmSafe(a, b) {
  return (a / gcd(a, b)) * b;
}

// 测试大数场景
console.log(lcmSafe(1000000007, 999999937)); // 结果依然精确

另外,如果输入可能包含0或负数,还需要做边界处理。数学上规定任何数与0的LCM为0,负数则通常先取绝对值:

// 带边界处理的健壮版本
function lcmRobust(a, b) {
  a = Math.abs(a);
  b = Math.abs(b);
  if (a === 0 || b === 0) return 0;
  return (a / gcd(a, b)) * b;
}

三、扩展:一次计算多个数的最小公倍数

实际业务中经常需要求多个数的LCM,比如找出3个、5个甚至更多个数的公共周期。数学上有个重要性质:LCM具有结合性,即 LCM(a, b, c) = LCM(LCM(a, b), c)。利用这个性质,可以用reduce不断累积计算:

// 计算多个数的最小公倍数
function lcmOfArray(arr) {
  return arr.reduce((acc, cur) => lcmRobust(acc, cur));
}

// 示例:求动画周期的同步时间点
const periods = [8, 12, 20];
console.log(lcmOfArray(periods)); // 120,三个动画每120秒同步一次

reduce的初始值是数组第一个元素,然后依次把当前累积结果与下一个数求LCM。由于每次都先除以GCD再相乘,中间结果始终保持在能整除的最小范围内,整个累积过程不会出现精度问题。

如果习惯函数式风格,也可以把GCD写成递归形式,配合箭头函数让代码更紧凑:

// 递归写法配合箭头函数
const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
const lcm = (a, b) => a / gcd(a, b) * b;

const lcmAll = (...nums) => nums.reduce((acc, n) => lcm(acc, n));

console.log(lcmAll(4, 6, 8, 14)); // 168

递归版GCD代码量最少,可读性也不错。不过当数字规模特别大时,递归深度理论上不会超过对数量级,性能上和循环版几乎没差别,选择哪种写法主要看团队的代码风格偏好。

四、常见问题与性能对比

有人可能想到用穷举法:从较大的数开始逐个尝试,找到第一个能同时整除两个数的值。这种写法虽然容易理解,但时间复杂度是O(a×b/GCD),当两个数互质且很大时,循环次数会呈线性增长,性能完全无法和辗转相除法的O(log min(a, b))相比。生产代码中应始终优先选择辗转相除法。

还有一种思路是分别对两个数做质因数分解,取每个质因数的最高次幂相乘。这个方法在数学上成立,但实现质因数分解本身就需要大量计算,代码复杂度也更高,除非题目明确要求展示分解过程,否则不推荐。

总结一下推荐做法:用循环版辗转相除法求GCD,采用先除后乘的顺序计算LCM,对输入做绝对值和零值处理,最后用reduce扩展到多数场景。这一套组合拳既保证了计算精度,又兼顾了性能和代码健壮性,可以直接复制到项目工具库中使用。

JavaScript最小公倍数LCM算法修改时间:2026-09-15 13:38:32

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260915/57303.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。