Files
ritmex-bot/src/exchanges/edgex/decimal.ts
T

116 lines
4.0 KiB
TypeScript

import { strict as assert } from "assert";
export function countBase10Scale(resolution: bigint): number {
let scale = 0;
let value = resolution;
while (value % 10n === 0n) {
value /= 10n;
scale += 1;
}
if (value !== 1n) {
throw new Error(`Resolution ${resolution.toString()} is not a power of 10`);
}
return scale;
}
export function decimalToBigInt(value: number | string, scale: number): bigint {
const normalized = normalizeDecimal(typeof value === "number" ? value.toString() : value);
const [intPart, fracPart = ""] = normalized.split(".");
assert(fracPart.length <= scale, `Value ${value} exceeds scale ${scale}`);
const paddedFraction = (fracPart + "0".repeat(scale)).slice(0, scale);
const digits = stripLeadingZeros(intPart + paddedFraction);
return digits.length === 0 ? 0n : BigInt(digits);
}
export function bigIntToDecimal(value: bigint, scale: number): string {
const negative = value < 0n;
const abs = negative ? -value : value;
const factor = 10n ** BigInt(scale);
const intPart = abs / factor;
const fracPart = abs % factor;
if (scale === 0) {
return `${negative ? "-" : ""}${intPart.toString()}`;
}
const fracStr = fracPart.toString().padStart(scale, "0").replace(/0+$/, "");
if (fracStr.length === 0) {
return `${negative ? "-" : ""}${intPart.toString()}`;
}
return `${negative ? "-" : ""}${intPart.toString()}.${fracStr}`;
}
export function multiplyByDecimal(value: bigint, rate: string, roundUp = false): bigint {
const { numerator, denominator } = decimalToFraction(rate);
const product = value * numerator;
if (!roundUp) {
return product / denominator;
}
return (product + denominator - 1n) / denominator;
}
export function decimalToFraction(value: string): { numerator: bigint; denominator: bigint } {
const normalized = normalizeDecimal(value);
if (!normalized.includes(".")) {
return { numerator: BigInt(normalized), denominator: 1n };
}
const negative = normalized.startsWith("-");
const unsigned = negative ? normalized.slice(1) : normalized;
const parts = unsigned.split(".");
const intPart = parts[0] ?? "0";
const fracPart = parts[1] ?? "";
const denominator = 10n ** BigInt(fracPart.length);
const magnitude = BigInt(stripLeadingZeros(intPart + fracPart));
const numerator = negative ? -magnitude : magnitude;
return { numerator, denominator };
}
export function getScaleFromDenominator(denominator: bigint): number {
let scale = 0;
let value = denominator;
while (value > 1n) {
if (value % 10n !== 0n) {
throw new Error(`Denominator ${denominator.toString()} is not a power of 10`);
}
value /= 10n;
scale += 1;
}
return scale;
}
export function formatDecimal(numerator: bigint, scale: number): string {
const negative = numerator < 0n;
let absValue = negative ? -numerator : numerator;
if (scale === 0) {
return `${negative ? "-" : ""}${absValue.toString()}`;
}
const factor = 10n ** BigInt(scale);
const intPart = absValue / factor;
let fracPart = (absValue % factor).toString().padStart(scale, "0");
fracPart = fracPart.replace(/0+$/, "");
if (fracPart.length === 0) {
return `${negative ? "-" : ""}${intPart.toString()}`;
}
return `${negative ? "-" : ""}${intPart.toString()}.${fracPart}`;
}
function normalizeDecimal(input: string): string {
const trimmed = input.trim();
if (!/^[-+]?((\d+\.?\d*)|(\.\d+))$/.test(trimmed)) {
throw new Error(`Invalid decimal value: ${input}`);
}
const negative = trimmed.startsWith("-");
const unsigned = trimmed.replace(/^[-+]/, "");
const [rawInt = "0", rawFrac = ""] = unsigned.split(".");
const intDigits = stripLeadingZeros(rawInt);
const fracDigits = rawFrac.replace(/0+$/, "");
const magnitude = fracDigits.length > 0 ? `${intDigits}.${fracDigits}` : intDigits;
if (magnitude === "0") {
return "0";
}
return negative ? `-${magnitude}` : magnitude;
}
function stripLeadingZeros(value: string): string {
const stripped = value.replace(/^0+/, "");
return stripped.length === 0 ? "0" : stripped;
}