Skip to content

Commit

Permalink
feat: extract bitwise
Browse files Browse the repository at this point in the history
  • Loading branch information
loks0n committed Dec 28, 2024
1 parent 951f104 commit d47d4e7
Show file tree
Hide file tree
Showing 20 changed files with 858 additions and 320 deletions.
259 changes: 148 additions & 111 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,9 @@
"release-it": "^17.11.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5",
"vite": "^6.0.6",
"vite-node": "^2.1.8",
"vite-plugin-dts": "^4.4.0",
"vitest": "^2.1.8"
},
"dependencies": {
"long": "^5.2.3"
}
}
138 changes: 138 additions & 0 deletions src/bitwise/long.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { describe, expect, it } from 'vitest';
import { BitwiseLong, BitwiseLongAlloc, LongUtils } from './long';
import { Long } from './types';

describe('BitwiseLong and BitwiseLongAlloc', () => {
const implementations = [BitwiseLong, BitwiseLongAlloc];
const createLong = (low: number, high: number): Long =>
new Uint32Array([low, high]);

implementations.forEach((implementation) => {
const name =
implementation === BitwiseLong ? 'BitwiseLong' : 'BitwiseLongAlloc';

describe(name, () => {
describe('and', () => {
it('performs bitwise AND operation', () => {
const a = createLong(0b1010, 0b1100);
const b = createLong(0b1100, 0b1010);
const result = implementation.and(a, b);
expect(Array.from(result)).toEqual([0b1000, 0b1000]);
});
});

describe('or', () => {
it('performs bitwise OR operation', () => {
const a = createLong(0b1010, 0b1100);
const b = createLong(0b1100, 0b1010);
const result = implementation.or(a, b);
expect(Array.from(result)).toEqual([0b1110, 0b1110]);
});
});

describe('xor', () => {
it('performs bitwise XOR operation', () => {
const a = createLong(0b1010, 0b1100);
const b = createLong(0b1100, 0b1010);
const result = implementation.xor(a, b);
expect(Array.from(result)).toEqual([0b0110, 0b0110]);
});
});

describe('not', () => {
it('performs bitwise NOT operation', () => {
const a = createLong(0b1010, 0b1100);
const result = implementation.not(a);
expect(Array.from(result)).toEqual([0xfffffff5, 0xfffffff3]);
});
});

describe('rotLeft', () => {
it('rotates bits left within 32-bit boundary', () => {
const a = createLong(0b1, 0);
const result = implementation.rotLeft(a, 1);
expect(Array.from(result)).toEqual([0b10, 0]);
});

it('rotates bits left across 32-bit boundary', () => {
const a = createLong(0x80000000, 0);
const result = implementation.rotLeft(a, 1);
expect(Array.from(result)).toEqual([0, 1]);
});

it('handles rotation greater than 63 bits', () => {
const a = createLong(0b1, 0);
const result = implementation.rotLeft(a, 65);
expect(Array.from(result)).toEqual([0b10, 0]);
});
});

describe('rotRight', () => {
it('rotates bits right within 32-bit boundary', () => {
const a = createLong(0b10, 0);
const result = implementation.rotRight(a, 1);
expect(Array.from(result)).toEqual([0b1, 0]);
});

it('rotates bits right across 32-bit boundary', () => {
const a = createLong(0, 1);
const result = implementation.rotRight(a, 1);
expect(Array.from(result)).toEqual([0x80000000, 0]);
});

it('handles rotation greater than 63 bits', () => {
const a = createLong(0b10, 0);
const result = implementation.rotRight(a, 65);
expect(Array.from(result)).toEqual([0b1, 0]);
});
});

describe('cardinality', () => {
it('counts number of set bits', () => {
const a = createLong(0b1010, 0b1100);
expect(implementation.cardinality(a)).toBe(4);
});
});

describe('decompose', () => {
it('decomposes long into powers of 2', () => {
const a = createLong(0b1010, 0b1100);
const result = implementation.decompose(a);
expect(result.length).toBe(4);
expect(result.map((x) => Array.from(x))).toEqual([
[2, 0],
[8, 0],
[0, 4],
[0, 8],
]);
});
});
});
});
});

