diff --git a/NEWS.md b/NEWS.md index d3b9f618ddcee..58ee3e30475b3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,8 +30,9 @@ New library functions New library features -------------------- -The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!` -is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]). +* The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!` + is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]). +* `binomial(x, k)` now supports non-integer `x` ([#48124]). Standard library changes ------------------------ diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 3064a28458997..aa9655fe65a64 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -1088,3 +1088,34 @@ Base.@assume_effects :terminates_locally function binomial(n::T, k::T) where T<: end copysign(x, sgn) end + +""" + binomial(x::Number, k::Integer) + +The generalized binomial coefficient, defined for `k ≥ 0` by +the polynomial +```math +\\frac{1}{k!} \\prod_{j=0}^{k-1} (x - j) +``` +When `k < 0` it returns zero. + +For the case of integer `x`, this is equivalent to the ordinary +integer binomial coefficient +```math +\\binom{n}{k} = \\frac{n!}{k! (n-k)!} +``` + +Further generalizations to non-integer `k` are mathematically possible, but +involve the Gamma function and/or the beta function, which are +not provided by the Julia standard library but are available +in external packages such as [SpecialFunctions.jl](https://github.com/JuliaMath/SpecialFunctions.jl). + +# External links +* [Binomial coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient) on Wikipedia. +""" +function binomial(x::Number, k::Integer) + k < 0 && return zero(x)/one(k) + # we don't use prod(i -> (x-i+1), 1:k) / factorial(k), + # and instead divide each term by i, to avoid spurious overflow. + return prod(i -> (x-(i-1))/i, OneTo(k), init=oneunit(x)/one(k)) +end diff --git a/test/intfuncs.jl b/test/intfuncs.jl index c74e5be305a31..061bab673cf48 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -518,6 +518,14 @@ end for x in ((false,false), (false,true), (true,false), (true,true)) @test binomial(x...) == (x != (false,true)) end + + # binomial(x,k) for non-integer x + @test @inferred(binomial(10.0,3)) === 120.0 + @test @inferred(binomial(10//1,3)) === 120//1 + @test binomial(2.5,3) ≈ 5//16 === binomial(5//2,3) + @test binomial(2.5,0) == 1.0 + @test binomial(35.0, 30) ≈ binomial(35, 30) # naive method overflows + @test binomial(2.5,-1) == 0.0 end # concrete-foldability