From 99a9c7e9e1589f9ab95b94f7eea487fe64c638a1 Mon Sep 17 00:00:00 2001 From: Alexander Kireev Date: Sat, 20 Jun 2026 00:11:17 +0700 Subject: [PATCH] fix: allow zero values in multinomial multinomial threw a TypeError on any array containing a zero, e.g. multinomial([2, 0, 1]). A zero count is mathematically valid since 0! = 1, and the multinomial coefficient is the standard generalization of the binomial coefficient: multinomial([k, n - k]) === combinations(n, k). The validation rejected zero via !isPositive(ai) (isPositive(0) is false). Reject only negatives with isNegative(ai) instead, matching the non-negative-integer semantics already used by combinations, factorial, and combinationsWithRep. Also fix the doc comment typo (ai <= 0 -> ai >= 0). --- HISTORY.md | 3 +++ src/function/probability/multinomial.js | 8 ++++---- .../function/probability/multinomial.test.js | 10 ++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index deb73875db..8dd9d071d0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,9 @@ # unpublished changes since 15.2.0 +- Fix: `multinomial` threw a `TypeError` on arrays containing a zero, like + `multinomial([2, 0, 1])`. Zero is a valid count (`0! = 1`), consistent with + `combinations` and `factorial`. - Docs: fix the browser example `rocket_trajectory_optimization.html` (#3654). Thanks @dvd101x. diff --git a/src/function/probability/multinomial.js b/src/function/probability/multinomial.js index d67d839f34..242d0d8e8c 100644 --- a/src/function/probability/multinomial.js +++ b/src/function/probability/multinomial.js @@ -2,14 +2,14 @@ import { deepForEach } from '../../utils/collection.js' import { factory } from '../../utils/factory.js' const name = 'multinomial' -const dependencies = ['typed', 'add', 'divide', 'multiply', 'factorial', 'isInteger', 'isPositive'] +const dependencies = ['typed', 'add', 'divide', 'multiply', 'factorial', 'isInteger', 'isNegative'] -export const createMultinomial = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, divide, multiply, factorial, isInteger, isPositive }) => { +export const createMultinomial = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, divide, multiply, factorial, isInteger, isNegative }) => { /** * Multinomial Coefficients compute the number of ways of picking a1, a2, ..., ai unordered outcomes from `n` possibilities. * * multinomial takes one array of integers as an argument. - * The following condition must be enforced: every ai <= 0 + * The following condition must be enforced: every ai >= 0 * * Syntax: * @@ -32,7 +32,7 @@ export const createMultinomial = /* #__PURE__ */ factory(name, dependencies, ({ let denom = 1 deepForEach(a, function (ai) { - if (!isInteger(ai) || !isPositive(ai)) { + if (!isInteger(ai) || isNegative(ai)) { throw new TypeError('Positive integer value expected in function multinomial') } sum = add(sum, ai) diff --git a/test/unit-tests/function/probability/multinomial.test.js b/test/unit-tests/function/probability/multinomial.test.js index 052987c9cc..a6ba350dd3 100644 --- a/test/unit-tests/function/probability/multinomial.test.js +++ b/test/unit-tests/function/probability/multinomial.test.js @@ -15,6 +15,16 @@ describe('multinomial', function () { assert.deepStrictEqual(multinomial([math.bignumber(10), math.bignumber(1), math.bignumber(2)]), math.bignumber(858)) }) + it('should allow zero values (0! = 1), consistent with combinations', function () { + assert.strictEqual(multinomial([0]), 1) + assert.strictEqual(multinomial([3, 0]), 1) + assert.strictEqual(multinomial([2, 0, 1]), 3) + assert.strictEqual(multinomial([0, 4, 4]), 70) + // multinomial([k, n - k]) must agree with combinations(n, k) + assert.strictEqual(multinomial([5, 0]), math.combinations(5, 5)) + assert.deepStrictEqual(multinomial([math.bignumber(3), math.bignumber(0)]), math.bignumber(1)) + }) + it('should not work with non-integer and negative input', function () { assert.throws(function () { multinomial([0.5, 3]) }, TypeError) assert.throws(function () { multinomial([math.bignumber(3), math.bignumber(0.5)]) }, TypeError)