/** 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}`; }