62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
const NANO = 1_000_000_000n;
|
|
|
|
export type ProtoDecimal = { units?: string | number; nano?: string | number };
|
|
export type Decimal = { value: bigint; scale: number };
|
|
|
|
export function decimalFromString(value: string): Decimal {
|
|
if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value))
|
|
throw new Error('Invalid decimal string');
|
|
const negative = value.startsWith('-');
|
|
const [whole, fraction = ''] = (negative ? value.slice(1) : value).split('.');
|
|
return {
|
|
value: (negative ? -1n : 1n) * BigInt(`${whole}${fraction}`),
|
|
scale: fraction.length,
|
|
};
|
|
}
|
|
|
|
export function decimal(value: ProtoDecimal): Decimal {
|
|
return {
|
|
value: BigInt(value.units ?? 0) * NANO + BigInt(value.nano ?? 0),
|
|
scale: 9,
|
|
};
|
|
}
|
|
|
|
export function add(left: Decimal, right: Decimal): Decimal {
|
|
const scale = Math.max(left.scale, right.scale);
|
|
return {
|
|
value:
|
|
left.value * 10n ** BigInt(scale - left.scale) +
|
|
right.value * 10n ** BigInt(scale - right.scale),
|
|
scale,
|
|
};
|
|
}
|
|
|
|
export function subtract(left: Decimal, right: Decimal): Decimal {
|
|
return add(left, { value: -right.value, scale: right.scale });
|
|
}
|
|
|
|
export function multiply(left: Decimal, right: Decimal): Decimal {
|
|
return { value: left.value * right.value, scale: left.scale + right.scale };
|
|
}
|
|
|
|
export function isZero(value: Decimal): boolean {
|
|
return value.value === 0n;
|
|
}
|
|
|
|
export function compare(left: Decimal, right: Decimal): number {
|
|
const difference = subtract(left, right).value;
|
|
return difference === 0n ? 0 : difference > 0n ? 1 : -1;
|
|
}
|
|
|
|
export function formatDecimal({ value, scale }: Decimal): string {
|
|
if (value === 0n) return '0';
|
|
const sign = value < 0n ? '-' : '';
|
|
if (scale === 0) return `${sign}${value < 0n ? -value : value}`;
|
|
const digits = (value < 0n ? -value : value)
|
|
.toString()
|
|
.padStart(scale + 1, '0');
|
|
const whole = digits.slice(0, -scale) || '0';
|
|
const fraction = digits.slice(-scale).replace(/0+$/, '');
|
|
return `${sign}${whole}${fraction ? `.${fraction}` : ''}`;
|
|
}
|