Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions bitset.go
Original file line number Diff line number Diff line change
Expand Up @@ -877,13 +877,22 @@ func (b *BitSet) Copy(c *BitSet) (count uint) {
if c == nil {
return
}
if b.set != nil { // Copy should not modify current object
copy(c.set, b.set)
}
count = c.length
if b.length < c.length {
count = b.length
}
fullWords := count >> log2WordSize
remainingBits := count & wordMask
var lastWord uint64
if remainingBits != 0 {
// Save before copy in case the source and destination overlap.
lastWord = b.set[fullWords]
}
copy(c.set[:fullWords], b.set[:fullWords])
if remainingBits != 0 {
mask := uint64(1)<<remainingBits - 1
c.set[fullWords] = c.set[fullWords]&^mask | lastWord&mask
}
// Cleaning the last word is needed to keep the invariant that other functions, such as Count, require
// that any bits in the last word that would exceed the length of the bitmask are set to 0.
c.cleanLastWord()
Expand Down
67 changes: 67 additions & 0 deletions bitset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,73 @@ func TestCopyUnaligned(t *testing.T) {
}
}

func TestCopyPreservesUncopiedBits(t *testing.T) {
for _, sourceLength := range []uint{0, 1, 63, 64, 65, 127, 128, 129} {
for _, destinationLength := range []uint{0, 1, 63, 64, 65, 127, 128, 129, 193} {
t.Run(fmt.Sprintf("%d_to_%d", sourceLength, destinationLength), func(t *testing.T) {
source := New(sourceLength)
for i := uint(0); i < sourceLength; i += 3 {
source.Set(i)
}
destination := New(destinationLength).SetAll()
wantCount := sourceLength
if destinationLength < wantCount {
wantCount = destinationLength
}
if got := source.Copy(destination); got != wantCount {
t.Fatalf("copied %d bits, want %d", got, wantCount)
}
if destination.Len() != destinationLength {
t.Fatalf("destination length changed to %d", destination.Len())
}
for i := uint(0); i < sourceLength; i++ {
if source.Test(i) != (i%3 == 0) {
t.Fatalf("source bit %d changed", i)
}
}
var wantSet uint
for i := uint(0); i < destinationLength; i++ {
want := i >= wantCount || i%3 == 0
if want {
wantSet++
}
if destination.Test(i) != want {
t.Fatalf("bit %d = %v, want %v", i, destination.Test(i), want)
}
}
if destination.Count() != wantSet {
t.Fatalf("destination count = %d, want %d", destination.Count(), wantSet)
}
})
}
}
}

func TestCopyOverlappingWords(t *testing.T) {
for _, offsets := range [][2]int{{0, 0}, {0, 1}, {1, 0}} {
t.Run(fmt.Sprint(offsets), func(t *testing.T) {
words := []uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0x3333333333333333, 0}
source := FromWithLength(65, words[offsets[0]:])
destination := FromWithLength(128, words[offsets[1]:offsets[1]+2])
var want [128]bool
for i := uint(0); i < 128; i++ {
want[i] = destination.Test(i)
if i < 65 {
want[i] = source.Test(i)
}
}
if got := source.Copy(destination); got != 65 {
t.Fatalf("copied %d bits, want 65", got)
}
for i, value := range want {
if destination.Test(uint(i)) != value {
t.Fatalf("bit %d = %v, want %v", i, destination.Test(uint(i)), value)
}
}
})
}
}

func TestCopyFull(t *testing.T) {
a := New(10)
b := &BitSet{}
Expand Down