987 lines
26 KiB
Odin
987 lines
26 KiB
Odin
package many_bits
|
|
|
|
import "base:builtin"
|
|
import "base:intrinsics"
|
|
import "core:fmt"
|
|
import "core:slice"
|
|
|
|
@(private)
|
|
ODIN_BOUNDS_CHECK :: !ODIN_NO_BOUNDS_CHECK
|
|
// Number of bits in system uint
|
|
UINT_NUM_BITS :: size_of(uint) * 8
|
|
UINT_MAX: uint : 1 << UINT_NUM_BITS - 1
|
|
// Power to which 2 is raised to get the size of uint in bits
|
|
// For bitshift division which gives index of integer in int_bits_array
|
|
INDEX_SHIFT :: uint(intrinsics.count_trailing_zeros(UINT_NUM_BITS))
|
|
// Value to & overall index by to get bit position
|
|
BIT_POS_MASK :: UINT_NUM_BITS - 1
|
|
|
|
Int_Bits :: bit_set[0 ..< UINT_NUM_BITS;uint]
|
|
|
|
// Use `core:container.Bit_Array` if dynamic length is needed.
|
|
// This has a more specific purpose.
|
|
Bits :: struct {
|
|
int_array: []Int_Bits,
|
|
length: int, // Total number of bits being stored
|
|
}
|
|
|
|
delete :: proc(using bits: Bits, allocator := context.allocator) {
|
|
delete_slice(int_array, allocator)
|
|
}
|
|
|
|
make :: proc(#any_int length: int, allocator := context.allocator) -> Bits {
|
|
return Bits {
|
|
int_array = make_slice([]Int_Bits, ((length - 1) >> INDEX_SHIFT) + 1, allocator),
|
|
length = length,
|
|
}
|
|
}
|
|
|
|
// Sets all bits to 0 (false)
|
|
zero :: #force_inline proc(bits: Bits) {
|
|
slice.zero(bits.int_array)
|
|
}
|
|
|
|
set :: #force_inline proc(bits: Bits, #any_int index: int, set_to: bool) {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if index >= bits.length {
|
|
panic(fmt.tprintf("Bit position %i out of bounds for length %i.", index, bits.length))
|
|
}
|
|
}
|
|
if set_to == true {
|
|
bits.int_array[index >> INDEX_SHIFT] += {index & BIT_POS_MASK}
|
|
} else {
|
|
bits.int_array[index >> INDEX_SHIFT] -= {index & BIT_POS_MASK}
|
|
}
|
|
}
|
|
|
|
set_true :: #force_inline proc(bits: Bits, #any_int index: int) {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if index >= bits.length {
|
|
panic(fmt.tprintf("Bit position %i out of bounds for length %i.", index, bits.length))
|
|
}
|
|
}
|
|
bits.int_array[index >> INDEX_SHIFT] += {index & BIT_POS_MASK}
|
|
}
|
|
|
|
set_one :: set_true
|
|
|
|
set_false :: #force_inline proc(bits: Bits, #any_int index: int) {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if index >= bits.length {
|
|
panic(fmt.tprintf("Bit position %i out of bounds for length %i.", index, bits.length))
|
|
}
|
|
}
|
|
bits.int_array[index >> INDEX_SHIFT] -= {index & BIT_POS_MASK}
|
|
}
|
|
|
|
set_zero :: set_false
|
|
|
|
get :: #force_inline proc(bits: Bits, #any_int index: int) -> bool {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if index >= bits.length {
|
|
panic(fmt.tprintf("Bit position %i out of bounds for length %i.", index, bits.length))
|
|
}
|
|
}
|
|
return (index & BIT_POS_MASK) in bits.int_array[index >> INDEX_SHIFT]
|
|
}
|
|
|
|
// Returns true if all bits in range [start, end) are set [start is inclusive, end is exclusive)
|
|
range_true :: proc(bits: Bits, #any_int start, end: int) -> bool {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if start < 0 {
|
|
panic(fmt.tprintf("Start %i is negative.", start))
|
|
}
|
|
if start > end {
|
|
panic(fmt.tprintf("Start %i is greater than end %i.", start, end))
|
|
}
|
|
if end > bits.length {
|
|
panic(fmt.tprintf("End %i out of bounds for length %i.", end, bits.length))
|
|
}
|
|
}
|
|
|
|
// Empty range is vacuously true
|
|
if start == end do return true
|
|
|
|
start_u := uint(start)
|
|
end_u := uint(end)
|
|
|
|
start_word := start_u >> INDEX_SHIFT
|
|
end_word := (end_u - 1) >> INDEX_SHIFT
|
|
|
|
start_bit := start_u & BIT_POS_MASK
|
|
end_bit := end_u & BIT_POS_MASK // end is exclusive; 0 means "to end of word"
|
|
|
|
// Range is within a single word
|
|
if start_word == end_word {
|
|
word := transmute(uint)bits.int_array[start_word]
|
|
|
|
low_mask: uint = (uint(1) << start_bit) - 1
|
|
high_mask: uint = ((uint(1) << end_bit) - 1) | (UINT_MAX * uint(end_bit == 0))
|
|
|
|
mask := high_mask & ~low_mask
|
|
return word & mask == mask
|
|
}
|
|
|
|
// Range spans multiple words
|
|
|
|
// First word: [start_bit, UINT_NUM_BITS)
|
|
if start_bit != 0 {
|
|
first_word := transmute(uint)bits.int_array[start_word]
|
|
start_mask: uint = ~((uint(1) << start_bit) - 1)
|
|
if first_word & start_mask != start_mask {
|
|
return false
|
|
}
|
|
start_word += 1
|
|
}
|
|
|
|
// Last word: [0, end_bit)
|
|
// If end_bit == 0, we need the whole last word, so include it in the middle scan.
|
|
if end_bit != 0 {
|
|
last_word := transmute(uint)bits.int_array[end_word]
|
|
end_mask: uint = (uint(1) << end_bit) - 1
|
|
if last_word & end_mask != end_mask {
|
|
return false
|
|
}
|
|
} else {
|
|
end_word += 1
|
|
}
|
|
|
|
// Middle words: all bits must be set
|
|
for i := start_word; i < end_word; i += 1 {
|
|
if transmute(uint)bits.int_array[i] != UINT_MAX {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
range_ones :: range_true
|
|
|
|
all_true :: proc(bits: Bits) -> bool {
|
|
// Empty bit array is vacuously true
|
|
if bits.length == 0 do return true
|
|
|
|
bit_index := uint(bits.length - 1)
|
|
int_index := bit_index >> INDEX_SHIFT
|
|
// The last int needs special treatment because we only want to check part of it
|
|
last_bit_pos := bit_index & BIT_POS_MASK
|
|
last_bit_mask: uint = (1 << (last_bit_pos + 1)) - 1
|
|
int_val := transmute(uint)bits.int_array[int_index]
|
|
if int_val & last_bit_mask != last_bit_mask {
|
|
return false
|
|
}
|
|
if int_index == 0 { // If there was only 1 int in the array
|
|
return true
|
|
}
|
|
int_index -= 1
|
|
|
|
// All other ints should be all 1s
|
|
for {
|
|
int_val := transmute(uint)bits.int_array[int_index]
|
|
if int_val != UINT_MAX {
|
|
return false
|
|
}
|
|
|
|
if int_index == 0 {
|
|
return true
|
|
}
|
|
int_index -= 1
|
|
}
|
|
}
|
|
|
|
all_ones :: all_true
|
|
|
|
// Returns ok = false if there are no 1 bits in the entire array.
|
|
nearest_true :: proc(bits: Bits, index: int) -> (nearest: int, ok: bool) {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if index >= bits.length {
|
|
panic(fmt.tprintf("Bit position %i out of bounds for length %i.", index, bits.length))
|
|
}
|
|
}
|
|
|
|
bit_index := uint(index)
|
|
word_index := bit_index >> INDEX_SHIFT
|
|
bit_pos := bit_index & BIT_POS_MASK
|
|
|
|
word_index_int := int(word_index)
|
|
total_words := len(bits.int_array)
|
|
max_left := word_index_int
|
|
max_right := total_words - 1 - word_index_int
|
|
max_offset := max(max_left, max_right)
|
|
|
|
word_val := transmute(uint)bits.int_array[word_index_int]
|
|
if word_val != 0 {
|
|
if (word_val & (uint(1) << bit_pos)) != 0 do return index, true
|
|
|
|
left_mask := (uint(1) << bit_pos) | ((uint(1) << bit_pos) - 1)
|
|
left_bits_value := word_val & left_mask
|
|
|
|
right_mask := ~((uint(1) << bit_pos) - 1)
|
|
right_bits_value := word_val & right_mask
|
|
|
|
nearest_left := 0
|
|
left_found := false
|
|
if left_bits_value != 0 {
|
|
left_offset_from_top := intrinsics.count_leading_zeros(left_bits_value)
|
|
left_bit := (UINT_NUM_BITS - 1) - left_offset_from_top
|
|
nearest_left = (word_index_int << INDEX_SHIFT) + int(left_bit)
|
|
left_found = true
|
|
}
|
|
|
|
nearest_right := 0
|
|
right_found := false
|
|
if right_bits_value != 0 {
|
|
right_offset := intrinsics.count_trailing_zeros(right_bits_value)
|
|
nearest_right = (word_index_int << INDEX_SHIFT) + int(right_offset)
|
|
right_found = true
|
|
}
|
|
|
|
if left_found && right_found {
|
|
left_dist := index - nearest_left
|
|
right_dist := nearest_right - index
|
|
if left_dist <= right_dist {
|
|
return nearest_left, true
|
|
} else {
|
|
return nearest_right, true
|
|
}
|
|
} else if left_found {
|
|
return nearest_left, true
|
|
} else if right_found {
|
|
return nearest_right, true
|
|
}
|
|
}
|
|
|
|
for offset := 1; offset <= max_offset; offset += 1 {
|
|
right_found := false
|
|
left_found := false
|
|
nearest_right := 0
|
|
nearest_left := 0
|
|
right_dist := 0
|
|
left_dist := 0
|
|
|
|
right_index := word_index_int + offset
|
|
if right_index < total_words {
|
|
word_val := transmute(uint)bits.int_array[right_index]
|
|
if word_val != 0 {
|
|
right_offset := intrinsics.count_trailing_zeros(word_val)
|
|
nearest_right = (right_index << INDEX_SHIFT) + int(right_offset)
|
|
right_found = true
|
|
right_dist = nearest_right - index
|
|
}
|
|
}
|
|
|
|
left_index := word_index_int - offset
|
|
if left_index >= 0 {
|
|
word_val := transmute(uint)bits.int_array[left_index]
|
|
if word_val != 0 {
|
|
left_offset_from_top := intrinsics.count_leading_zeros(word_val)
|
|
left_bit := (UINT_NUM_BITS - 1) - left_offset_from_top
|
|
nearest_left = (left_index << INDEX_SHIFT) + int(left_bit)
|
|
left_found = true
|
|
left_dist = index - nearest_left
|
|
}
|
|
}
|
|
|
|
if left_found && right_found {
|
|
if left_dist <= right_dist {
|
|
return nearest_left, true
|
|
} else {
|
|
return nearest_right, true
|
|
}
|
|
} else if left_found {
|
|
return nearest_left, true
|
|
} else if right_found {
|
|
return nearest_right, true
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
nearest_one :: nearest_true
|
|
|
|
// Returns ok = false if there are no 0 bits in the entire array.
|
|
nearest_false :: proc(bits: Bits, index: int) -> (nearest: int, ok: bool) {
|
|
when ODIN_BOUNDS_CHECK {
|
|
if index >= bits.length {
|
|
panic(fmt.tprintf("Bit position %i out of bounds for length %i.", index, bits.length))
|
|
}
|
|
}
|
|
|
|
bit_index := uint(index)
|
|
word_index := bit_index >> INDEX_SHIFT
|
|
bit_pos := bit_index & BIT_POS_MASK
|
|
|
|
word_index_int := int(word_index)
|
|
total_words := len(bits.int_array)
|
|
max_left := word_index_int
|
|
max_right := total_words - 1 - word_index_int
|
|
max_offset := max(max_left, max_right)
|
|
|
|
last_bit_index := uint(bits.length - 1)
|
|
last_word_index := int(last_bit_index >> INDEX_SHIFT)
|
|
last_bit_pos := last_bit_index & BIT_POS_MASK
|
|
valid_bits_mask: uint
|
|
if last_bit_pos == UINT_NUM_BITS - 1 {
|
|
valid_bits_mask = UINT_MAX
|
|
} else {
|
|
valid_bits_mask = (uint(1) << (last_bit_pos + 1)) - 1
|
|
}
|
|
|
|
word_val := transmute(uint)bits.int_array[word_index_int]
|
|
word_val_search := ~word_val
|
|
if word_index_int == last_word_index {
|
|
word_val_search &= valid_bits_mask
|
|
}
|
|
if word_val_search != 0 {
|
|
if (word_val & (uint(1) << bit_pos)) == 0 do return index, true
|
|
|
|
left_mask := (uint(1) << bit_pos) | ((uint(1) << bit_pos) - 1)
|
|
left_bits_value := word_val_search & left_mask
|
|
|
|
right_mask := ~((uint(1) << bit_pos) - 1)
|
|
right_bits_value := word_val_search & right_mask
|
|
|
|
nearest_left := 0
|
|
left_found := false
|
|
if left_bits_value != 0 {
|
|
left_offset_from_top := intrinsics.count_leading_zeros(left_bits_value)
|
|
left_bit := (UINT_NUM_BITS - 1) - left_offset_from_top
|
|
nearest_left = (word_index_int << INDEX_SHIFT) + int(left_bit)
|
|
left_found = true
|
|
}
|
|
|
|
nearest_right := 0
|
|
right_found := false
|
|
if right_bits_value != 0 {
|
|
right_offset := intrinsics.count_trailing_zeros(right_bits_value)
|
|
nearest_right = (word_index_int << INDEX_SHIFT) + int(right_offset)
|
|
right_found = true
|
|
}
|
|
|
|
if left_found && right_found {
|
|
left_dist := index - nearest_left
|
|
right_dist := nearest_right - index
|
|
if left_dist <= right_dist {
|
|
return nearest_left, true
|
|
} else {
|
|
return nearest_right, true
|
|
}
|
|
} else if left_found {
|
|
return nearest_left, true
|
|
} else if right_found {
|
|
return nearest_right, true
|
|
}
|
|
}
|
|
|
|
for offset := 1; offset <= max_offset; offset += 1 {
|
|
right_found := false
|
|
left_found := false
|
|
nearest_right := 0
|
|
nearest_left := 0
|
|
right_dist := 0
|
|
left_dist := 0
|
|
|
|
right_index := word_index_int + offset
|
|
if right_index < total_words {
|
|
word_val := transmute(uint)bits.int_array[right_index]
|
|
word_val_search := ~word_val
|
|
if right_index == last_word_index {
|
|
word_val_search &= valid_bits_mask
|
|
}
|
|
if word_val_search != 0 {
|
|
right_offset := intrinsics.count_trailing_zeros(word_val_search)
|
|
nearest_right = (right_index << INDEX_SHIFT) + int(right_offset)
|
|
right_found = true
|
|
right_dist = nearest_right - index
|
|
}
|
|
}
|
|
|
|
left_index := word_index_int - offset
|
|
if left_index >= 0 {
|
|
word_val := transmute(uint)bits.int_array[left_index]
|
|
word_val_search := ~word_val
|
|
if word_val_search != 0 {
|
|
left_offset_from_top := intrinsics.count_leading_zeros(word_val_search)
|
|
left_bit := (UINT_NUM_BITS - 1) - left_offset_from_top
|
|
nearest_left = (left_index << INDEX_SHIFT) + int(left_bit)
|
|
left_found = true
|
|
left_dist = index - nearest_left
|
|
}
|
|
}
|
|
|
|
if left_found && right_found {
|
|
if left_dist <= right_dist {
|
|
return nearest_left, true
|
|
} else {
|
|
return nearest_right, true
|
|
}
|
|
} else if left_found {
|
|
return nearest_left, true
|
|
} else if right_found {
|
|
return nearest_right, true
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
nearest_zero :: nearest_false
|
|
|
|
Iterator :: struct {
|
|
bits: ^Bits,
|
|
word_idx: int,
|
|
bit_idx: uint,
|
|
}
|
|
|
|
iterator :: #force_inline proc(bits: ^Bits) -> Iterator {
|
|
return {bits = bits}
|
|
}
|
|
|
|
iterate :: proc(iterator: ^Iterator) -> (is_true: bool, idx: int, cond: bool) {
|
|
idx = iterator.word_idx * UINT_NUM_BITS + int(iterator.bit_idx)
|
|
if idx >= iterator.bits.length {
|
|
return false, 0, false
|
|
}
|
|
|
|
word := transmute(uint)iterator.bits.int_array[iterator.word_idx]
|
|
is_true = (word >> iterator.bit_idx & 1) == 1
|
|
|
|
iterator.bit_idx += 1
|
|
if iterator.bit_idx >= UINT_NUM_BITS {
|
|
iterator.bit_idx = 0
|
|
iterator.word_idx += 1
|
|
}
|
|
|
|
return is_true, idx, true
|
|
}
|
|
|
|
@(private = "file")
|
|
_iterate_kind :: #force_inline proc(iterator: ^Iterator, $ITERATE_ZEROS: bool) -> (idx: int, cond: bool) {
|
|
for iterator.word_idx < len(iterator.bits.int_array) {
|
|
word := transmute(uint)iterator.bits.int_array[iterator.word_idx]
|
|
when ITERATE_ZEROS do word = ~word
|
|
|
|
word >>= iterator.bit_idx // Mask out already-processed bits
|
|
|
|
if word != 0 {
|
|
// Found a bit - count_trailing_zeros gives position in shifted word
|
|
iterator.bit_idx += uint(intrinsics.count_trailing_zeros(word))
|
|
idx = iterator.word_idx * UINT_NUM_BITS + int(iterator.bit_idx)
|
|
|
|
// Advance for next call
|
|
iterator.bit_idx += 1
|
|
if iterator.bit_idx >= UINT_NUM_BITS {
|
|
iterator.bit_idx = 0
|
|
iterator.word_idx += 1
|
|
}
|
|
|
|
return idx, idx < iterator.bits.length
|
|
}
|
|
|
|
// Word exhausted, move to next
|
|
iterator.word_idx += 1
|
|
iterator.bit_idx = 0
|
|
}
|
|
|
|
return 0, false
|
|
}
|
|
|
|
iterate_true :: proc(iterator: ^Iterator) -> (idx: int, cond: bool) {
|
|
return _iterate_kind(iterator, ITERATE_ZEROS = false)
|
|
}
|
|
|
|
iterate_ones :: iterate_true
|
|
|
|
iterate_false :: proc(iterator: ^Iterator) -> (idx: int, cond: bool) {
|
|
return _iterate_kind(iterator, ITERATE_ZEROS = true)
|
|
}
|
|
|
|
iterate_zeros :: iterate_false
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// ----- Tests ------------------------
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
import "core:testing"
|
|
|
|
@(test)
|
|
test_set :: proc(t: ^testing.T) {
|
|
bits := make(128)
|
|
defer delete(bits)
|
|
|
|
set(bits, 0, true)
|
|
testing.expect_value(t, bits.int_array[0], Int_Bits{0})
|
|
set(bits, 3, true)
|
|
testing.expect_value(t, bits.int_array[0], Int_Bits{0, 3})
|
|
set(bits, 64, true)
|
|
testing.expect_value(t, bits.int_array[1], Int_Bits{0})
|
|
set(bits, 127, true)
|
|
testing.expect_value(t, bits.int_array[1], Int_Bits{0, 63})
|
|
set(bits, 127, false)
|
|
testing.expect_value(t, bits.int_array[1], Int_Bits{0})
|
|
}
|
|
|
|
@(test)
|
|
test_get :: proc(t: ^testing.T) {
|
|
bits := make(128)
|
|
defer delete(bits)
|
|
|
|
// Default is false
|
|
testing.expect(t, !get(bits, 0))
|
|
testing.expect(t, !get(bits, 63))
|
|
testing.expect(t, !get(bits, 64))
|
|
testing.expect(t, !get(bits, 127))
|
|
|
|
// Set and verify within first uint
|
|
set(bits, 0, true)
|
|
testing.expect(t, get(bits, 0))
|
|
testing.expect(t, !get(bits, 1))
|
|
|
|
set(bits, 3, true)
|
|
testing.expect(t, get(bits, 3))
|
|
testing.expect(t, !get(bits, 2))
|
|
testing.expect(t, !get(bits, 4))
|
|
|
|
// Cross uint boundary
|
|
set(bits, 64, true)
|
|
testing.expect(t, get(bits, 64))
|
|
testing.expect(t, !get(bits, 63))
|
|
testing.expect(t, !get(bits, 65))
|
|
|
|
// Last bit
|
|
set(bits, 127, true)
|
|
testing.expect(t, get(bits, 127))
|
|
|
|
// Unset and verify
|
|
set(bits, 127, false)
|
|
testing.expect(t, !get(bits, 127))
|
|
}
|
|
|
|
@(test)
|
|
test_set_true_set_false :: proc(t: ^testing.T) {
|
|
bits := make(128)
|
|
defer delete(bits)
|
|
|
|
// set_true within first uint
|
|
set_true(bits, 0)
|
|
testing.expect_value(t, bits.int_array[0], Int_Bits{0})
|
|
testing.expect(t, get(bits, 0))
|
|
|
|
set_true(bits, 3)
|
|
testing.expect_value(t, bits.int_array[0], Int_Bits{0, 3})
|
|
testing.expect(t, get(bits, 3))
|
|
|
|
// set_true across uint boundary
|
|
set_true(bits, 64)
|
|
testing.expect_value(t, bits.int_array[1], Int_Bits{0})
|
|
testing.expect(t, get(bits, 64))
|
|
testing.expect(t, !get(bits, 63))
|
|
testing.expect(t, !get(bits, 65))
|
|
|
|
// set_true on last bit
|
|
set_true(bits, 127)
|
|
testing.expect_value(t, bits.int_array[1], Int_Bits{0, 63})
|
|
testing.expect(t, get(bits, 127))
|
|
|
|
// set_false to clear bits
|
|
set_false(bits, 127)
|
|
testing.expect_value(t, bits.int_array[1], Int_Bits{0})
|
|
testing.expect(t, !get(bits, 127))
|
|
|
|
set_false(bits, 0)
|
|
testing.expect_value(t, bits.int_array[0], Int_Bits{3})
|
|
testing.expect(t, !get(bits, 0))
|
|
testing.expect(t, get(bits, 3)) // bit 3 still set
|
|
|
|
// set_false on already-false bit (should be no-op)
|
|
set_false(bits, 1)
|
|
testing.expect_value(t, bits.int_array[0], Int_Bits{3})
|
|
testing.expect(t, !get(bits, 1))
|
|
}
|
|
|
|
@(test)
|
|
all_true_test :: proc(t: ^testing.T) {
|
|
uint_max := UINT_MAX
|
|
all_ones := transmute(Int_Bits)uint_max
|
|
|
|
bits := make(132)
|
|
defer delete(bits)
|
|
|
|
bits.int_array[0] = all_ones
|
|
bits.int_array[1] = all_ones
|
|
bits.int_array[2] = {0, 1, 2, 3}
|
|
testing.expect(t, all_true(bits))
|
|
|
|
bits.int_array[2] = {0, 1, 2}
|
|
testing.expect(t, !all_true(bits))
|
|
|
|
bits2 := make(1)
|
|
defer delete(bits2)
|
|
|
|
bits2.int_array[0] = {0}
|
|
testing.expect(t, all_true(bits2))
|
|
}
|
|
|
|
@(test)
|
|
test_range_true :: proc(t: ^testing.T) {
|
|
uint_max := UINT_MAX
|
|
all_ones := transmute(Int_Bits)uint_max
|
|
|
|
bits := make(192)
|
|
defer delete(bits)
|
|
|
|
// Empty range is vacuously true
|
|
testing.expect(t, range_true(bits, 0, 0))
|
|
testing.expect(t, range_true(bits, 50, 50))
|
|
// inverted range should panic under bounds checking; keep this test case out of here
|
|
|
|
// Single word, partial range
|
|
bits.int_array[0] = {3, 4, 5, 6}
|
|
testing.expect(t, range_true(bits, 3, 7))
|
|
testing.expect(t, !range_true(bits, 2, 7)) // bit 2 not set
|
|
testing.expect(t, !range_true(bits, 3, 8)) // bit 7 not set
|
|
|
|
// Single word, full word
|
|
bits.int_array[0] = all_ones
|
|
testing.expect(t, range_true(bits, 0, 64))
|
|
|
|
// Range spanning two words
|
|
bits.int_array[0] = all_ones
|
|
bits.int_array[1] = {0, 1, 2, 3}
|
|
testing.expect(t, range_true(bits, 60, 68)) // bits 60-63 in word 0, bits 0-3 in word 1
|
|
testing.expect(t, !range_true(bits, 60, 69)) // bit 68 (4 in word 1) not set
|
|
|
|
// Range spanning three words with full middle word
|
|
bits.int_array[0] = all_ones
|
|
bits.int_array[1] = all_ones
|
|
bits.int_array[2] = {0, 1, 2, 3}
|
|
testing.expect(t, range_true(bits, 60, 132)) // partial first, full middle, partial last
|
|
testing.expect(t, !range_true(bits, 60, 133)) // bit 132 (4 in word 2) not set
|
|
|
|
// Middle word not all set
|
|
bits.int_array[1] = all_ones - {32}
|
|
testing.expect(t, !range_true(bits, 60, 132))
|
|
|
|
// Boundary: range ends exactly at word boundary
|
|
bits.int_array[0] = all_ones
|
|
bits.int_array[1] = all_ones
|
|
testing.expect(t, range_true(bits, 32, 128))
|
|
|
|
// Boundary: range starts exactly at word boundary
|
|
bits.int_array[1] = all_ones
|
|
bits.int_array[2] = all_ones
|
|
testing.expect(t, range_true(bits, 64, 192))
|
|
}
|
|
|
|
@(test)
|
|
nearest_true_handles_same_word_and_boundaries :: proc(t: ^testing.T) {
|
|
bits := make(128, context.temp_allocator)
|
|
|
|
set_true(bits, 0)
|
|
set_true(bits, 10)
|
|
set_true(bits, 20)
|
|
set_true(bits, 63)
|
|
|
|
nearest, ok := nearest_true(bits, 10)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 10)
|
|
|
|
nearest, ok = nearest_true(bits, 12)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 10)
|
|
|
|
nearest, ok = nearest_true(bits, 17)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 20)
|
|
|
|
nearest, ok = nearest_true(bits, 15)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 10)
|
|
|
|
nearest, ok = nearest_true(bits, 0)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 0)
|
|
|
|
nearest, ok = nearest_true(bits, 63)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 63)
|
|
}
|
|
|
|
@(test)
|
|
nearest_false_handles_same_word_and_boundaries :: proc(t: ^testing.T) {
|
|
bits := make(128, context.temp_allocator)
|
|
|
|
// Start with all bits true, then clear a few to false.
|
|
for i := 0; i < bits.length; i += 1 {
|
|
set_true(bits, i)
|
|
}
|
|
|
|
set_false(bits, 0)
|
|
set_false(bits, 10)
|
|
set_false(bits, 20)
|
|
set_false(bits, 63)
|
|
|
|
nearest, ok := nearest_false(bits, 10)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 10)
|
|
|
|
nearest, ok = nearest_false(bits, 12)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 10)
|
|
|
|
nearest, ok = nearest_false(bits, 17)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 20)
|
|
|
|
nearest, ok = nearest_false(bits, 15)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 10)
|
|
|
|
nearest, ok = nearest_false(bits, 0)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 0)
|
|
|
|
nearest, ok = nearest_false(bits, 63)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 63)
|
|
}
|
|
|
|
@(test)
|
|
nearest_false_scans_across_words_and_returns_false_when_all_true :: proc(t: ^testing.T) {
|
|
bits := make(192, context.temp_allocator)
|
|
|
|
// Start with all bits true, then clear a couple far apart.
|
|
for i := 0; i < bits.length; i += 1 {
|
|
set_true(bits, i)
|
|
}
|
|
|
|
set_false(bits, 5)
|
|
set_false(bits, 130)
|
|
|
|
nearest, ok := nearest_false(bits, 96)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 130)
|
|
|
|
// Restore the only zero bits so there are no zeros left.
|
|
set_true(bits, 5)
|
|
set_true(bits, 130)
|
|
|
|
nearest, ok = nearest_false(bits, 96)
|
|
testing.expect(t, !ok)
|
|
}
|
|
|
|
@(test)
|
|
nearest_true_scans_across_words_and_returns_false_when_empty :: proc(t: ^testing.T) {
|
|
bits := make(192, context.temp_allocator)
|
|
|
|
set_true(bits, 5)
|
|
set_true(bits, 130)
|
|
|
|
nearest, ok := nearest_true(bits, 96)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 130)
|
|
|
|
zero(bits)
|
|
|
|
nearest, ok = nearest_true(bits, 96)
|
|
testing.expect(t, !ok)
|
|
}
|
|
|
|
@(test)
|
|
nearest_false_handles_last_word_partial_length :: proc(t: ^testing.T) {
|
|
bits := make(130, context.temp_allocator)
|
|
|
|
// Start with all bits true, then clear the first and last valid bits.
|
|
for i := 0; i < bits.length; i += 1 {
|
|
set_true(bits, i)
|
|
}
|
|
|
|
set_false(bits, 0)
|
|
set_false(bits, 129)
|
|
|
|
nearest, ok := nearest_false(bits, 128)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 129)
|
|
|
|
nearest, ok = nearest_false(bits, 127)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 129)
|
|
}
|
|
|
|
@(test)
|
|
nearest_true_handles_last_word_partial_length :: proc(t: ^testing.T) {
|
|
bits := make(130, context.temp_allocator)
|
|
|
|
set_true(bits, 0)
|
|
set_true(bits, 129)
|
|
|
|
nearest, ok := nearest_true(bits, 128)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 129)
|
|
|
|
nearest, ok = nearest_true(bits, 127)
|
|
testing.expect(t, ok)
|
|
testing.expect_value(t, nearest, 129)
|
|
}
|
|
|
|
@(test)
|
|
iterator_basic_mixed_bits :: proc(t: ^testing.T) {
|
|
// Use non-word-aligned length to test partial last word handling
|
|
bits := make(100, context.temp_allocator)
|
|
|
|
// Set specific bits: 0, 3, 64, 99 (last valid index)
|
|
set_true(bits, 0)
|
|
set_true(bits, 3)
|
|
set_true(bits, 64)
|
|
set_true(bits, 99)
|
|
|
|
expected_true_indices := [?]int{0, 3, 64, 99}
|
|
|
|
// Test iterate - should return all 100 bits with correct set state
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for is_set, idx in iterate(&it) {
|
|
expected_set := slice.contains(expected_true_indices[:], idx)
|
|
testing.expectf(
|
|
t,
|
|
is_set == expected_set,
|
|
"iterate: bit %d expected is_set=%v, got %v",
|
|
idx,
|
|
expected_set,
|
|
is_set,
|
|
)
|
|
testing.expectf(t, idx == count, "iterate: expected sequential idx=%d, got %d", count, idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 100, "iterate: expected 100 iterations, got %d", count)
|
|
}
|
|
|
|
// Test iterate_true - should only return the 4 set bits
|
|
{
|
|
it := iterator(&bits)
|
|
result_indices := builtin.make([dynamic]int, allocator = context.temp_allocator)
|
|
for idx in iterate_true(&it) {
|
|
append(&result_indices, idx)
|
|
}
|
|
testing.expectf(
|
|
t,
|
|
len(result_indices) == 4,
|
|
"iterate_true: expected 4 set bits, got %d",
|
|
len(result_indices),
|
|
)
|
|
for expected_idx, i in expected_true_indices {
|
|
testing.expectf(
|
|
t,
|
|
result_indices[i] == expected_idx,
|
|
"iterate_true: at position %d expected idx=%d, got %d",
|
|
i,
|
|
expected_idx,
|
|
result_indices[i],
|
|
)
|
|
}
|
|
}
|
|
|
|
// Test iterate_false - should return all 96 unset bits
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for idx in iterate_false(&it) {
|
|
testing.expectf(
|
|
t,
|
|
!slice.contains(expected_true_indices[:], idx),
|
|
"iterate_false: returned set bit index %d",
|
|
idx,
|
|
)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 96, "iterate_false: expected 96 unset bits, got %d", count)
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
iterator_all_false_bits :: proc(t: ^testing.T) {
|
|
// Use non-word-aligned length
|
|
bits := make(100, context.temp_allocator)
|
|
// All bits default to false, no need to set anything
|
|
|
|
// Test iterate - should return all 100 bits as false
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for is_set, idx in iterate(&it) {
|
|
testing.expectf(t, !is_set, "iterate: bit %d expected is_set=false, got true", idx)
|
|
testing.expectf(t, idx == count, "iterate: expected sequential idx=%d, got %d", count, idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 100, "iterate: expected 100 iterations, got %d", count)
|
|
}
|
|
|
|
// Test iterate_true - should return nothing
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for idx in iterate_true(&it) {
|
|
testing.expectf(t, false, "iterate_true: unexpectedly returned idx=%d when all bits are false", idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 0, "iterate_true: expected 0 iterations, got %d", count)
|
|
}
|
|
|
|
// Test iterate_false - should return all 100 indices
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for idx in iterate_false(&it) {
|
|
testing.expectf(t, idx == count, "iterate_false: expected sequential idx=%d, got %d", count, idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 100, "iterate_false: expected 100 iterations, got %d", count)
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
iterator_all_true_bits :: proc(t: ^testing.T) {
|
|
// Use non-word-aligned length
|
|
bits := make(100, context.temp_allocator)
|
|
// Set all bits to true
|
|
for i := 0; i < bits.length; i += 1 {
|
|
set_true(bits, i)
|
|
}
|
|
|
|
// Test iterate - should return all 100 bits as true
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for is_set, idx in iterate(&it) {
|
|
testing.expectf(t, is_set, "iterate: bit %d expected is_set=true, got false", idx)
|
|
testing.expectf(t, idx == count, "iterate: expected sequential idx=%d, got %d", count, idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 100, "iterate: expected 100 iterations, got %d", count)
|
|
}
|
|
|
|
// Test iterate_true - should return all 100 indices
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for idx in iterate_true(&it) {
|
|
testing.expectf(t, idx == count, "iterate_true: expected sequential idx=%d, got %d", count, idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 100, "iterate_true: expected 100 iterations, got %d", count)
|
|
}
|
|
|
|
// Test iterate_false - should return nothing
|
|
{
|
|
it := iterator(&bits)
|
|
count := 0
|
|
for idx in iterate_false(&it) {
|
|
testing.expectf(t, false, "iterate_false: unexpectedly returned idx=%d when all bits are true", idx)
|
|
count += 1
|
|
}
|
|
testing.expectf(t, count == 0, "iterate_false: expected 0 iterations, got %d", count)
|
|
}
|
|
}
|