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:
parent
4fbf7d426e
commit
f840742724
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue