JavaScript 中缺失的数学工具:Sum、Product、Prime 等实用函数实现

作者:函数茶馆时间:2026-10-30 21:25:10浏览:380

JavaScript 原生 Math 对象缺少许多基础数学操作。本文详细分享了在 JavaScript 中实现求和、乘积、奇偶判断、三角数、阶乘、因子分解、素数检测、最大公约数及最小公倍数等函数的方法,涵盖 for 循环与 reduce、递归等多种实现路径,并提供实际应用场景代码示例。

JavaScript 的 Math 对象提供了一些基础且强大的数学运算功能,适用于 Web 开发场景,但它并未涵盖其他多数编程语言(如 Haskell)所具备的大量高级数学操作。

下文将列出各个数学操作的快速链接:

  • 求和(Sum)
  • 乘积(Product)
  • 奇数与偶数判断
  • 三角数(Triangle Number)
  • 阶乘(Factorial)
  • 因子分解(Factors)
  • 素数判断(isPrime)
  • 最大公约数(Greatest Common Divisor)
  • 最小公倍数(Lowest Common Multiple)

1. 求和(Sum)

你可能记得在数学课上,“sum”即“add”的同义词。例如,对数字 1、2 和 3 求和,实际上就是计算 1 + 2 + 3 的结果。

我们的求和函数旨在对数组中的所有数值进行累加。

实现该函数有两种主要方式:使用 for 循环,或者利用 reduce 函数。若需复习 reduce 函数的用法,可参考关于 JavaScript 中 map() 和 reduce() 的文档。

使用 for 循环的实现方式:

function sum(array){
    let total = 0
    for(let count = 0; count < array.length; count++){
        total = total + array[count]
    }
    return total
}

使用 reduce 函数的实现方式:

function sum(array){
    return array.reduce((sum, number) => sum + number, 0)
}

这两种函数在逻辑上完全一致(reduce 本质上是一个内置的 for 循环),在输入相同数组时返回结果相同,但 reduce 函数的代码更为简洁。

示例如下:

sum([1,2,3,4]) === 10 // 1 + 2 + 3 + 4
sum([2,4,6,8]) === 20 // 2 + 4 + 6 + 8

能够对一组数字求和可能是JavaScript Math对象中最有用且最需要的“缺失”数学操作。再次强调,求和函数可以作为一个很好的检查工具。例如,在数独游戏中,我们可以通过检查列或行的总和是否为45(1 + 2 + 3 + 4 +…+ 9)来检查用户是否没有重复的数字。该函数在在线购物应用程序中也非常适用,如果我们想要计算总账单,假设所有价格都存储在一个数组中。

以下是在购物应用示例中调用该函数的代码:

const prices = [2.80, 6.10, 1.50, 1.00, 8.99, 2.99]
function totalCost(prices){
    return prices.reduce((sum, item) => sum + item, 0)
}

2. 乘积(Product)

我们的乘积函数工作方式与求和函数类似,区别在于它将列表中的所有数字相乘。

同样地,我们可以使用与求和函数几乎相同的 for 循环来实现:

function product(array){
    let total = 1
    for(let count = 0; count < array.length; count++){
        total = total * array[count]
    }
    return total
}

请注意,此处使用 1 来初始化 total 变量,而非 0,否则最终乘积将始终为 0。

在此场景下,reduce 函数依然适用,且仍是更简洁的代码编写方式:

function product(array){
    return array.reduce((total, num) => total*num, 1)
}

以下是一些示例代码:

product([2,5,8,6]) === 480 // 2 x 5 x 8 x 6
product([3,7,10,2]) === 420 // 3 x 7 x 10 x 2

该函数的用途可能乍看之下不明显,但在涉及多次转换的计算中非常高效。例如,若想计算十个苹果包装袋(每袋一公斤,售价 1.50 RMB)的美元价格,无需进行繁琐的手动乘法运算,只需将所有相关数值存储于数组中,并调用刚才编写的乘积函数即可。