describe('LongUtils', () => {
describe('create', () => {
it('creates a Long with specified low and high values', () => {
const result = LongUtils.create(0x12345678, 0x90abcdef);
expect(Array.from(result)).toEqual([0x12345678, 0x90abcdef]);
});

it('handles values outside 32-bit range', () => {
const result = LongUtils.create(0x100000000, 0x100000000);
expect(Array.from(result)).toEqual([0, 0]);
});
});

describe('createAlloc', () => {
it('creates a new Long with specified low and high values', () => {
const result = LongUtils.createAlloc(0x12345678, 0x90abcdef);
expect(Array.from(result)).toEqual([0x12345678, 0x90abcdef]);
});

it('handles values outside 32-bit range', () => {
const result = LongUtils.createAlloc(0x100000000, 0x100000000);
expect(Array.from(result)).toEqual([0, 0]);
});
});
});
198 changes: 198 additions & 0 deletions src/bitwise/long.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { IBitwiseOps, Long } from './types';
import { BitwiseNumber } from './number';

export const BitwiseLongAlloc: IBitwiseOps<Long> = {
and(...args: Long[]): Long {
return new Uint32Array(
args.reduce(
(a, b) => [a[0] & b[0], a[1] & b[1]],
[0xffffffff, 0xffffffff]
)
);
},

or(...args: Long[]): Long {
return new Uint32Array(
args.reduce((a, b) => [a[0] | b[0], a[1] | b[1]], [0, 0])
);
},

xor(a: Long, b: Long): Long {
return new Uint32Array([a[0] ^ b[0], a[1] ^ b[1]]);
},

not(a: Long): Long {
return new Uint32Array([~a[0] >>> 0, ~a[1] >>> 0]);
},

rotLeft(a: Long, shift: number): Long {
const rotation = shift & 63;
if (rotation === 0) {
return new Uint32Array(a);
}

if (rotation < 32) {
const lowToHigh = a[0] >>> (32 - rotation);
const highToLow = a[1] >>> (32 - rotation);
return new Uint32Array([
((a[0] << rotation) | highToLow) >>> 0,
((a[1] << rotation) | lowToHigh) >>> 0,
]);
}

const actualRotation = rotation - 32;
const lowToHigh = a[0] << actualRotation;
const highToLow = a[1] << actualRotation;
return new Uint32Array([
((a[1] >>> (32 - actualRotation)) | highToLow) >>> 0,
((a[0] >>> (32 - actualRotation)) | lowToHigh) >>> 0,
]);
},

rotRight(a: Long, shift: number): Long {
const rotation = shift & 63;
if (rotation === 0) {
return new Uint32Array(a);
}

if (rotation < 32) {
const lowToHigh = a[0] << (32 - rotation);
const highToLow = a[1] << (32 - rotation);
return new Uint32Array([
((a[0] >>> rotation) | highToLow) >>> 0,
((a[1] >>> rotation) | lowToHigh) >>> 0,
]);
}

const actualRotation = rotation - 32;
return new Uint32Array([
(a[1] >>> actualRotation) >>> 0,
(a[0] >>> actualRotation) >>> 0,
]);
},

cardinality(a: Long): number {
return BitwiseNumber.cardinality(a[0]) + BitwiseNumber.cardinality(a[1]);
},

decompose(a: Long): Long[] {
const result: Long[] = [];
let remainingLow = a[0];
let remainingHigh = a[1];

for (let bit = 1; remainingLow; bit <<= 1) {
if (remainingLow & bit) {
result.push(new Uint32Array([bit, 0]));
remainingLow ^= bit;
}
}

for (let bit = 1; remainingHigh; bit <<= 1) {
if (remainingHigh & bit) {
result.push(new Uint32Array([0, bit]));
remainingHigh ^= bit;
}
}

return result;
},
} as IBitwiseOps<Long>;

