Files
Investidor-V2/resources/js/lib/fiscal-rational.ts
T

78 lines
2.4 KiB
TypeScript

/** Exact amounts in currency units; decimal formatting is presentation, never a tax rule. */
export type FiscalFraction = { numerator: string; denominator: string };
function parts(value: FiscalFraction): [bigint, bigint] {
if (
!/^-?\d{1,80}$/.test(value.numerator) ||
!/^\d{1,80}$/.test(value.denominator) ||
BigInt(value.denominator) === 0n
)
throw new Error(
'Representação racional inválida; sem substituição por zero.',
);
return [BigInt(value.numerator), BigInt(value.denominator)];
}
export function fraction(n: bigint, d = 1n): FiscalFraction {
if (d === 0n) throw new Error('Denominador nulo.');
if (d < 0n) {
n = -n;
d = -d;
}
let a = n < 0n ? -n : n,
b = d;
while (b) {
const r = a % b;
a = b;
b = r;
}
return {
numerator: String(n / (a || 1n)),
denominator: String(d / (a || 1n)),
};
}
export function decimalFraction(raw: string): FiscalFraction {
if (!/^\d{1,18}(\.\d{1,6})?$/.test(raw))
throw new Error('Montante decimal inválido.');
const [a, b = ''] = raw.split('.');
return fraction(BigInt(a + b), 10n ** BigInt(b.length));
}
export function addFractions(values: FiscalFraction[]): FiscalFraction {
return values.reduce((a, b) => {
const [an, ad] = parts(a),
[bn, bd] = parts(b);
return fraction(an * bd + bn * ad, ad * bd);
}, fraction(0n));
}
export function subtractFractions(
a: FiscalFraction,
b: FiscalFraction,
): FiscalFraction {
const [an, ad] = parts(a),
[bn, bd] = parts(b);
return fraction(an * bd - bn * ad, ad * bd);
}
export function positiveFraction(value: FiscalFraction): FiscalFraction {
return parts(value)[0] > 0n ? value : fraction(0n);
}
export function fractionPresentation(value: FiscalFraction): {
decimal: string;
approximate: boolean;
} {
const [raw, d] = parts(value),
n = raw < 0n ? -raw : raw,
scaled = n * 1000000n;
const q = (scaled + d / 2n) / d,
dec = (q % 1000000n)
.toString()
.padStart(6, '0')
.replace(/0+$/, '')
.padEnd(2, '0');
return {
decimal: `${raw < 0n ? '-' : ''}${q / 1000000n}.${dec}`,
approximate: scaled % d !== 0n,
};
}
export function fractionLabel(value: FiscalFraction): string {
parts(value);
return `${value.numerator}/${value.denominator}`;
}