数组的一种示例格式如下:

const pricePerKg = 1.50
const numberOfKg = 10
const conversionRate = 1.16
const conversion = [1.50, 10, 1.16]
const USprice = product([pricePerKg,numberOfKg,conversionRate])

3. 奇数和偶数

这些函数接受一个数字作为参数(可以是数组长度),并根据该数字是奇数还是偶数返回布尔值 true 或 false。

判断偶数的条件是数字必须能被 2 整除;而奇数则相反,不能被 2 整除。这是函数的核心逻辑。

例如,Haskell 语言中内置了这些函数,使得代码编写更为简便,可以直接编写如下代码:

even 29
<< false
odd 29
<< true

另一方面,Ruby 将这些功能作为方法提供,这样编写依然更加便捷:

29.even?
<< false
29.odd?
<< true

在 JavaScript 中,编写这些函数的最简单方法是使用取余运算符 %。它返回一个数除以另一个数的余数。例如:

11 % 3 === 2 // 11 divide 3 === 3 remainder 2

以下是偶数函数的一个示例:

function even(number){
    return number % 2 === 0
}

如我们所见,该函数接受一个数字作为参数,并根据条件返回布尔值:

number % 2 === 0

当该数被 2 整除时,若余数等于零,则说明其可被 2 整除,函数将返回 true。例如:

even(6) === true
even (9) === false

以下是奇数函数的一个示例:

function odd(number){
    return number % 2 !== 0
}

这两个函数非常相似:均接受一个数字作为参数,并根据条件返回布尔值:

number % 2 !== 0

若将数字除以 2 的余数不等于零,则该数为奇数,函数将返回 true。例如:

odd(7) === true
odd(114) === false

检查数字是奇数还是偶数非常重要且实现简单。起初可能看似不重要,但它可以作为一种优秀的输入验证技术,例如验证数组长度,或通过检查两个玩家游戏的胜者来判断胜负。你可以追踪已进行的回合数,若回合数为奇数,则玩家 1 获胜;若为偶数,则玩家 2 获胜——假设第一回合计为 1。

这两个函数可以互换使用,我们可能只需其中一个。然而,同时拥有这两个函数有助于在代码中更清晰地追踪真值和假值逻辑,特别是在大型代码块中。

以下是上述示例的代码实现:

function checkWinner(gamesPlayed){
    let winner
    if(odd(gamesPlayed)){
        winner = "player1"
    }
    else{
        winner = "player2"
    }
    return winner
}

4. 三角数(Triangle Number)

三角数听起来比实际复杂得多。它们仅仅是某个数之前所有整数的和。

例如,第五个三角数为:5 + 4 + 3 + 2 + 1 = 15。

这与我们之前提到的数独例子相关。我们要检查所有数字是否唯一,可以通过检查它们的总和是否等于 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 的结果来实现。这恰好就是第九个三角数!

当然,我们可以使用 for 循环编写这个函数,如下所示:

function triangleNumber(number){
    let sum = 0
    for(let i=1; i < number + 1; i++){
        sum = sum + i
    }
    return sum
}

然而,这将是一个非常低效的决定,因为存在一个非常简单的公式来计算三角数:0.5 x (number) x (number + 1)。

因此,该函数的最高效版本应如下所示:

function triangleNumber(number){
    return 0.5 * number * (number + 1)
}

以下是一些使用示例代码:

triangleNumber(7) === 28 // 0.5 x 7 x 8
triangleNumber(123) === 7626 // 0.5 x 123 x 124

5. 阶乘(Factorial)

一个自然数(大于 0 的任意整数)的阶乘是小于等于该数的所有整数的乘积。例如:3 的阶乘(表示为 3!)是 3 x 2 x 1 = 6。

