Skip to content

Commit 8a91e8f

Browse files
authored
Merge pull request #526 from SAY-5/fix/bsi-sign-bit-growth
fix(bsi): sign-extend existing negatives when bA grows in NewDefaultBSI
2 parents 5449c7a + 0895c48 commit 8a91e8f

2 files changed

Lines changed: 47 additions & 4 deletions

File tree

roaring64/bsi64.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,20 @@ func (b *BSI) SetBigValue(columnID uint64, value *big.Int) {
100100
if minBits == 1 {
101101
minBits = 2
102102
}
103-
for len(b.bA) < minBits {
104-
b.bA = append(b.bA, Bitmap{})
103+
if len(b.bA) < minBits {
104+
oldSignPos := len(b.bA) - 1
105+
for len(b.bA) < minBits {
106+
b.bA = append(b.bA, Bitmap{})
107+
}
108+
// When bA grows, the sign slot shifts from oldSignPos to the new end
109+
// of bA. For existing negative entries (whose sign bit is set in
110+
// bA[oldSignPos]), sign-extension requires that all intermediate bit
111+
// positions between oldSignPos and the new sign position also be set.
112+
// Copy the old sign bitmap into every new slot (sign extension).
113+
newSignPos := len(b.bA) - 1
114+
for i := oldSignPos + 1; i <= newSignPos; i++ {
115+
b.bA[i].Or(&b.bA[oldSignPos])
116+
}
105117
}
106118
}
107119

@@ -122,8 +134,16 @@ func (b *BSI) SetBigMany(foundSet *Bitmap, value *big.Int) {
122134
if minBits == 1 {
123135
minBits = 2
124136
}
125-
for len(b.bA) < minBits {
126-
b.bA = append(b.bA, Bitmap{})
137+
if len(b.bA) < minBits {
138+
oldSignPos := len(b.bA) - 1
139+
for len(b.bA) < minBits {
140+
b.bA = append(b.bA, Bitmap{})
141+
}
142+
// Sign-extend existing negative entries into the new bit slots.
143+
newSignPos := len(b.bA) - 1
144+
for i := oldSignPos + 1; i <= newSignPos; i++ {
145+
b.bA[i].Or(&b.bA[oldSignPos])
146+
}
127147
}
128148
}
129149
for i := b.BitCount(); i >= 0; i-- {

roaring64/bsi64_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,3 +870,26 @@ func BenchmarkClearValues(b *testing.B) {
870870
b2.ClearValues(resultA)
871871
}
872872
}
873+
874+
// TestGetValueNegativeAfterGrowth checks that negative values stored in a
875+
// NewDefaultBSI are returned correctly after bA grows because a larger positive
876+
// value is added later. Previously, the sign bit was stranded at the old slot
877+
// when bA was extended, causing wrong-sign results.
878+
// See https://github.com/RoaringBitmap/roaring/issues/462
879+
func TestGetValueNegativeAfterGrowth(t *testing.T) {
880+
bsi := NewDefaultBSI()
881+
for i := int64(-15); i <= 15; i++ {
882+
bsi.SetValue(uint64(i+15), i)
883+
}
884+
885+
val, exists := bsi.GetValue(0)
886+
require.True(t, exists)
887+
assert.Equal(t, int64(-15), val, "before growth")
888+
889+
// Adding 16 requires an extra bit and grows bA, shifting the sign slot.
890+
bsi.SetValue(uint64(16+15), 16)
891+
892+
val, exists = bsi.GetValue(0)
893+
require.True(t, exists)
894+
assert.Equal(t, int64(-15), val, "after growth")
895+
}

0 commit comments

Comments
 (0)