Skip to content

Commit

Permalink
feat(common): add bigIntToStr() and strToBigInt() utility functions
Browse files Browse the repository at this point in the history
  • Loading branch information
capt-nemo429 committed Dec 23, 2022
1 parent 98c9352 commit 5c75337
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/common/src/utils/bigIntLiterals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export const _0n = BigInt(0);
export const _1n = BigInt(1);
export const _7n = BigInt(7);
export const _10n = BigInt(10);
export const _63n = BigInt(63);
export const _127n = BigInt(127);
export const _128n = BigInt(128);
91 changes: 89 additions & 2 deletions packages/common/src/utils/bigIntUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,93 @@
import { ensureBigInt, sumBy } from "./bigIntUtils";
import { bigIntToStr, ensureBigInt, strToBigInt, sumBy } from "./bigIntUtils";

describe("BigInt toBigInt() conversion", () => {
describe("bigIntToStr()", () => {
it("Should decimalize", () => {
expect(bigIntToStr(1763874n)).toBe("1763874");
expect(bigIntToStr(1763874n, { decimals: 2 })).toBe("17638.74");
expect(bigIntToStr(1874n, { decimals: 10 })).toBe("0.0000001874");
expect(bigIntToStr(1n, { decimals: 2 })).toBe("0.01");
expect(bigIntToStr(1298379183n)).toBe("1298379183");
});

it("Should format", () => {
expect(
bigIntToStr(129837918300001n, {
decimals: 5,
thousandMark: ",",
decimalMark: "."
})
).toBe("1,298,379,183.00001");

expect(
bigIntToStr(129837918300001n, {
decimals: 5
})
).toBe("1298379183.00001");

expect(
bigIntToStr(129837918300001n, {
decimals: 5,
thousandMark: ".",
decimalMark: ","
})
).toBe("1.298.379.183,00001");

expect(
bigIntToStr(129837918300001n, {
decimals: 5,
thousandMark: "#",
decimalMark: "-"
})
).toBe("1#298#379#183-00001");

expect(
bigIntToStr("129837918300001", {
thousandMark: "#"
})
).toBe("129#837#918#300#001");

expect(bigIntToStr("1100000", { decimals: 9 })).toBe("0.0011");
expect(bigIntToStr(129837918300n, { decimals: 9 })).toBe("129.8379183");
expect(bigIntToStr(100n, { decimals: 2 })).toBe("1");
expect(bigIntToStr(123n, { decimals: 2 })).toBe("1.23");
expect(bigIntToStr(1n, { decimals: 2 })).toBe("0.01");
expect(bigIntToStr(1298379183n)).toBe("1298379183");
});

it("Should do a roundtrip", () => {
const options = { decimals: 9 };
expect(bigIntToStr(strToBigInt("129.8379183", options), options)).toBe("129.8379183");
});
});

describe("strToBigInt()", () => {
it("Should parse BigInt strings", () => {
expect(strToBigInt(" ")).toBe(0n);
expect(strToBigInt("")).toBe(0n);
expect(strToBigInt("", { decimals: 9 })).toBe(0n);
expect(strToBigInt("0.0011", { decimals: 9 })).toBe(1100000n);
expect(strToBigInt("129.8379183", { decimals: 9 })).toBe(129837918300n);
expect(strToBigInt("1", { decimals: 2 })).toBe(100n);
expect(strToBigInt("1.23", { decimals: 2 })).toBe(123n);
expect(strToBigInt("123", { decimals: 2 })).toBe(12300n);
expect(strToBigInt("1.233", { decimals: 2 })).toBe(1233n);
expect(strToBigInt("1.23")).toBe(123n);
expect(strToBigInt("1")).toBe(1n);
expect(strToBigInt("1", { decimals: 0 })).toBe(1n);
expect(strToBigInt("0,0011", { decimals: 9, decimalMark: "," })).toBe(1100000n);
expect(strToBigInt("129-8379183", { decimals: 9, decimalMark: "-" })).toBe(129837918300n);

expect(strToBigInt("-129.8379183", { decimals: 9 })).toBe(-129837918300n);
});

it("Should throw if used with wrong number format", () => {
expect(() => {
strToBigInt("12.23.1");
}).toThrow();
});
});

describe("ensureBigInt()", () => {
it("Should convert arbitrary types to bigint", () => {
expect(ensureBigInt("1298379183")).toBe(1298379183n);
expect(ensureBigInt(10)).toBe(10n);
Expand Down
98 changes: 96 additions & 2 deletions packages/common/src/utils/bigIntUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,105 @@
import { Amount } from "../types";
import { isEmpty } from "./arrayUtils";
import { _0n } from "./bigIntLiterals";
import { _0n, _10n } from "./bigIntLiterals";
import { isUndefined } from "./objectUtils";

export function ensureBigInt(number: string | number | bigint | boolean): bigint {
type NumberLike = string | number | bigint | boolean;

export function ensureBigInt(number: NumberLike): bigint {
return typeof number === "bigint" ? number : BigInt(number);
}

type BigIntParseOptions = {
/**
* Number of decimals.
*/
decimals?: number;

/**
* Thousand mark char.
* Default: `.`
*/
decimalMark?: string;
};

export function strToBigInt(value: string, options?: BigIntParseOptions): bigint {
if (!value) {
return _0n;
}

const fragments = value.split(options?.decimalMark || ".");
if (fragments.length > 2) {
throw new Error("Invalid numeric string.");
}

let [integer, decimal] = fragments;
integer = removeLeadingZeros(integer);
const decimals = options?.decimals || 0;
const negative = integer.startsWith("-") ? "-" : "";

if (!decimal) {
decimal = "0".repeat(decimals);
} else if (decimal.length < decimals) {
decimal = decimal.padEnd(decimals, "0");
}

return BigInt(negative + (integer + decimal).replace(/\D/g, ""));
}

type BigIntFormatOptions = {
/**
* Number of decimals.
*/
decimals?: number;

/**
* Thousand mark char.
*/
thousandMark?: string;

/**
* Decimal mark char.
* Default: `.`
*/
decimalMark?: string;
};

export function bigIntToStr(value: Amount, options?: BigIntFormatOptions): string {
value = ensureBigInt(value);
if (!options) {
return value.toString();
}

const decimals = options.decimals || 0;
const pow = _10n ** BigInt(decimals);
const integer = value / pow;
const decimal = value - integer * pow;
const decimalPart = stripTrailingZeros(decimal.toString(10).padStart(decimals, "0"));
const integerPart = addThousandMarks(integer.toString(10), options.thousandMark);

if (decimalPart) {
return `${integerPart}${options.decimalMark || "."}${decimalPart}`;
} else {
return integerPart;
}
}

function addThousandMarks(value: string, mark?: string): string {
if (!mark) {
return value;
}

return value.replace(/\B(?=(\d{3})+(?!\d))/g, mark);
}

function stripTrailingZeros(value: string): string {
return value.replace(/\.?0+$/, "");
}

function removeLeadingZeros(value: string): string {
return value.replace(/^0+\.?/, "");
}

export function sumBy<T>(
collection: T[],
iteratee: (value: T) => bigint,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/serializer/sigma/sigmaByteWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export class SigmaByteWriter {
}

public writeBigInt(number: bigint): SigmaByteWriter {
// todo: take a look at https://coolaj86.com/articles/convert-decimal-to-hex-with-js-bigints/
// and https://coolaj86.com/articles/convert-hex-to-decimal-with-js-bigints/
if (number < _0n) {
throw new Error("Negative BigInt values are not supported Fleet serializer.");
}
Expand Down

0 comments on commit 5c75337

Please sign in to comment.