From 051e52a9248fef0698ec179a8c1c659b450ff8fa Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 29 Oct 2026 20:53:33 +0000 Subject: [PATCH] Evaluate a binomial with a whole upper argument and a fractional lower one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit binomial(n, k) with a whole n and a k that is not whole was left as written, and EvalNumerical threw: the gamma ratio was guarded to an n that is not an integer. It is Γ(n + 1)/(Γ(k + 1) Γ(n - k + 1)) for any n, and binomial(3, 1/2) is 32/(5 pi). For a negative whole n, a pole of Γ(n + 1), it has no value, as the node's domain condition says, and is NaN. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 +++++++++++ ...aluation.Continuous.Arithmetics.Classes.cs | 7 +++++-- .../UnitTests/Convenience/BinomialTest.cs | 20 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e3f02e093..d17ff0df7 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -608,6 +608,18 @@ It is the forward difference with a step of 1, as Mathematica's `DifferenceDelta | `"forwarddifference(k^2, k)".ToEntity().Simplify()` | throws `UnhandledParseException` | `1 + 2 * k` | | `"forwarddifference(2^k, k)".ToEntity()` | throws `UnhandledParseException` | `2 ^ (k + 1) - 2 ^ k` | +### A binomial with a whole upper argument and a lower one that is not whole is evaluated + +**Not silent: what threw is a number.** `binomial(n, k)` with a whole `n` and a `k` that is not whole was left as +written, so `EvalNumerical` threw `CannotEvalException`. It is the gamma ratio `Γ(n + 1)/(Γ(k + 1) Γ(n - k + 1))`, as +for any other `n`, and for a negative whole `n` it has no value: `Γ(n + 1)` has a pole there +([#1411](https://github.com/asc-community/AngouriMath/issues/1411)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"binomial(3, 1/2)".ToEntity().EvalNumerical()` | `CannotEvalException` | `2.0371832715762602978…`, `32/(5 pi)` | +| `"binomial(-3, 1/2)".ToEntity().EvalNumerical()` | `CannotEvalException` | `NaN` | + ### The harmonic numbers **Not silent: `harmonicnumber(n)` and `harmonicnumber(n, m)` are the harmonic numbers, where the one was a product and the diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs index c2f8e8f43..6f2a617db 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs @@ -566,9 +566,12 @@ protected override Entity InnerSimplify(bool isExact) => // negative n included: binomial(-1, 3) = (-1)(-2)(-3)/3! = -1. (Number top, Integer bottom) when bottom.EInteger.CanFitInInt32() => FallingFactorialOver(top, bottom.EInteger.ToInt32Unchecked()), (_, Integer { IsZero: true }) => Integer.Create(1), - // Through the gamma function only numerically: there is no exact form. + // A k that is not whole has no value with a negative whole n, a pole of Γ(n + 1). + (Integer { EInteger.Sign: < 0 }, Complex bottom) when bottom is not Integer => MathS.NaN, + // Through the gamma function only numerically: there is no exact form. A whole n + // takes it too: binomial(3, 1/2) is 32/(5 pi). (Complex top, Complex bottom) when !isExact - && top is not Integer && Number.Factorial(top) is var topF && Number.Factorial(bottom) is var bottomF + && Number.Factorial(top) is var topF && Number.Factorial(bottom) is var bottomF && Number.Factorial(top - bottom) is var restF && !topF.IsNaN && !bottomF.IsNaN && !restF.IsNaN => topF / (bottomF * restF), _ => null diff --git a/Sources/Tests/UnitTests/Convenience/BinomialTest.cs b/Sources/Tests/UnitTests/Convenience/BinomialTest.cs index 6a01ed231..1269ac20f 100644 --- a/Sources/Tests/UnitTests/Convenience/BinomialTest.cs +++ b/Sources/Tests/UnitTests/Convenience/BinomialTest.cs @@ -67,6 +67,26 @@ public void AFractionalLowerArgumentEvaluatesThroughTheGammaFunction() Assert.True(Math.Abs((double)value.RealPart - 2.5) < 1e-9, $"binomial(5/2, 3/2) evaluated to {value}"); } + /// + /// A whole upper argument with a lower one that is not whole goes through the gamma function + /// as well, against mpmath to 25 digits: binomial(3, 1/2) is 32/(5 pi). It was + /// left unevaluated. With a negative whole upper argument there is no value. + /// + [Theory] + [InlineData("binomial(3, 1/2)", "2.03718327157626029784171217117")] + [InlineData("binomial(0, 1/2)", "0.63661977236758134307553505349")] + [InlineData("binomial(4, 3/2)", "5.43248872420336079424456578978")] + [InlineData("binomial(3, -1/2)", "0.291026181653751471120244595881")] + public void AWholeUpperArgumentWithAFractionalLowerOneEvaluatesThroughTheGammaFunction(string input, string expected) + { + var value = Assert.IsAssignableFrom(input.ToEntity().EvalNumerical()); + Assert.True(value.EDecimal.Subtract(PeterO.Numbers.EDecimal.FromString(expected)).Abs().CompareTo(PeterO.Numbers.EDecimal.FromString("1e-25")) < 0, $"{input} evaluated to {value}"); + } + + [Fact] + public void ANegativeWholeUpperArgumentWithAFractionalLowerOneHasNoValue() + => Assert.Equal(MathS.NaN, "binomial(-3, 1/2)".ToEntity().EvalNumerical()); + /// /// Every whole k has a value, whatever n is; a k that is not whole has one unless n is /// a negative whole number, where Γ(n + 1) has a pole.