与求和及乘积函数类似,我们可以通过两种方式创建阶乘函数:使用 for 循环或使用递归。若你之前未接触过递归算法,它们本质上是反复调用自身的函数,直到达到一个“基本情况”。你可以在《函数式 JavaScript 中的递归》中了解更多相关内容。

以下是使用 for 循环创建阶乘函数的方法:

function factorial(number){
  let total = 1
  for (let i = 1; i < number+1; i++){
    total = total * i
  }
  return total
}

该函数通过从 1 到给定数字的循环遍历(每次递增),并将总数与每个数字相乘,最后返回最终总数(即该数字的阶乘)。

以下是使用递归创建阶乘函数的方法:

function factorial(number){
  if (number <= 0){
    return 1
  }
  else{
    return number * factorial(number - 1)
  }
}

在此函数中,我们的基本情况是 0,因为 0 的阶乘令人惊讶地是 1(这个证明实际上非常有趣)。这意味着,当数字通过函数时,只要它不为零,它将乘以 factorial(number - 1)。

为了更好地理解函数在每次传递时的具体操作,跟踪算法可能会有所帮助。以下是使用 3 跟踪算法的示例:

factorial(3) === 3*factorial(2) === 3*2*factorial(1) === 3*2*1*factorial(0) === 3*2*1*1 === 3*2*1 === 6

无论采用哪种方式,这两个函数都会返回相同的值。例如:

factorial(5) === 120 // 5 x 4 x 3 x 2 x 1

6. 因子(Factors)

因子是成对出现的,每一对因子相乘得到原始数字。例如:

数字 10 的因子有:1 和 10;2 和 5。

数字 18 的因子有:1 和 18;2 和 9;3 和 6。

我们希望我们的因子函数接受一个数字,并返回包含所有因子的数组。有许多方法可以编写这个函数,但最简单的方法是使用命令式方法,如下所示:

function factors(number){
    let factorsList = []
    for(let count = 1; count < number+1; count++){
        if(number % count === 0){
            factorsList.push(count)
        }
    }
    return factorsList
}

首先,我们创建一个初始为空的数组。然后使用 for 循环遍历从 1 到给定数字的每个整数,每次遍历时检查数字是否可被整数(或在此情况下的计数器)整除。

如你所见,为了检查可被整除性,我们再次使用了模运算符。如果数字可被整数整除,则它是一个因子,可以添加到我们的数组中。

然后返回该数组,每次运行函数时,将返回一个升序排列的因子数组。例如:

factors(50) === [1,2,5,10,25,50]

找出一个数字的因子在许多情况下非常有用,特别是当你需要组织分组时,比如在线游戏中,当你需要每个队伍都有相等数量的玩家。例如,如果你有 20 个玩家,每个队伍需要 10 个玩家,你可以使用因子函数将 10 个玩家分配到两个队伍中。同样地,如果每个队伍需要 4 个玩家,你可以使用因子函数将 4 个玩家分配到五个队伍中。

在实际应用中,可能如下所示:

function createTeams(numberOfPlayers, numberOfTeams){
    let playersInEachTeam
    if(factors(numberOfPlayers).includes(numberOfTeams)){
        playersInEachTeam = numberOfPlayers / numberOfTeams
    }
    else{
        playersInEachTeam = "wait for more players"
    }
    return playersInEachTeam
}

7. isPrime(是否为素数)

这是学校中最早学到的条件之一,但在日常生活中很少使用。简而言之,如果一个数有两个不同的因子,而这两个因子分别为 1 和它本身,那么这个数就是素数。素数从 2 开始:2、3、5、7、11、13、17、19......一直延伸到无穷大。

这个函数可能一开始看起来很复杂,但如果我们刚刚写过一个非常有用的因子函数,它实际上会变得非常简单。如前所述,如果一个数有两个不同的因子,那么它就是素数,因此我们的函数可以简单地写为:

function isPrime(number){
    return factors(number).length === 2
}

该函数将根据因子列表的长度是否为 2 返回一个布尔值,换句话说,它将返回该数字是否有两个因子。

