Skip to content

Commit

Permalink
implement i128/u128 types (credit goes to @Jabolol)
Browse files Browse the repository at this point in the history
  • Loading branch information
MierenManz committed Feb 4, 2025
1 parent 68d2479 commit 1ebb18a
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 0 deletions.
116 changes: 116 additions & 0 deletions src/non-standard-numbers/big_numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { type Options, SizedType } from "../types/mod.ts";
import { isLittleEndian } from "../util.ts";

export class U128 extends SizedType<bigint> {
constructor(
readonly littleEndian: boolean = isLittleEndian,
/**
* Clang, GCC and rust 1.78 (with LLVM 18) say 16 byte alignment.
*
* Older rust versions (or rust with older LLVM versions) say 8 byte alignment.
*
* Due to compatiblity reason we allow 8 byte alignment with these older versions.
*
* But by default it uses the now correct 16 byte alignment.
*
* See [Rust's blogpost](https://blog.rust-lang.org/2024/03/30/i128-layout-update.html) for more details
*/
alignment: 8 | 16 = 16,
) {
super(16, alignment);
}

override readPacked(
dt: DataView,
options: Options = { byteOffset: 0 },
): bigint {
const partOne = dt.getBigUint64(options.byteOffset, this.littleEndian);
const partTwo = dt.getBigUint64(options.byteOffset + 8, this.littleEndian);
super.incrementOffset(options);
// deno-fmt-ignore
return this.littleEndian
? (partTwo << 64n) | partOne
: (partOne << 64n) | partTwo;
}

override writePacked(
value: bigint,
dt: DataView,
options: Options = { byteOffset: 0 },
): void {
const hi = value >> 64n;
dt.setBigUint64(
options.byteOffset,
this.littleEndian ? hi : value,
this.littleEndian,
);
dt.setBigUint64(
options.byteOffset + 8,
this.littleEndian ? value : hi,
this.littleEndian,
);

super.incrementOffset(options);
}
}

export class I128 extends SizedType<bigint> {
constructor(
readonly littleEndian: boolean = isLittleEndian,
/**
* Clang, GCC and rust 1.78 (with LLVM 18) say 16 byte alignment.
*
* Older rust versions (or rust with older LLVM versions) say 8 byte alignment.
*
* Due to compatiblity reason we allow 8 byte alignment with these older versions.
*
* But by default it uses the now correct 16 byte alignment.
*
* See [Rust's blogpost](https://blog.rust-lang.org/2024/03/30/i128-layout-update.html) for more details
*/
alignment: 8 | 16 = 16,
) {
super(16, alignment);
}

override readPacked(
dt: DataView,
options: Options = { byteOffset: 0 },
): bigint {
const partOne = dt.getBigInt64(options.byteOffset, this.littleEndian);
const partTwo = dt.getBigInt64(options.byteOffset + 8, this.littleEndian);
super.incrementOffset(options);
// deno-fmt-ignore
return this.littleEndian
? (partTwo << 64n) | partOne
: (partOne << 64n) | partTwo;
}

override writePacked(
value: bigint,
dt: DataView,
options: Options = { byteOffset: 0 },
): void {
const hi = value >> 64n;
dt.setBigInt64(
options.byteOffset,
this.littleEndian ? hi : value,
this.littleEndian,
);
dt.setBigInt64(
options.byteOffset + 8,
this.littleEndian ? value : hi,
this.littleEndian,
);

super.incrementOffset(options);
}
}

export const u128le: U128 = new U128(true);
export const u128be: U128 = new U128(false);
export const u128: U128 = new U128();

export const i128le: I128 = new I128(true);
export const i128be: I128 = new I128(false);
export const i128: I128 = new I128();
78 changes: 78 additions & 0 deletions src/non-standard-numbers/big_numbers_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { assertEquals, assertThrows } from "../../test_deps.ts";
import { i128be, i128le, u128be, u128le } from "./big_numbers.ts";

Deno.test("u128", async (t) => {
const buff = new ArrayBuffer(16);
const dt = new DataView(buff);
const value = 12n;

await t.step("read", () => {
// Little endian
const lo = value & 0xffffffffffffffffn;
const hi = value >> 64n;
dt.setBigUint64(0, lo, true);
dt.setBigUint64(8, hi, true);
assertEquals(value, u128le.read(dt));
// Big endian
dt.setBigUint64(0, hi, false);
dt.setBigUint64(8, lo, false);
assertEquals(value, u128be.read(dt));
});

await t.step("write", () => {
// Little endian
u128le.write(value, dt);
let lo = dt.getBigInt64(0, true);
let hi = dt.getBigInt64(8, true);
assertEquals(value, (lo << 64n) | hi);
// Big endian
u128be.write(value, dt);
lo = dt.getBigInt64(8, false);
hi = dt.getBigInt64(0, false);
assertEquals(value, (lo << 64n) | hi);
});

await t.step("OOB Read", () => {
assertThrows(() => {
u128le.read(dt, { byteOffset: 9 });
}, RangeError);
});
});

Deno.test("i128", async (t) => {
const buff = new ArrayBuffer(16);
const dt = new DataView(buff);
const value = 12n;

await t.step("read", () => {
// Little endian
const lo = value & 0xffffffffffffffffn;
const hi = value >> 64n;
dt.setBigUint64(0, lo, true);
dt.setBigUint64(8, hi, true);
assertEquals(value, i128le.read(dt));
// Big endian
dt.setBigUint64(0, hi, false);
dt.setBigUint64(8, lo, false);
assertEquals(value, i128be.read(dt));
});

await t.step("write", () => {
// Little endian
i128le.write(value, dt);
let lo = dt.getBigInt64(0, true);
let hi = dt.getBigInt64(8, true);
assertEquals(value, (lo << 64n) | hi);
// Big endian
i128be.write(value, dt);
lo = dt.getBigInt64(8, false);
hi = dt.getBigInt64(0, false);
assertEquals(value, (lo << 64n) | hi);
});

await t.step("OOB Read", () => {
assertThrows(() => {
i128le.read(dt, { byteOffset: 9 });
}, RangeError);
});
});
2 changes: 2 additions & 0 deletions src/non-standard-numbers/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./small_numbers.ts";
export * from "./big_numbers.ts";
File renamed without changes.

0 comments on commit 1ebb18a

Please sign in to comment.