1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
// Package addchain provides addition chain types and operations on them.
package addchain
import (
"errors"
"fmt"
"math/big"
"github.com/mmcloughlin/addchain/internal/bigint"
"github.com/mmcloughlin/addchain/internal/bigints"
)
// References:
//
// [efficientcompaddchain] Bergeron, F., Berstel, J. and Brlek, S. Efficient computation of addition
// chains. Journal de theorie des nombres de Bordeaux. 1994.
// http://www.numdam.org/item/JTNB_1994__6_1_21_0
// [knuth] Knuth, Donald E. Evaluation of Powers. The Art of Computer Programming, Volume 2
// (Third Edition): Seminumerical Algorithms, chapter 4.6.3. 1997.
// https://www-cs-faculty.stanford.edu/~knuth/taocp.html
// Chain is an addition chain.
type Chain []*big.Int
// New constructs the minimal chain {1}.
func New() Chain {
return Chain{big.NewInt(1)}
}
// Int64s builds a chain from the given int64 values.
func Int64s(xs ...int64) Chain {
return Chain(bigints.Int64s(xs...))
}
// Clone the chain.
func (c Chain) Clone() Chain {
return bigints.Clone(c)
}
// AppendClone appends a copy of x to c.
func (c *Chain) AppendClone(x *big.Int) {
*c = append(*c, bigint.Clone(x))
}
// End returns the last element of the chain.
func (c Chain) End() *big.Int {
return c[len(c)-1]
}
// Ops returns all operations that produce the kth position. This could be empty
// for an invalid chain.
func (c Chain) Ops(k int) []Op {
ops := []Op{}
s := new(big.Int)
// If the prefix is ascending this can be done in linear time.
if c[:k].IsAscending() {
for l, r := 0, k-1; l <= r; {
s.Add(c[l], c[r])
cmp := s.Cmp(c[k])
if cmp == 0 {
ops = append(ops, Op{l, r})
}
if cmp <= 0 {
l++
} else {
r--
}
}
return ops
}
// Fallback to quadratic.
for i := 0; i < k; i++ {
for j := i; j < k; j++ {
s.Add(c[i], c[j])
if s.Cmp(c[k]) == 0 {
ops = append(ops, Op{i, j})
}
}
}
return ops
}
// Op returns an Op that produces the kth position.
func (c Chain) Op(k int) (Op, error) {
ops := c.Ops(k)
if len(ops) == 0 {
return Op{}, fmt.Errorf("position %d is not the sum of previous entries", k)
}
return ops[0], nil
}
// Program produces a program that generates the chain.
func (c Chain) Program() (Program, error) {
// Sanity checks.
if len(c) == 0 {
return nil, errors.New("chain empty")
}
if c[0].Cmp(big.NewInt(1)) != 0 {
return nil, errors.New("chain must start with 1")
}
if bigints.Contains(bigint.Zero(), c) {
return nil, errors.New("chain contains zero")
}
for i := 0; i < len(c); i++ {
for j := i + 1; j < len(c); j++ {
if bigint.Equal(c[i], c[j]) {
return nil, fmt.Errorf("chain contains duplicate: %v at positions %d and %d", c[i], i, j)
}
}
}
// Produce the program.
p := Program{}
for k := 1; k < len(c); k++ {
op, err := c.Op(k)
if err != nil {
return nil, err
}
p = append(p, op)
}
return p, nil
}
// Validate checks that c is in fact an addition chain.
func (c Chain) Validate() error {
_, err := c.Program()
return err
}
// Produces checks that c is a valid chain ending with target.
func (c Chain) Produces(target *big.Int) error {
if err := c.Validate(); err != nil {
return err
}
if c.End().Cmp(target) != 0 {
return errors.New("chain does not end with target")
}
return nil
}
// Superset checks that c is a valid chain containing all the targets.
func (c Chain) Superset(targets []*big.Int) error {
if err := c.Validate(); err != nil {
return err
}
for _, target := range targets {
if !bigints.Contains(target, c) {
return fmt.Errorf("chain does not contain %v", target)
}
}
return nil
}
// IsAscending reports whether the chain is ascending, that is if it's in sorted
// order without repeats, as defined in [knuth] Section 4.6.3 formula (11).
// Does not fully validate the chain, only that it is ascending.
func (c Chain) IsAscending() bool {
if len(c) == 0 || !bigint.EqualInt64(c[0], 1) {
return false
}
for i := 1; i < len(c); i++ {
if c[i-1].Cmp(c[i]) >= 0 {
return false
}
}
return true
}
// Product computes the product of two addition chains. The is the "o times"
// operator defined in [efficientcompaddchain] Section 2.
func Product(a, b Chain) Chain {
c := a.Clone()
last := c.End()
for _, x := range b[1:] {
y := new(big.Int).Mul(last, x)
c = append(c, y)
}
return c
}
// Plus adds x to the addition chain. This is the "o plus" operator defined in
// [efficientcompaddchain] Section 2.
func Plus(a Chain, x *big.Int) Chain {
c := a.Clone()
y := new(big.Int).Add(c.End(), x)
return append(c, y)
}
|