mirror of
https://github.com/golang/go
synced 2024-09-29 22:34:33 -06:00
math/big: improve performance of Binomial
This change improves the performance of Binomial by implementing an
algorithm that produces smaller intermediate values at each step.
Working with smaller big.Int values has the advantage that fewer allocations
and computations are required for each mathematical operation.
The algorithm used is the Multiplicative Formula, which is a well known
way of calculating the Binomial coefficient and is described at:
https://en.wikipedia.org/wiki/Binomial_coefficient#Multiplicative_formula
https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages
In addition to that, an optimization has been made to remove a
redundant computation of (i+1) on each loop which has a measurable
impact when using big.Int.
Performance improvement measured on an M1 MacBook Pro
running the existing benchmark for Binomial:
name old time/op new time/op delta
Binomial-8 589ns ± 0% 435ns ± 0% -26.05% (p=0.000 n=10+10)
name old alloc/op new alloc/op delta
Binomial-8 1.02kB ± 0% 0.08kB ± 0% -92.19% (p=0.000 n=10+10)
name old allocs/op new allocs/op delta
Binomial-8 38.0 ± 0% 5.0 ± 0% -86.84% (p=0.000 n=10+10)
Change-Id: I5a830386dd42f062e17af88411dd74fcb110ded9
GitHub-Last-Rev: 6b2fca07de
GitHub-Pull-Request: golang/go#56339
Reviewed-on: https://go-review.googlesource.com/c/go/+/444315
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
b726b0cadb
commit
91a1f0d918
@ -205,16 +205,46 @@ func (z *Int) MulRange(a, b int64) *Int {
|
||||
return z
|
||||
}
|
||||
|
||||
// Binomial sets z to the binomial coefficient of (n, k) and returns z.
|
||||
func (z *Int) Binomial(n, k int64) *Int {
|
||||
// reduce the number of multiplications by reducing k
|
||||
if n/2 < k && k <= n {
|
||||
k = n - k // Binomial(n, k) == Binomial(n, n-k)
|
||||
// Binomial sets z to the binomial coefficient C(n, k) and returns z.
|
||||
func (z *Int) Binomial(n_, k_ int64) *Int {
|
||||
if k_ > n_ {
|
||||
return z.SetInt64(0)
|
||||
}
|
||||
var a, b Int
|
||||
a.MulRange(n-k+1, n)
|
||||
b.MulRange(1, k)
|
||||
return z.Quo(&a, &b)
|
||||
// reduce the number of multiplications by reducing k
|
||||
if k_ > n_-k_ {
|
||||
k_ = n_ - k_ // C(n, k) == C(n, n-k)
|
||||
}
|
||||
// C(n, k) == n * (n-1) * ... * (n-k+1) / k * (k-1) * ... * 1
|
||||
// == n * (n-1) * ... * (n-k+1) / 1 * (1+1) * ... * k
|
||||
//
|
||||
// Using the multiplicative formula produces smaller values
|
||||
// at each step, requiring fewer allocations and computations:
|
||||
//
|
||||
// z = 1
|
||||
// for i := 0; i < k; i = i+1 {
|
||||
// z *= n-i
|
||||
// z /= i+1
|
||||
// }
|
||||
//
|
||||
// finally to avoid computing i+1 twice per loop:
|
||||
//
|
||||
// z = 1
|
||||
// i := 0
|
||||
// for i < k {
|
||||
// z *= n-i
|
||||
// i++
|
||||
// z /= i
|
||||
// }
|
||||
var n, k, i, t Int
|
||||
n.SetInt64(n_)
|
||||
k.SetInt64(k_)
|
||||
z.Set(intOne)
|
||||
for i.Cmp(&k) < 0 {
|
||||
z.Mul(z, t.Sub(&n, &i))
|
||||
i.Add(&i, intOne)
|
||||
z.Quo(z, &i)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
// Quo sets z to the quotient x/y for y != 0 and returns z.
|
||||
|
Loading…
Reference in New Issue
Block a user