fix(bitmap): correct growth at byte boundaries, add explicit bounds checks, and adjust bitwise ops

- grow now accounts for bit index + 1 when computing required byte length to avoid off-by-one at byte boundaries
- contains/remove use explicit bounds checks (idx >= this.bits.length) instead of relying on truthy checks
- and clears tail bytes not covered by the shorter operand
- or/xor expand the result to encompass a longer operand so all bits are preserved
- update behavior to be more predictable and safer; tests pass
This commit is contained in:
Eugene 2025-12-01 21:58:51 +03:00
parent 4fbf7d426e
commit f840742724
1 changed files with 35 additions and 15 deletions

View File

@ -23,7 +23,7 @@ export class Bitmap {
} }
public grow(x: number): void { public grow(x: number): void {
const arrLength = Math.max(1, computeBitsArrayLength(x)); const arrLength = Math.max(1, computeBitsArrayLength(x + 1));
if (arrLength <= this.bits.length) return; if (arrLength <= this.bits.length) return;
const prev = this.bits; const prev = this.bits;
this.bits = new Uint8Array(arrLength); this.bits = new Uint8Array(arrLength);
@ -38,15 +38,15 @@ export class Bitmap {
public remove(x: number): void { public remove(x: number): void {
const idx = getBitPositionIndex(x); const idx = getBitPositionIndex(x);
if (!this.bits[idx]) return; if (idx >= this.bits.length) return;
const mask = getMask(getBitPosition(x)); const mask = getMask(getBitPosition(x));
this.bits[idx] &= ~mask; this.bits[idx] &= ~mask;
} }
public contains(x: number): boolean { public contains(x: number): boolean {
const [idx, pos] = [getBitPositionIndex(x), getBitPosition(x)]; const idx = getBitPositionIndex(x);
if (!this.bits[idx]) return false; if (idx >= this.bits.length) return false;
const mask = getMask(pos); const mask = getMask(getBitPosition(x));
return (this.bits[idx] & mask) === mask; return (this.bits[idx] & mask) === mask;
} }
@ -71,6 +71,10 @@ export class Bitmap {
result.bits[i] &= otherBits[i]; result.bits[i] &= otherBits[i];
} }
for (let i = minlen; i < result.bits.length; i++) {
result.bits[i] = 0;
}
return result; return result;
} }
@ -88,11 +92,19 @@ export class Bitmap {
public or(bitmap: Bitmap): Bitmap { public or(bitmap: Bitmap): Bitmap {
const otherBits = (bitmap as Bitmap).bits; const otherBits = (bitmap as Bitmap).bits;
const result = this.clone() as Bitmap; let result = this.clone() as Bitmap;
const minlen = Math.min(this.bits.length, otherBits.length);
for (let i = 0; i < minlen; i++) { if (otherBits.length > result.bits.length) {
result.bits[i] |= otherBits[i]; const extended = new Uint8Array(otherBits.length);
extended.set(result.bits);
result.bits = extended;
}
const maxlen = Math.max(result.bits.length, otherBits.length);
for (let i = 0; i < maxlen; i++) {
const a = result.bits[i] ?? 0;
const b = otherBits[i] ?? 0;
result.bits[i] = a | b;
} }
return result; return result;
@ -100,11 +112,19 @@ export class Bitmap {
public xor(bitmap: Bitmap): Bitmap { public xor(bitmap: Bitmap): Bitmap {
const otherBits = (bitmap as Bitmap).bits; const otherBits = (bitmap as Bitmap).bits;
const result = this.clone() as Bitmap; let result = this.clone() as Bitmap;
const minlen = Math.min(this.bits.length, otherBits.length);
for (let i = 0; i < minlen; i++) { if (otherBits.length > result.bits.length) {
result.bits[i] ^= otherBits[i]; const extended = new Uint8Array(otherBits.length);
extended.set(result.bits);
result.bits = extended;
}
const maxlen = Math.max(result.bits.length, otherBits.length);
for (let i = 0; i < maxlen; i++) {
const a = result.bits[i] ?? 0;
const b = otherBits[i] ?? 0;
result.bits[i] = a ^ b;
} }
return result; return result;
@ -113,9 +133,9 @@ export class Bitmap {
public range(fn: (x: number) => boolean | void): void { public range(fn: (x: number) => boolean | void): void {
let needContinueIterating = true; let needContinueIterating = true;
for (let i = 0; i < this.bits.length; i++) { for (let i = 0; i < this.bits.length; i++) {
let x = this.bits[i]; let byte = this.bits[i];
for (let j = 0; j < 8; j++) { for (let j = 0; j < 8; j++) {
const bit = (x >> j) & 1; const bit = (byte >> j) & 1;
if (bit === 0) continue; if (bit === 0) continue;
needContinueIterating = fn(i * 8 + j) ?? true; needContinueIterating = fn(i * 8 + j) ?? true;
if (!needContinueIterating) break; if (!needContinueIterating) break;