实际应用时,它会像这样:

isPrime(3) === true
isPrime(76) === false
isPrime(57) === true

在上面的“分组用户”示例中继续,如果用户数量是素数,我们无法平均分组(除非我们只有一个组,但这会违背示例的目的),这意味着我们必须等待另一个用户加入。因此,我们可以在这样的函数中使用它:

function addUsers(users){
    if(isPrime(users)){
        wait = true
    }
    else{
        wait = false
    }
}

8. gcd(最大公约数)

有时被称为“最高公因数”,最大公约数操作找到两个数共有的最大因数。

例如:

12 和 15 的最大公约数是 3

8 和 4 的最大公约数是 4。

一种简单的方法是列出每个数的所有因数(使用上面提到的令人难以置信的函数),然后比较这些列表。然而,比较这些列表需要一些巧妙但也不太高效的数组操作。

不过,下面仍然是一个示例:

function gcd(number1, number2){
    let inCommon = []
    for(let i of factors(number1)){
        if(factors(number2).includes(i)){
            inCommon.push(i)
        }
    }
    return inCommon.sort((a,b)=> b - a)[0]
}

这里,我们将一个空数组赋给变量 inCommon,并循环遍历 number1 的因数数组(使用之前的函数)。如果 number2 的因数数组包含当前遍历的项,我们将其推入 inCommon 数组中。

一旦我们获得了两个数共有的因数数组,我们返回数组中按降序排序的第一个值。换句话说,我们返回最大公约数。

可以想象,如果我们没有之前创建的 factors 函数,这个代码会很庞大。

一种更简洁但更难的方法是使用递归。这是一个相当著名的算法,被称为欧几里得算法:

function gcd(number1, number2){
    if(number2 === 0){
        return number1
    }
    else{
        return gcd(number2, number1%number2)
    }
}

我们的基本情况是当 number2 等于 0 时,此时 number1 就是最大公约数。否则,最大公约数是 number2 和 number1 除以 number2 的余数的最大公约数。

同样,这两个函数都会返回相同的结果。例如:

gcd(24, 16) === 8
gcd(75, 1) === 1

最小公倍数(LCM)是找出两个或多个数能够整除的最小的正整数。

例如:

2 和 6 的最小公倍数是 6。 4 和 15 的最小公倍数是 60。 不幸的是,我们不能简单地列举出每个数的所有倍数,因为这将是一个无限列表。

然而,我们可以使用一个非常有用的公式来计算最小公倍数:

(number1 x number2) / the Greatest Common Divisor of the two numbers

使用公式计算 2 和 6 的最小公倍数(LCM)的步骤如下:

  • 找到两个数的最大公约数(GCD)。在这种情况下,2 和 6 的最大公约数是 2。
  • 将两个数相乘并除以它们的最大公约数。在这种情况下,2 乘以 6 除以 2 等于 6。

因此,2 和 6 的最小公倍数(LCM)是 6。

(2 x 6)/gcd(2,6) = 12/2 = 6

幸运的是,我们刚刚创建了一个 gcd 函数,因此创建 lcm 函数非常容易:

function lcm(number1, number2){
    return (number1*number2)/gcd(number1, number2)
}

就这样!我们只需要返回上面的公式,它应该可以正常工作:

lcm(12, 9) === 36 // (12 x 9)/3

这个函数可能没有明显的实际用途,但在两个事件以不同的间隔发生时,我经常发现它非常有用。这意味着我们可以使用最小公倍数(LCM)来找出两个事件同时发生的时间。

例如,如果一个图像被设置为每隔六秒出现一次,而一个段落的文本被设置为每隔八秒出现一次,那么在第 24 秒时,图像和段落将首次同时出现。

以上就是 JavaScript 中你不知道的数学方法分享(非常实用)的详细内容,更多关于 JavaScript 数学方法的资料请关注本站其它相关文章!

作者最新文章