mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 16:28:06 +00:00
116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
function normalizeDecimalInput(value: number | string | bigint): { sign: 1 | -1; digits: string; fractionLength: number } {
|
|
if (typeof value === "bigint") {
|
|
const sign = value < 0n ? -1 : 1;
|
|
const digits = (sign === -1 ? -value : value).toString();
|
|
return { sign, digits, fractionLength: 0 };
|
|
}
|
|
if (typeof value === "number") {
|
|
if (!Number.isFinite(value)) {
|
|
throw new Error(`Invalid decimal input: ${value}`);
|
|
}
|
|
value = value.toString();
|
|
}
|
|
const trimmed = value.trim();
|
|
if (!trimmed.length) throw new Error("Decimal input cannot be empty");
|
|
const sign: 1 | -1 = trimmed[0] === "-" ? -1 : 1;
|
|
const unsigned = sign === -1 ? trimmed.slice(1) : trimmed;
|
|
if (!/^\d*(?:\.\d*)?$/.test(unsigned)) {
|
|
throw new Error(`Invalid decimal format: ${value}`);
|
|
}
|
|
if (unsigned === "" || unsigned === ".") {
|
|
return { sign: 1, digits: "0", fractionLength: 0 };
|
|
}
|
|
const parts = unsigned.split(".");
|
|
const integerPartRaw = parts[0] ?? "";
|
|
const fractionRaw = parts[1] ?? "";
|
|
const integerPart = integerPartRaw.replace(/^0+(?=\d)/, "");
|
|
const fractionPart = fractionRaw.replace(/0+$/, "");
|
|
if (!integerPart && !fractionPart) {
|
|
return { sign: 1, digits: "0", fractionLength: 0 };
|
|
}
|
|
const digits = `${integerPart || "0"}${fractionPart}`;
|
|
return { sign, digits, fractionLength: fractionPart.length };
|
|
}
|
|
|
|
export function decimalToScaled(value: number | string | bigint, decimals: number): bigint {
|
|
if (decimals < 0) throw new Error("Decimals must be non-negative");
|
|
const { sign, digits, fractionLength } = normalizeDecimalInput(value);
|
|
if (fractionLength > decimals) {
|
|
const trimLength = fractionLength - decimals;
|
|
const truncated = digits.slice(0, digits.length - trimLength) || "0";
|
|
const result = BigInt(truncated);
|
|
return sign === -1 ? -result : result;
|
|
}
|
|
const padded = digits.padEnd(digits.length + (decimals - fractionLength), "0");
|
|
const result = BigInt(padded);
|
|
return sign === -1 ? -result : result;
|
|
}
|
|
|
|
export function scaleQuantityWithMinimum(value: number | string | bigint, decimals: number): bigint {
|
|
const scaled = decimalToScaled(value, decimals);
|
|
if (scaled !== 0n) {
|
|
return scaled;
|
|
}
|
|
const numeric = typeof value === "bigint" ? Number(value) : Number(value);
|
|
if (!Number.isFinite(numeric) || numeric === 0) {
|
|
return scaled;
|
|
}
|
|
return numeric < 0 ? -1n : 1n;
|
|
}
|
|
|
|
export function scaledToDecimalString(value: bigint | number | string, decimals: number): string {
|
|
if (typeof value === "number") {
|
|
if (!Number.isFinite(value)) throw new Error(`Invalid scaled number: ${value}`);
|
|
value = BigInt(Math.trunc(value));
|
|
} else if (typeof value === "string") {
|
|
if (!/^[-]?\d+$/.test(value.trim())) {
|
|
throw new Error(`Invalid scaled string: ${value}`);
|
|
}
|
|
value = BigInt(value.trim());
|
|
}
|
|
const negative = value < 0n;
|
|
let abs = negative ? -value : value;
|
|
let digits = abs.toString();
|
|
if (decimals === 0) {
|
|
return negative ? `-${digits}` : digits;
|
|
}
|
|
if (digits.length <= decimals) {
|
|
digits = digits.padStart(decimals + 1, "0");
|
|
}
|
|
const splitIndex = digits.length - decimals;
|
|
const integerPart = digits.slice(0, splitIndex) || "0";
|
|
const fractionPart = digits.slice(splitIndex);
|
|
const result = `${integerPart}.${fractionPart}`.replace(/\.0+$/, "").replace(/\.$/, "");
|
|
return negative ? `-${result}` : result;
|
|
}
|
|
|
|
export function clampToInt64(value: bigint): bigint {
|
|
const max = (1n << 63n) - 1n;
|
|
const min = -(1n << 63n);
|
|
if (value > max || value < min) {
|
|
throw new Error("Value exceeds int64 bounds");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function toInt64(value: bigint): number {
|
|
const bounded = clampToInt64(value);
|
|
return Number(bounded);
|
|
}
|
|
|
|
export function safeNumberToUint32(value: number | bigint): number {
|
|
const val = typeof value === "bigint" ? Number(value) : value;
|
|
if (!Number.isFinite(val) || val < 0 || val > 0xffffffff) {
|
|
throw new Error(`Value ${value} is out of uint32 range`);
|
|
}
|
|
return Math.trunc(val);
|
|
}
|
|
|
|
export function toSafeNumber(value: bigint | number): number {
|
|
const numeric = typeof value === "bigint" ? Number(value) : value;
|
|
if (!Number.isSafeInteger(numeric)) {
|
|
throw new Error(`Value ${value.toString()} exceeds safe integer range`);
|
|
}
|
|
return numeric;
|
|
}
|