Add @cosmjs/encoding and @cosmjs/math
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import { Decimal } from "./decimal";
|
||||
|
||||
describe("Decimal", () => {
|
||||
describe("fromAtomics", () => {
|
||||
it("leads to correct atomics value", () => {
|
||||
expect(Decimal.fromAtomics("1", 0).atomics).toEqual("1");
|
||||
expect(Decimal.fromAtomics("1", 1).atomics).toEqual("1");
|
||||
expect(Decimal.fromAtomics("1", 2).atomics).toEqual("1");
|
||||
|
||||
expect(Decimal.fromAtomics("1", 5).atomics).toEqual("1");
|
||||
expect(Decimal.fromAtomics("2", 5).atomics).toEqual("2");
|
||||
expect(Decimal.fromAtomics("3", 5).atomics).toEqual("3");
|
||||
expect(Decimal.fromAtomics("10", 5).atomics).toEqual("10");
|
||||
expect(Decimal.fromAtomics("20", 5).atomics).toEqual("20");
|
||||
expect(Decimal.fromAtomics("30", 5).atomics).toEqual("30");
|
||||
expect(Decimal.fromAtomics("100000000000000000000000", 5).atomics).toEqual("100000000000000000000000");
|
||||
expect(Decimal.fromAtomics("200000000000000000000000", 5).atomics).toEqual("200000000000000000000000");
|
||||
expect(Decimal.fromAtomics("300000000000000000000000", 5).atomics).toEqual("300000000000000000000000");
|
||||
|
||||
expect(Decimal.fromAtomics("44", 5).atomics).toEqual("44");
|
||||
expect(Decimal.fromAtomics("044", 5).atomics).toEqual("44");
|
||||
expect(Decimal.fromAtomics("0044", 5).atomics).toEqual("44");
|
||||
expect(Decimal.fromAtomics("00044", 5).atomics).toEqual("44");
|
||||
});
|
||||
|
||||
it("reads fractional digits correctly", () => {
|
||||
expect(Decimal.fromAtomics("44", 0).toString()).toEqual("44");
|
||||
expect(Decimal.fromAtomics("44", 1).toString()).toEqual("4.4");
|
||||
expect(Decimal.fromAtomics("44", 2).toString()).toEqual("0.44");
|
||||
expect(Decimal.fromAtomics("44", 3).toString()).toEqual("0.044");
|
||||
expect(Decimal.fromAtomics("44", 4).toString()).toEqual("0.0044");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromUserInput", () => {
|
||||
it("throws helpful error message for invalid characters", () => {
|
||||
expect(() => Decimal.fromUserInput(" 13", 5)).toThrowError(/invalid character at position 1/i);
|
||||
expect(() => Decimal.fromUserInput("1,3", 5)).toThrowError(/invalid character at position 2/i);
|
||||
expect(() => Decimal.fromUserInput("13-", 5)).toThrowError(/invalid character at position 3/i);
|
||||
expect(() => Decimal.fromUserInput("13/", 5)).toThrowError(/invalid character at position 3/i);
|
||||
expect(() => Decimal.fromUserInput("13\\", 5)).toThrowError(/invalid character at position 3/i);
|
||||
});
|
||||
|
||||
it("throws for more than one separator", () => {
|
||||
expect(() => Decimal.fromUserInput("1.3.5", 5)).toThrowError(/more than one separator found/i);
|
||||
expect(() => Decimal.fromUserInput("1..3", 5)).toThrowError(/more than one separator found/i);
|
||||
expect(() => Decimal.fromUserInput("..", 5)).toThrowError(/more than one separator found/i);
|
||||
});
|
||||
|
||||
it("throws for separator only", () => {
|
||||
expect(() => Decimal.fromUserInput(".", 5)).toThrowError(/fractional part missing/i);
|
||||
});
|
||||
|
||||
it("throws for more fractional digits than supported", () => {
|
||||
expect(() => Decimal.fromUserInput("44.123456", 5)).toThrowError(
|
||||
/got more fractional digits than supported/i,
|
||||
);
|
||||
expect(() => Decimal.fromUserInput("44.1", 0)).toThrowError(
|
||||
/got more fractional digits than supported/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws for fractional digits that are not non-negative integers", () => {
|
||||
// no integer
|
||||
expect(() => Decimal.fromUserInput("1", Number.NaN)).toThrowError(
|
||||
/fractional digits is not an integer/i,
|
||||
);
|
||||
expect(() => Decimal.fromUserInput("1", Number.POSITIVE_INFINITY)).toThrowError(
|
||||
/fractional digits is not an integer/i,
|
||||
);
|
||||
expect(() => Decimal.fromUserInput("1", Number.NEGATIVE_INFINITY)).toThrowError(
|
||||
/fractional digits is not an integer/i,
|
||||
);
|
||||
expect(() => Decimal.fromUserInput("1", 1.78945544484)).toThrowError(
|
||||
/fractional digits is not an integer/i,
|
||||
);
|
||||
|
||||
// negative
|
||||
expect(() => Decimal.fromUserInput("1", -1)).toThrowError(/fractional digits must not be negative/i);
|
||||
expect(() => Decimal.fromUserInput("1", Number.MIN_SAFE_INTEGER)).toThrowError(
|
||||
/fractional digits must not be negative/i,
|
||||
);
|
||||
|
||||
// exceeds supported range
|
||||
expect(() => Decimal.fromUserInput("1", 101)).toThrowError(/fractional digits must not exceed 100/i);
|
||||
});
|
||||
|
||||
it("returns correct value", () => {
|
||||
expect(Decimal.fromUserInput("44", 0).atomics).toEqual("44");
|
||||
expect(Decimal.fromUserInput("44", 1).atomics).toEqual("440");
|
||||
expect(Decimal.fromUserInput("44", 2).atomics).toEqual("4400");
|
||||
expect(Decimal.fromUserInput("44", 3).atomics).toEqual("44000");
|
||||
|
||||
expect(Decimal.fromUserInput("44.2", 1).atomics).toEqual("442");
|
||||
expect(Decimal.fromUserInput("44.2", 2).atomics).toEqual("4420");
|
||||
expect(Decimal.fromUserInput("44.2", 3).atomics).toEqual("44200");
|
||||
|
||||
expect(Decimal.fromUserInput("44.1", 6).atomics).toEqual("44100000");
|
||||
expect(Decimal.fromUserInput("44.12", 6).atomics).toEqual("44120000");
|
||||
expect(Decimal.fromUserInput("44.123", 6).atomics).toEqual("44123000");
|
||||
expect(Decimal.fromUserInput("44.1234", 6).atomics).toEqual("44123400");
|
||||
expect(Decimal.fromUserInput("44.12345", 6).atomics).toEqual("44123450");
|
||||
expect(Decimal.fromUserInput("44.123456", 6).atomics).toEqual("44123456");
|
||||
});
|
||||
|
||||
it("cuts leading zeros", () => {
|
||||
expect(Decimal.fromUserInput("4", 2).atomics).toEqual("400");
|
||||
expect(Decimal.fromUserInput("04", 2).atomics).toEqual("400");
|
||||
expect(Decimal.fromUserInput("004", 2).atomics).toEqual("400");
|
||||
});
|
||||
|
||||
it("cuts tailing zeros", () => {
|
||||
expect(Decimal.fromUserInput("4.12", 5).atomics).toEqual("412000");
|
||||
expect(Decimal.fromUserInput("4.120", 5).atomics).toEqual("412000");
|
||||
expect(Decimal.fromUserInput("4.1200", 5).atomics).toEqual("412000");
|
||||
expect(Decimal.fromUserInput("4.12000", 5).atomics).toEqual("412000");
|
||||
expect(Decimal.fromUserInput("4.120000", 5).atomics).toEqual("412000");
|
||||
expect(Decimal.fromUserInput("4.1200000", 5).atomics).toEqual("412000");
|
||||
});
|
||||
|
||||
it("interprets the empty string as zero", () => {
|
||||
expect(Decimal.fromUserInput("", 0).atomics).toEqual("0");
|
||||
expect(Decimal.fromUserInput("", 1).atomics).toEqual("0");
|
||||
expect(Decimal.fromUserInput("", 2).atomics).toEqual("0");
|
||||
expect(Decimal.fromUserInput("", 3).atomics).toEqual("0");
|
||||
});
|
||||
|
||||
it("accepts american notation with skipped leading zero", () => {
|
||||
expect(Decimal.fromUserInput(".1", 3).atomics).toEqual("100");
|
||||
expect(Decimal.fromUserInput(".12", 3).atomics).toEqual("120");
|
||||
expect(Decimal.fromUserInput(".123", 3).atomics).toEqual("123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toString", () => {
|
||||
it("displays no decimal point for full numbers", () => {
|
||||
expect(Decimal.fromUserInput("44", 0).toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("44", 1).toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("44", 2).toString()).toEqual("44");
|
||||
|
||||
expect(Decimal.fromUserInput("44", 2).toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("44.0", 2).toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("44.00", 2).toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("44.000", 2).toString()).toEqual("44");
|
||||
});
|
||||
|
||||
it("only shows significant digits", () => {
|
||||
expect(Decimal.fromUserInput("44.1", 2).toString()).toEqual("44.1");
|
||||
expect(Decimal.fromUserInput("44.10", 2).toString()).toEqual("44.1");
|
||||
expect(Decimal.fromUserInput("44.100", 2).toString()).toEqual("44.1");
|
||||
});
|
||||
|
||||
it("fills up leading zeros", () => {
|
||||
expect(Decimal.fromAtomics("3", 0).toString()).toEqual("3");
|
||||
expect(Decimal.fromAtomics("3", 1).toString()).toEqual("0.3");
|
||||
expect(Decimal.fromAtomics("3", 2).toString()).toEqual("0.03");
|
||||
expect(Decimal.fromAtomics("3", 3).toString()).toEqual("0.003");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toFloatApproximation", () => {
|
||||
it("works", () => {
|
||||
expect(Decimal.fromUserInput("0", 5).toFloatApproximation()).toEqual(0);
|
||||
expect(Decimal.fromUserInput("1", 5).toFloatApproximation()).toEqual(1);
|
||||
expect(Decimal.fromUserInput("1.5", 5).toFloatApproximation()).toEqual(1.5);
|
||||
expect(Decimal.fromUserInput("0.1", 5).toFloatApproximation()).toEqual(0.1);
|
||||
|
||||
expect(Decimal.fromUserInput("1234500000000000", 5).toFloatApproximation()).toEqual(1.2345e15);
|
||||
expect(Decimal.fromUserInput("1234500000000000.002", 5).toFloatApproximation()).toEqual(1.2345e15);
|
||||
});
|
||||
});
|
||||
|
||||
describe("plus", () => {
|
||||
it("returns correct values", () => {
|
||||
const zero = Decimal.fromUserInput("0", 5);
|
||||
expect(zero.plus(Decimal.fromUserInput("0", 5)).toString()).toEqual("0");
|
||||
expect(zero.plus(Decimal.fromUserInput("1", 5)).toString()).toEqual("1");
|
||||
expect(zero.plus(Decimal.fromUserInput("2", 5)).toString()).toEqual("2");
|
||||
expect(zero.plus(Decimal.fromUserInput("2.8", 5)).toString()).toEqual("2.8");
|
||||
expect(zero.plus(Decimal.fromUserInput("0.12345", 5)).toString()).toEqual("0.12345");
|
||||
|
||||
const one = Decimal.fromUserInput("1", 5);
|
||||
expect(one.plus(Decimal.fromUserInput("0", 5)).toString()).toEqual("1");
|
||||
expect(one.plus(Decimal.fromUserInput("1", 5)).toString()).toEqual("2");
|
||||
expect(one.plus(Decimal.fromUserInput("2", 5)).toString()).toEqual("3");
|
||||
expect(one.plus(Decimal.fromUserInput("2.8", 5)).toString()).toEqual("3.8");
|
||||
expect(one.plus(Decimal.fromUserInput("0.12345", 5)).toString()).toEqual("1.12345");
|
||||
|
||||
const oneDotFive = Decimal.fromUserInput("1.5", 5);
|
||||
expect(oneDotFive.plus(Decimal.fromUserInput("0", 5)).toString()).toEqual("1.5");
|
||||
expect(oneDotFive.plus(Decimal.fromUserInput("1", 5)).toString()).toEqual("2.5");
|
||||
expect(oneDotFive.plus(Decimal.fromUserInput("2", 5)).toString()).toEqual("3.5");
|
||||
expect(oneDotFive.plus(Decimal.fromUserInput("2.8", 5)).toString()).toEqual("4.3");
|
||||
expect(oneDotFive.plus(Decimal.fromUserInput("0.12345", 5)).toString()).toEqual("1.62345");
|
||||
|
||||
// original value remain unchanged
|
||||
expect(zero.toString()).toEqual("0");
|
||||
expect(one.toString()).toEqual("1");
|
||||
expect(oneDotFive.toString()).toEqual("1.5");
|
||||
});
|
||||
|
||||
it("throws for different fractional digits", () => {
|
||||
const zero = Decimal.fromUserInput("0", 5);
|
||||
expect(() => zero.plus(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i);
|
||||
expect(() => zero.plus(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i);
|
||||
expect(() => zero.plus(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i);
|
||||
expect(() => zero.plus(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i);
|
||||
|
||||
expect(() => zero.plus(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i);
|
||||
expect(() => zero.plus(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import BN from "bn.js";
|
||||
|
||||
// Too large values lead to massive memory usage. Limit to something sensible.
|
||||
// The largest value we need is 18 (Ether).
|
||||
const maxFractionalDigits = 100;
|
||||
|
||||
/**
|
||||
* A type for arbitrary precision, non-negative decimals.
|
||||
*
|
||||
* Instances of this class are immutable.
|
||||
*/
|
||||
export class Decimal {
|
||||
public static fromUserInput(input: string, fractionalDigits: number): Decimal {
|
||||
Decimal.verifyFractionalDigits(fractionalDigits);
|
||||
|
||||
const badCharacter = input.match(/[^0-9.]/);
|
||||
if (badCharacter) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
throw new Error(`Invalid character at position ${badCharacter.index! + 1}`);
|
||||
}
|
||||
|
||||
let whole: string;
|
||||
let fractional: string;
|
||||
|
||||
if (input.search(/\./) === -1) {
|
||||
// integer format, no separator
|
||||
whole = input;
|
||||
fractional = "";
|
||||
} else {
|
||||
const parts = input.split(".");
|
||||
switch (parts.length) {
|
||||
case 0:
|
||||
case 1:
|
||||
throw new Error("Fewer than two elements in split result. This must not happen here.");
|
||||
case 2:
|
||||
if (!parts[1]) throw new Error("Fractional part missing");
|
||||
whole = parts[0];
|
||||
fractional = parts[1].replace(/0+$/, "");
|
||||
break;
|
||||
default:
|
||||
throw new Error("More than one separator found");
|
||||
}
|
||||
}
|
||||
|
||||
if (fractional.length > fractionalDigits) {
|
||||
throw new Error("Got more fractional digits than supported");
|
||||
}
|
||||
|
||||
const quantity = `${whole}${fractional.padEnd(fractionalDigits, "0")}`;
|
||||
|
||||
return new Decimal(quantity, fractionalDigits);
|
||||
}
|
||||
|
||||
public static fromAtomics(atomics: string, fractionalDigits: number): Decimal {
|
||||
Decimal.verifyFractionalDigits(fractionalDigits);
|
||||
return new Decimal(atomics, fractionalDigits);
|
||||
}
|
||||
|
||||
private static verifyFractionalDigits(fractionalDigits: number): void {
|
||||
if (!Number.isInteger(fractionalDigits)) throw new Error("Fractional digits is not an integer");
|
||||
if (fractionalDigits < 0) throw new Error("Fractional digits must not be negative");
|
||||
if (fractionalDigits > maxFractionalDigits) {
|
||||
throw new Error(`Fractional digits must not exceed ${maxFractionalDigits}`);
|
||||
}
|
||||
}
|
||||
|
||||
public get atomics(): string {
|
||||
return this.data.atomics.toString();
|
||||
}
|
||||
|
||||
public get fractionalDigits(): number {
|
||||
return this.data.fractionalDigits;
|
||||
}
|
||||
|
||||
private readonly data: {
|
||||
readonly atomics: BN;
|
||||
readonly fractionalDigits: number;
|
||||
};
|
||||
|
||||
private constructor(atomics: string, fractionalDigits: number) {
|
||||
this.data = {
|
||||
atomics: new BN(atomics),
|
||||
fractionalDigits: fractionalDigits,
|
||||
};
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
|
||||
const whole = this.data.atomics.div(factor);
|
||||
const fractional = this.data.atomics.mod(factor);
|
||||
|
||||
if (fractional.isZero()) {
|
||||
return whole.toString();
|
||||
} else {
|
||||
const fullFractionalPart = fractional.toString().padStart(this.data.fractionalDigits, "0");
|
||||
const trimmedFractionalPart = fullFractionalPart.replace(/0+$/, "");
|
||||
return `${whole.toString()}.${trimmedFractionalPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an approximation as a float type. Only use this if no
|
||||
* exact calculation is required.
|
||||
*/
|
||||
public toFloatApproximation(): number {
|
||||
const out = Number(this.toString());
|
||||
if (Number.isNaN(out)) throw new Error("Conversion to number failed");
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* a.plus(b) returns a+b.
|
||||
*
|
||||
* Both values need to have the same fractional digits.
|
||||
*/
|
||||
public plus(b: Decimal): Decimal {
|
||||
if (this.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match");
|
||||
const sum = this.data.atomics.add(new BN(b.atomics));
|
||||
return new Decimal(sum.toString(), this.fractionalDigits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { Decimal } from "./decimal";
|
||||
export { Int53, Uint32, Uint53, Uint64 } from "./integers";
|
||||
@@ -0,0 +1,475 @@
|
||||
import { Int53, Uint32, Uint53, Uint64 } from "./integers";
|
||||
|
||||
describe("Integers", () => {
|
||||
describe("Uint32", () => {
|
||||
it("can be constructed", () => {
|
||||
expect(new Uint32(0)).toBeTruthy();
|
||||
expect(new Uint32(1)).toBeTruthy();
|
||||
expect(new Uint32(1.0)).toBeTruthy();
|
||||
expect(new Uint32(42)).toBeTruthy();
|
||||
expect(new Uint32(1000000000)).toBeTruthy();
|
||||
expect(new Uint32(2147483647)).toBeTruthy();
|
||||
expect(new Uint32(2147483648)).toBeTruthy();
|
||||
expect(new Uint32(4294967295)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("throws for invald numbers", () => {
|
||||
expect(() => new Uint32(NaN)).toThrowError(/not a number/);
|
||||
|
||||
expect(() => new Uint32(1.1)).toThrowError(/not an integer/i);
|
||||
expect(() => new Uint32(Number.NEGATIVE_INFINITY)).toThrowError(/not an integer/i);
|
||||
expect(() => new Uint32(Number.POSITIVE_INFINITY)).toThrowError(/not an integer/i);
|
||||
});
|
||||
|
||||
it("throws for values out of range", () => {
|
||||
expect(() => new Uint32(-1)).toThrowError(/not in uint32 range/);
|
||||
expect(() => new Uint32(4294967296)).toThrowError(/not in uint32 range/);
|
||||
expect(() => new Uint32(Number.MIN_SAFE_INTEGER)).toThrowError(/not in uint32 range/);
|
||||
expect(() => new Uint32(Number.MAX_SAFE_INTEGER)).toThrowError(/not in uint32 range/);
|
||||
});
|
||||
|
||||
it("can convert to number", () => {
|
||||
expect(new Uint32(0).toNumber()).toEqual(0);
|
||||
expect(new Uint32(1).toNumber()).toEqual(1);
|
||||
expect(new Uint32(42).toNumber()).toEqual(42);
|
||||
expect(new Uint32(1000000000).toNumber()).toEqual(1000000000);
|
||||
expect(new Uint32(2147483647).toNumber()).toEqual(2147483647);
|
||||
expect(new Uint32(2147483648).toNumber()).toEqual(2147483648);
|
||||
expect(new Uint32(4294967295).toNumber()).toEqual(4294967295);
|
||||
});
|
||||
|
||||
it("can convert to string", () => {
|
||||
expect(new Uint32(0).toString()).toEqual("0");
|
||||
expect(new Uint32(1).toString()).toEqual("1");
|
||||
expect(new Uint32(42).toString()).toEqual("42");
|
||||
expect(new Uint32(1000000000).toString()).toEqual("1000000000");
|
||||
expect(new Uint32(2147483647).toString()).toEqual("2147483647");
|
||||
expect(new Uint32(2147483648).toString()).toEqual("2147483648");
|
||||
expect(new Uint32(4294967295).toString()).toEqual("4294967295");
|
||||
});
|
||||
|
||||
describe("toBytesBigEndian", () => {
|
||||
it("works", () => {
|
||||
expect(new Uint32(0).toBytesBigEndian()).toEqual(new Uint8Array([0, 0, 0, 0]));
|
||||
expect(new Uint32(1).toBytesBigEndian()).toEqual(new Uint8Array([0, 0, 0, 1]));
|
||||
expect(new Uint32(42).toBytesBigEndian()).toEqual(new Uint8Array([0, 0, 0, 42]));
|
||||
expect(new Uint32(1000000000).toBytesBigEndian()).toEqual(new Uint8Array([0x3b, 0x9a, 0xca, 0x00]));
|
||||
expect(new Uint32(2147483647).toBytesBigEndian()).toEqual(new Uint8Array([0x7f, 0xff, 0xff, 0xff]));
|
||||
expect(new Uint32(2147483648).toBytesBigEndian()).toEqual(new Uint8Array([0x80, 0x00, 0x00, 0x00]));
|
||||
expect(new Uint32(4294967295).toBytesBigEndian()).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0xff]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("toBytesLittleEndian", () => {
|
||||
it("works", () => {
|
||||
expect(new Uint32(0).toBytesLittleEndian()).toEqual(new Uint8Array([0, 0, 0, 0]));
|
||||
expect(new Uint32(1).toBytesLittleEndian()).toEqual(new Uint8Array([1, 0, 0, 0]));
|
||||
expect(new Uint32(42).toBytesLittleEndian()).toEqual(new Uint8Array([42, 0, 0, 0]));
|
||||
expect(new Uint32(1000000000).toBytesLittleEndian()).toEqual(
|
||||
new Uint8Array([0x00, 0xca, 0x9a, 0x3b]),
|
||||
);
|
||||
expect(new Uint32(2147483647).toBytesLittleEndian()).toEqual(
|
||||
new Uint8Array([0xff, 0xff, 0xff, 0x7f]),
|
||||
);
|
||||
expect(new Uint32(2147483648).toBytesLittleEndian()).toEqual(
|
||||
new Uint8Array([0x00, 0x00, 0x00, 0x80]),
|
||||
);
|
||||
expect(new Uint32(4294967295).toBytesLittleEndian()).toEqual(
|
||||
new Uint8Array([0xff, 0xff, 0xff, 0xff]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromBigEndianBytes", () => {
|
||||
it("can be constructed from to byte array", () => {
|
||||
expect(Uint32.fromBigEndianBytes([0, 0, 0, 0]).toNumber()).toEqual(0);
|
||||
expect(Uint32.fromBigEndianBytes([0, 0, 0, 1]).toNumber()).toEqual(1);
|
||||
expect(Uint32.fromBigEndianBytes([0, 0, 0, 42]).toNumber()).toEqual(42);
|
||||
expect(Uint32.fromBigEndianBytes([0x3b, 0x9a, 0xca, 0x00]).toNumber()).toEqual(1000000000);
|
||||
expect(Uint32.fromBigEndianBytes([0x7f, 0xff, 0xff, 0xff]).toNumber()).toEqual(2147483647);
|
||||
expect(Uint32.fromBigEndianBytes([0x80, 0x00, 0x00, 0x00]).toNumber()).toEqual(2147483648);
|
||||
expect(Uint32.fromBigEndianBytes([0xff, 0xff, 0xff, 0xff]).toNumber()).toEqual(4294967295);
|
||||
});
|
||||
|
||||
it("can be constructed from Buffer", () => {
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0, 0, 0, 0])).toNumber()).toEqual(0);
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0, 0, 0, 1])).toNumber()).toEqual(1);
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0, 0, 0, 42])).toNumber()).toEqual(42);
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0x3b, 0x9a, 0xca, 0x00])).toNumber()).toEqual(
|
||||
1000000000,
|
||||
);
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0x7f, 0xff, 0xff, 0xff])).toNumber()).toEqual(
|
||||
2147483647,
|
||||
);
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0x80, 0x00, 0x00, 0x00])).toNumber()).toEqual(
|
||||
2147483648,
|
||||
);
|
||||
expect(Uint32.fromBigEndianBytes(Buffer.from([0xff, 0xff, 0xff, 0xff])).toNumber()).toEqual(
|
||||
4294967295,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws for invalid input length", () => {
|
||||
expect(() => Uint32.fromBigEndianBytes([])).toThrowError(/Invalid input length/);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0])).toThrowError(/Invalid input length/);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, 0, 0])).toThrowError(/Invalid input length/);
|
||||
});
|
||||
|
||||
it("throws for invalid values", () => {
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, -1])).toThrowError(/Invalid value in byte/);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, 1.5])).toThrowError(/Invalid value in byte/);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, 256])).toThrowError(/Invalid value in byte/);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, NaN])).toThrowError(/Invalid value in byte/);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, Number.NEGATIVE_INFINITY])).toThrowError(
|
||||
/Invalid value in byte/,
|
||||
);
|
||||
expect(() => Uint32.fromBigEndianBytes([0, 0, 0, Number.POSITIVE_INFINITY])).toThrowError(
|
||||
/Invalid value in byte/,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Int53", () => {
|
||||
it("can be constructed", () => {
|
||||
expect(new Int53(0)).toBeTruthy();
|
||||
expect(new Int53(1)).toBeTruthy();
|
||||
expect(new Int53(1.0)).toBeTruthy();
|
||||
expect(new Int53(42)).toBeTruthy();
|
||||
expect(new Int53(1000000000)).toBeTruthy();
|
||||
expect(new Int53(2147483647)).toBeTruthy();
|
||||
expect(new Int53(2147483648)).toBeTruthy();
|
||||
expect(new Int53(4294967295)).toBeTruthy();
|
||||
expect(new Int53(9007199254740991)).toBeTruthy();
|
||||
|
||||
expect(new Int53(-1)).toBeTruthy();
|
||||
expect(new Int53(-42)).toBeTruthy();
|
||||
expect(new Int53(-2147483648)).toBeTruthy();
|
||||
expect(new Int53(-2147483649)).toBeTruthy();
|
||||
expect(new Int53(-9007199254740991)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("throws for invald numbers", () => {
|
||||
expect(() => new Int53(NaN)).toThrowError(/not a number/);
|
||||
|
||||
expect(() => new Int53(1.1)).toThrowError(/not an integer/i);
|
||||
expect(() => new Int53(Number.NEGATIVE_INFINITY)).toThrowError(/not an integer/i);
|
||||
expect(() => new Int53(Number.POSITIVE_INFINITY)).toThrowError(/not an integer/i);
|
||||
});
|
||||
|
||||
it("throws for values out of range", () => {
|
||||
expect(() => new Int53(Number.MIN_SAFE_INTEGER - 1)).toThrowError(/not in int53 range/);
|
||||
expect(() => new Int53(Number.MAX_SAFE_INTEGER + 1)).toThrowError(/not in int53 range/);
|
||||
});
|
||||
|
||||
it("can convert to number", () => {
|
||||
expect(new Int53(0).toNumber()).toEqual(0);
|
||||
expect(new Int53(1).toNumber()).toEqual(1);
|
||||
expect(new Int53(42).toNumber()).toEqual(42);
|
||||
expect(new Int53(1000000000).toNumber()).toEqual(1000000000);
|
||||
expect(new Int53(2147483647).toNumber()).toEqual(2147483647);
|
||||
expect(new Int53(2147483648).toNumber()).toEqual(2147483648);
|
||||
expect(new Int53(4294967295).toNumber()).toEqual(4294967295);
|
||||
expect(new Int53(9007199254740991).toNumber()).toEqual(9007199254740991);
|
||||
|
||||
expect(new Int53(-1).toNumber()).toEqual(-1);
|
||||
expect(new Int53(-9007199254740991).toNumber()).toEqual(-9007199254740991);
|
||||
});
|
||||
|
||||
it("can convert to string", () => {
|
||||
expect(new Int53(0).toString()).toEqual("0");
|
||||
expect(new Int53(1).toString()).toEqual("1");
|
||||
expect(new Int53(42).toString()).toEqual("42");
|
||||
expect(new Int53(1000000000).toString()).toEqual("1000000000");
|
||||
expect(new Int53(2147483647).toString()).toEqual("2147483647");
|
||||
expect(new Int53(2147483648).toString()).toEqual("2147483648");
|
||||
expect(new Int53(4294967295).toString()).toEqual("4294967295");
|
||||
expect(new Int53(9007199254740991).toString()).toEqual("9007199254740991");
|
||||
|
||||
expect(new Int53(-1).toString()).toEqual("-1");
|
||||
expect(new Int53(-9007199254740991).toString()).toEqual("-9007199254740991");
|
||||
});
|
||||
|
||||
it("can be constructed from string", () => {
|
||||
expect(Int53.fromString("0").toString()).toEqual("0");
|
||||
expect(Int53.fromString("1").toString()).toEqual("1");
|
||||
expect(Int53.fromString("9007199254740991").toString()).toEqual("9007199254740991");
|
||||
|
||||
expect(Int53.fromString("-1").toString()).toEqual("-1");
|
||||
expect(Int53.fromString("-9007199254740991").toString()).toEqual("-9007199254740991");
|
||||
});
|
||||
|
||||
it("throws for invalid string format", () => {
|
||||
expect(() => Int53.fromString(" 0")).toThrowError(/invalid string format/i);
|
||||
expect(() => Int53.fromString("+0")).toThrowError(/invalid string format/i);
|
||||
expect(() => Int53.fromString("1e6")).toThrowError(/invalid string format/i);
|
||||
|
||||
expect(() => Int53.fromString("9007199254740992")).toThrowError(/input not in int53 range/i);
|
||||
expect(() => Int53.fromString("-9007199254740992")).toThrowError(/input not in int53 range/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Uint53", () => {
|
||||
it("can be constructed", () => {
|
||||
expect(new Uint53(0)).toBeTruthy();
|
||||
expect(new Uint53(1)).toBeTruthy();
|
||||
expect(new Uint53(1.0)).toBeTruthy();
|
||||
expect(new Uint53(42)).toBeTruthy();
|
||||
expect(new Uint53(1000000000)).toBeTruthy();
|
||||
expect(new Uint53(2147483647)).toBeTruthy();
|
||||
expect(new Uint53(2147483648)).toBeTruthy();
|
||||
expect(new Uint53(4294967295)).toBeTruthy();
|
||||
expect(new Uint53(9007199254740991)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("throws for invald numbers", () => {
|
||||
expect(() => new Uint53(NaN)).toThrowError(/not a number/);
|
||||
|
||||
expect(() => new Uint53(1.1)).toThrowError(/not an integer/i);
|
||||
expect(() => new Uint53(Number.NEGATIVE_INFINITY)).toThrowError(/not an integer/i);
|
||||
expect(() => new Uint53(Number.POSITIVE_INFINITY)).toThrowError(/not an integer/i);
|
||||
});
|
||||
|
||||
it("throws for values out of range", () => {
|
||||
expect(() => new Uint53(Number.MIN_SAFE_INTEGER - 1)).toThrowError(/not in int53 range/);
|
||||
expect(() => new Uint53(Number.MAX_SAFE_INTEGER + 1)).toThrowError(/not in int53 range/);
|
||||
});
|
||||
|
||||
it("throws for negative inputs", () => {
|
||||
expect(() => new Uint53(-1)).toThrowError(/is negative/);
|
||||
expect(() => new Uint53(-42)).toThrowError(/is negative/);
|
||||
expect(() => new Uint53(-2147483648)).toThrowError(/is negative/);
|
||||
expect(() => new Uint53(-2147483649)).toThrowError(/is negative/);
|
||||
expect(() => new Uint53(-9007199254740991)).toThrowError(/is negative/);
|
||||
});
|
||||
|
||||
it("can convert to number", () => {
|
||||
expect(new Uint53(0).toNumber()).toEqual(0);
|
||||
expect(new Uint53(1).toNumber()).toEqual(1);
|
||||
expect(new Uint53(42).toNumber()).toEqual(42);
|
||||
expect(new Uint53(1000000000).toNumber()).toEqual(1000000000);
|
||||
expect(new Uint53(2147483647).toNumber()).toEqual(2147483647);
|
||||
expect(new Uint53(2147483648).toNumber()).toEqual(2147483648);
|
||||
expect(new Uint53(4294967295).toNumber()).toEqual(4294967295);
|
||||
expect(new Uint53(9007199254740991).toNumber()).toEqual(9007199254740991);
|
||||
});
|
||||
|
||||
it("can convert to string", () => {
|
||||
expect(new Uint53(0).toString()).toEqual("0");
|
||||
expect(new Uint53(1).toString()).toEqual("1");
|
||||
expect(new Uint53(42).toString()).toEqual("42");
|
||||
expect(new Uint53(1000000000).toString()).toEqual("1000000000");
|
||||
expect(new Uint53(2147483647).toString()).toEqual("2147483647");
|
||||
expect(new Uint53(2147483648).toString()).toEqual("2147483648");
|
||||
expect(new Uint53(4294967295).toString()).toEqual("4294967295");
|
||||
expect(new Uint53(9007199254740991).toString()).toEqual("9007199254740991");
|
||||
});
|
||||
|
||||
it("can be constructed from string", () => {
|
||||
expect(Uint53.fromString("0").toString()).toEqual("0");
|
||||
expect(Uint53.fromString("1").toString()).toEqual("1");
|
||||
expect(Uint53.fromString("9007199254740991").toString()).toEqual("9007199254740991");
|
||||
});
|
||||
|
||||
it("throws for invalid string format", () => {
|
||||
expect(() => Uint53.fromString(" 0")).toThrowError(/invalid string format/i);
|
||||
expect(() => Uint53.fromString("+0")).toThrowError(/invalid string format/i);
|
||||
expect(() => Uint53.fromString("1e6")).toThrowError(/invalid string format/i);
|
||||
|
||||
expect(() => Uint53.fromString("-9007199254740992")).toThrowError(/input not in int53 range/i);
|
||||
expect(() => Uint53.fromString("9007199254740992")).toThrowError(/input not in int53 range/i);
|
||||
|
||||
expect(() => Uint53.fromString("-1")).toThrowError(/input is negative/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Uint64", () => {
|
||||
describe("fromBigEndianBytes", () => {
|
||||
it("can be constructed from bytes", () => {
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
|
||||
});
|
||||
|
||||
it("can be constructed from Uint8Array", () => {
|
||||
Uint64.fromBytesBigEndian(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
|
||||
});
|
||||
|
||||
it("throws for wrong number of bytes", () => {
|
||||
expect(() => Uint64.fromBytesBigEndian([])).toThrowError(/invalid input length/i);
|
||||
expect(() => Uint64.fromBytesBigEndian([0x00])).toThrowError(/invalid input length/i);
|
||||
expect(() => Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])).toThrowError(
|
||||
/invalid input length/i,
|
||||
);
|
||||
expect(() =>
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
).toThrowError(/invalid input length/i);
|
||||
});
|
||||
|
||||
it("throws for wrong byte value", () => {
|
||||
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, 256])).toThrowError(
|
||||
/invalid value in byte/i,
|
||||
);
|
||||
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, -1])).toThrowError(
|
||||
/invalid value in byte/i,
|
||||
);
|
||||
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, 1.5])).toThrowError(
|
||||
/invalid value in byte/i,
|
||||
);
|
||||
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, Number.NEGATIVE_INFINITY])).toThrowError(
|
||||
/invalid value in byte/i,
|
||||
);
|
||||
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, Number.POSITIVE_INFINITY])).toThrowError(
|
||||
/invalid value in byte/i,
|
||||
);
|
||||
expect(() => Uint64.fromBytesBigEndian([0, 0, 0, 0, 0, 0, 0, Number.NaN])).toThrowError(
|
||||
/invalid value in byte/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromString", () => {
|
||||
it("can be constructed from string", () => {
|
||||
{
|
||||
const a = Uint64.fromString("0");
|
||||
expect(a).toBeTruthy();
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromString("1");
|
||||
expect(a).toBeTruthy();
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromString("01");
|
||||
expect(a).toBeTruthy();
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromString("9999999999999999999");
|
||||
expect(a).toBeTruthy();
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromString("18446744073709551615");
|
||||
expect(a).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("throws for invalid string values", () => {
|
||||
expect(() => Uint64.fromString(" 1")).toThrowError(/invalid string format/i);
|
||||
expect(() => Uint64.fromString("-1")).toThrowError(/invalid string format/i);
|
||||
expect(() => Uint64.fromString("+1")).toThrowError(/invalid string format/i);
|
||||
expect(() => Uint64.fromString("1e6")).toThrowError(/invalid string format/i);
|
||||
});
|
||||
|
||||
it("throws for string values exceeding uint64", () => {
|
||||
expect(() => Uint64.fromString("18446744073709551616")).toThrowError(/input exceeds uint64 range/i);
|
||||
expect(() => Uint64.fromString("99999999999999999999")).toThrowError(/input exceeds uint64 range/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromNumber", () => {
|
||||
it("can be constructed from number", () => {
|
||||
const a = Uint64.fromNumber(0);
|
||||
expect(a.toNumber()).toEqual(0);
|
||||
const b = Uint64.fromNumber(1);
|
||||
expect(b.toNumber()).toEqual(1);
|
||||
const c = Uint64.fromNumber(Number.MAX_SAFE_INTEGER);
|
||||
expect(c.toNumber()).toEqual(Number.MAX_SAFE_INTEGER);
|
||||
});
|
||||
|
||||
it("throws when constructed from wrong numbers", () => {
|
||||
// not a number
|
||||
expect(() => Uint64.fromNumber(Number.NaN)).toThrowError(/input is not a number/i);
|
||||
|
||||
// not an integer
|
||||
expect(() => Uint64.fromNumber(Number.NEGATIVE_INFINITY)).toThrowError(
|
||||
/input is not a safe integer/i,
|
||||
);
|
||||
expect(() => Uint64.fromNumber(Number.POSITIVE_INFINITY)).toThrowError(
|
||||
/input is not a safe integer/i,
|
||||
);
|
||||
expect(() => Uint64.fromNumber(Number.MAX_SAFE_INTEGER + 1)).toThrowError(
|
||||
/input is not a safe integer/i,
|
||||
);
|
||||
|
||||
// negative integer
|
||||
expect(() => Uint64.fromNumber(-1)).toThrowError(/input is negative/i);
|
||||
expect(() => Uint64.fromNumber(Number.MIN_SAFE_INTEGER)).toThrowError(/input is negative/i);
|
||||
});
|
||||
});
|
||||
|
||||
it("can export bytes (big endian)", () => {
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesBigEndian(),
|
||||
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]).toBytesBigEndian(),
|
||||
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesBigEndian(),
|
||||
).toEqual(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]).toBytesBigEndian(),
|
||||
).toEqual(new Uint8Array([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]).toBytesBigEndian(),
|
||||
).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]));
|
||||
});
|
||||
|
||||
it("can export bytes (little endian)", () => {
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesLittleEndian(),
|
||||
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]).toBytesLittleEndian(),
|
||||
).toEqual(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toBytesLittleEndian(),
|
||||
).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0xab, 0x22, 0xbc, 0x5f, 0xa9, 0x20, 0x4e, 0x0d]).toBytesLittleEndian(),
|
||||
).toEqual(new Uint8Array([0x0d, 0x4e, 0x20, 0xa9, 0x5f, 0xbc, 0x22, 0xab]));
|
||||
expect(
|
||||
Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]).toBytesLittleEndian(),
|
||||
).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]));
|
||||
});
|
||||
|
||||
it("can export strings", () => {
|
||||
{
|
||||
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
expect(a.toString()).toEqual("0");
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
expect(a.toString()).toEqual("1");
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromBytesBigEndian([0x8a, 0xc7, 0x23, 0x04, 0x89, 0xe7, 0xff, 0xff]);
|
||||
expect(a.toString()).toEqual("9999999999999999999");
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
|
||||
expect(a.toString()).toEqual("18446744073709551615");
|
||||
}
|
||||
});
|
||||
|
||||
it("can export numbers", () => {
|
||||
{
|
||||
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
expect(a.toNumber()).toEqual(0);
|
||||
}
|
||||
{
|
||||
const a = Uint64.fromBytesBigEndian([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
expect(a.toNumber()).toEqual(1);
|
||||
}
|
||||
{
|
||||
// value too large for 53 bit integer
|
||||
const a = Uint64.fromBytesBigEndian([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
|
||||
expect(() => a.toNumber()).toThrowError(/number can only safely store up to 53 bits/i);
|
||||
}
|
||||
{
|
||||
// Number.MAX_SAFE_INTEGER + 1
|
||||
const a = Uint64.fromBytesBigEndian([0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
expect(() => a.toNumber()).toThrowError(/number can only safely store up to 53 bits/i);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,212 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
import BN from "bn.js";
|
||||
|
||||
const uint64MaxValue = new BN("18446744073709551615", 10, "be");
|
||||
|
||||
/** Internal interface to ensure all integer types can be used equally */
|
||||
interface Integer {
|
||||
readonly toNumber: () => number;
|
||||
readonly toString: () => string;
|
||||
}
|
||||
|
||||
interface WithByteConverters {
|
||||
readonly toBytesBigEndian: () => Uint8Array;
|
||||
readonly toBytesLittleEndian: () => Uint8Array;
|
||||
}
|
||||
|
||||
export class Uint32 implements Integer, WithByteConverters {
|
||||
public static fromBigEndianBytes(bytes: ArrayLike<number>): Uint32 {
|
||||
if (bytes.length !== 4) {
|
||||
throw new Error("Invalid input length. Expected 4 bytes.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
if (!Number.isInteger(bytes[i]) || bytes[i] > 255 || bytes[i] < 0) {
|
||||
throw new Error("Invalid value in byte. Found: " + bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Use mulitiplication instead of shifting since bitwise operators are defined
|
||||
// on SIGNED int32 in JavaScript and we don't want to risk surprises
|
||||
return new Uint32(bytes[0] * 2 ** 24 + bytes[1] * 2 ** 16 + bytes[2] * 2 ** 8 + bytes[3]);
|
||||
}
|
||||
|
||||
protected readonly data: number;
|
||||
|
||||
public constructor(input: number) {
|
||||
if (Number.isNaN(input)) {
|
||||
throw new Error("Input is not a number");
|
||||
}
|
||||
|
||||
if (!Number.isInteger(input)) {
|
||||
throw new Error("Input is not an integer");
|
||||
}
|
||||
|
||||
if (input < 0 || input > 4294967295) {
|
||||
throw new Error("Input not in uint32 range: " + input.toString());
|
||||
}
|
||||
|
||||
this.data = input;
|
||||
}
|
||||
|
||||
public toBytesBigEndian(): Uint8Array {
|
||||
// Use division instead of shifting since bitwise operators are defined
|
||||
// on SIGNED int32 in JavaScript and we don't want to risk surprises
|
||||
return new Uint8Array([
|
||||
Math.floor(this.data / 2 ** 24) & 0xff,
|
||||
Math.floor(this.data / 2 ** 16) & 0xff,
|
||||
Math.floor(this.data / 2 ** 8) & 0xff,
|
||||
Math.floor(this.data / 2 ** 0) & 0xff,
|
||||
]);
|
||||
}
|
||||
|
||||
public toBytesLittleEndian(): Uint8Array {
|
||||
// Use division instead of shifting since bitwise operators are defined
|
||||
// on SIGNED int32 in JavaScript and we don't want to risk surprises
|
||||
return new Uint8Array([
|
||||
Math.floor(this.data / 2 ** 0) & 0xff,
|
||||
Math.floor(this.data / 2 ** 8) & 0xff,
|
||||
Math.floor(this.data / 2 ** 16) & 0xff,
|
||||
Math.floor(this.data / 2 ** 24) & 0xff,
|
||||
]);
|
||||
}
|
||||
|
||||
public toNumber(): number {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.data.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export class Int53 implements Integer {
|
||||
public static fromString(str: string): Int53 {
|
||||
if (!str.match(/^-?[0-9]+$/)) {
|
||||
throw new Error("Invalid string format");
|
||||
}
|
||||
|
||||
return new Int53(Number.parseInt(str, 10));
|
||||
}
|
||||
|
||||
protected readonly data: number;
|
||||
|
||||
public constructor(input: number) {
|
||||
if (Number.isNaN(input)) {
|
||||
throw new Error("Input is not a number");
|
||||
}
|
||||
|
||||
if (!Number.isInteger(input)) {
|
||||
throw new Error("Input is not an integer");
|
||||
}
|
||||
|
||||
if (input < Number.MIN_SAFE_INTEGER || input > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error("Input not in int53 range: " + input.toString());
|
||||
}
|
||||
|
||||
this.data = input;
|
||||
}
|
||||
|
||||
public toNumber(): number {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.data.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export class Uint53 implements Integer {
|
||||
public static fromString(str: string): Uint53 {
|
||||
const signed = Int53.fromString(str);
|
||||
return new Uint53(signed.toNumber());
|
||||
}
|
||||
|
||||
protected readonly data: Int53;
|
||||
|
||||
public constructor(input: number) {
|
||||
const signed = new Int53(input);
|
||||
if (signed.toNumber() < 0) {
|
||||
throw new Error("Input is negative");
|
||||
}
|
||||
this.data = signed;
|
||||
}
|
||||
|
||||
public toNumber(): number {
|
||||
return this.data.toNumber();
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.data.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export class Uint64 implements Integer, WithByteConverters {
|
||||
public static fromBytesBigEndian(bytes: ArrayLike<number>): Uint64 {
|
||||
if (bytes.length !== 8) {
|
||||
throw new Error("Invalid input length. Expected 8 bytes.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
if (!Number.isInteger(bytes[i]) || bytes[i] > 255 || bytes[i] < 0) {
|
||||
throw new Error("Invalid value in byte. Found: " + bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const asArray: number[] = [];
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
asArray.push(bytes[i]);
|
||||
}
|
||||
|
||||
return new Uint64(new BN([...asArray]));
|
||||
}
|
||||
|
||||
public static fromString(str: string): Uint64 {
|
||||
if (!str.match(/^[0-9]+$/)) {
|
||||
throw new Error("Invalid string format");
|
||||
}
|
||||
return new Uint64(new BN(str, 10, "be"));
|
||||
}
|
||||
|
||||
public static fromNumber(input: number): Uint64 {
|
||||
if (Number.isNaN(input)) {
|
||||
throw new Error("Input is not a number");
|
||||
}
|
||||
|
||||
let bigint: BN;
|
||||
try {
|
||||
bigint = new BN(input);
|
||||
} catch {
|
||||
throw new Error("Input is not a safe integer");
|
||||
}
|
||||
return new Uint64(bigint);
|
||||
}
|
||||
|
||||
private readonly data: BN;
|
||||
|
||||
private constructor(data: BN) {
|
||||
if (data.isNeg()) {
|
||||
throw new Error("Input is negative");
|
||||
}
|
||||
if (data.gt(uint64MaxValue)) {
|
||||
throw new Error("Input exceeds uint64 range");
|
||||
}
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public toBytesBigEndian(): Uint8Array {
|
||||
return Uint8Array.from(this.data.toArray("be", 8));
|
||||
}
|
||||
|
||||
public toBytesLittleEndian(): Uint8Array {
|
||||
return Uint8Array.from(this.data.toArray("le", 8));
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.data.toString(10);
|
||||
}
|
||||
|
||||
public toNumber(): number {
|
||||
return this.data.toNumber();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user