const TEMP_AND = new Uint32Array(2);
const TEMP_OR = new Uint32Array(2);
const TEMP_XOR = new Uint32Array(2);
const TEMP_NOT = new Uint32Array(2);
const TEMP_ROT_LEFT = new Uint32Array(2);
const TEMP_ROT_RIGHT = new Uint32Array(2);

export const BitwiseLong: IBitwiseOps<Long> = {
and(...args: Long[]): Long {
TEMP_AND[0] = args.reduce((a, b) => a & b[0], 0xffffffff);
TEMP_AND[1] = args.reduce((a, b) => a & b[1], 0xffffffff);
return TEMP_AND;
},

or(...args: Long[]): Long {
TEMP_OR[0] = args.reduce((a, b) => a | b[0], 0);
TEMP_OR[1] = args.reduce((a, b) => a | b[1], 0);
return TEMP_OR;
},

xor(a: Long, b: Long): Long {
TEMP_XOR[0] = a[0] ^ b[0];
TEMP_XOR[1] = a[1] ^ b[1];
return TEMP_XOR;
},

not(a: Long): Long {
TEMP_NOT[0] = ~a[0] >>> 0;
TEMP_NOT[1] = ~a[1] >>> 0;
return TEMP_NOT;
},

rotLeft(a: Long, shift: number): Long {
const rotation = shift & 63;
if (rotation === 0) {
TEMP_ROT_LEFT.set(a);
return TEMP_ROT_LEFT;
}

if (rotation < 32) {
const lowToHigh = a[0] >>> (32 - rotation);
const highToLow = a[1] >>> (32 - rotation);
TEMP_ROT_LEFT[0] = ((a[0] << rotation) | highToLow) >>> 0;
TEMP_ROT_LEFT[1] = ((a[1] << rotation) | lowToHigh) >>> 0;
return TEMP_ROT_LEFT;
}

const actualRotation = rotation - 32;
const lowToHigh = a[0] << actualRotation;
const highToLow = a[1] << actualRotation;
TEMP_ROT_LEFT[0] = ((a[1] >>> (32 - actualRotation)) | highToLow) >>> 0;
TEMP_ROT_LEFT[1] = ((a[0] >>> (32 - actualRotation)) | lowToHigh) >>> 0;
return TEMP_ROT_LEFT;
},

rotRight(a: Long, shift: number): Long {
const rotation = shift & 63;
if (rotation === 0) {
TEMP_ROT_RIGHT.set(a);
return TEMP_ROT_RIGHT;
}

if (rotation < 32) {
const lowToHigh = a[0] << (32 - rotation);
const highToLow = a[1] << (32 - rotation);
TEMP_ROT_RIGHT[0] = ((a[0] >>> rotation) | highToLow) >>> 0;
TEMP_ROT_RIGHT[1] = ((a[1] >>> rotation) | lowToHigh) >>> 0;
return TEMP_ROT_RIGHT;
}

const actualRotation = rotation - 32;
TEMP_ROT_RIGHT[0] = (a[1] >>> actualRotation) >>> 0;
TEMP_ROT_RIGHT[1] = (a[0] >>> actualRotation) >>> 0;
return TEMP_ROT_RIGHT;
},

cardinality(a: Long): number {
return BitwiseLongAlloc.cardinality(a);
},

decompose(a: Long): Long[] {
return BitwiseLongAlloc.decompose(a);
},
} as IBitwiseOps<Long>;

const TEMP_CREATE = new Uint32Array(2);

export const LongUtils = {
createAlloc(low: number, high: number): Long {
return new Uint32Array([low >>> 0, high >>> 0]);
},

create(low: number, high: number): Long {
TEMP_CREATE[0] = low >>> 0;
TEMP_CREATE[1] = high >>> 0;
return TEMP_CREATE;
},
};
Loading

0 comments on commit d47d4e7

Please sign in to comment.