function executar(input = '1234', mode = 'Número') {
const { parseDecimal: parseDecimal } = (() => {
function parse(input, label) {
const text = input.trim();
if (text.length > 30 || !/^[+-]?\d{1,15}(?:[.,]\d{1,12})?$/.test(text))
throw new Error(`${label}: informe um decimal válido, com até 15 dígitos inteiros e 12 casas; sem agrupamento, expoentes ou separadores misturados.`);
const [integer, decimal = ''] = text.replace(',', '.').split('.');
const sign = integer.startsWith('-') ? -BigInt(1) : BigInt(1);
return { n: sign * BigInt(integer.replace(/^[+-]/, '') + decimal), d: BigInt(10) ** BigInt(decimal.length) };
}
return { parseDecimal: parse };
})();
const NUMBER_WORDS_MODES = ['Número', 'Valor em reais'];
const UNITS = ['zero', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove', 'dez', 'onze', 'doze', 'treze', 'quatorze', 'quinze', 'dezesseis', 'dezessete', 'dezoito', 'dezenove'];
const TENS = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa'];
const HUNDREDS = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos'];
const SCALES = [['mil', 'mil'], ['milhão', 'milhões'], ['bilhão', 'bilhões'], ['trilhão', 'trilhões']];
const FRACTIONS = ['décimo', 'centésimo', 'milésimo', 'décimo de milésimo', 'centésimo de milésimo', 'milionésimo', 'décimo de milionésimo', 'centésimo de milionésimo', 'bilionésimo', 'décimo de bilionésimo', 'centésimo de bilionésimo', 'trilionésimo'];
const THOUSAND = BigInt(1000);
const MILLION = BigInt(1000000);
function group(value) {
if (value === 100)
return 'cem';
const rest = value % 100;
const parts = [];
if (value >= 100)
parts.push(HUNDREDS[Math.floor(value / 100)]);
if (rest)
parts.push(rest < 20 ? UNITS[rest] : TENS[Math.floor(rest / 10)] + (rest % 10 ? ' e ' + UNITS[rest % 10] : ''));
return parts.join(' e ');
}
function cardinal(value) {
if (value === BigInt(0))
return UNITS[0];
const groups = [];
for (let rest = value; rest > BigInt(0); rest /= THOUSAND)
groups.push(Number(rest % THOUSAND));
if (groups.length > SCALES.length + 1)
throw new Error('Número acima de 999 trilhões.');
const lowest = groups.findIndex(item => item !== 0);
const parts = [];
for (let index = groups.length - 1; index >= 0; index--) {
const item = groups[index];
if (!item)
continue;
const text = index === 0 ? group(item) : index === 1 && item === 1 ? 'mil' : `${group(item)} ${SCALES[index - 1][item === 1 ? 0 : 1]}`;
parts.push((parts.length && index === lowest && (item < 100 || item % 100 === 0) ? 'e ' : '') + text);
}
return parts.join(' ');
}
function counted(value, singular, plural) {
const words = cardinal(value);
if (value >= MILLION && value % MILLION === BigInt(0))
return `${words} de ${plural}`;
return `${words} ${value <= BigInt(1) ? singular : plural}`;
}
function numberToWords(input, mode = NUMBER_WORDS_MODES[0]) {
if (!NUMBER_WORDS_MODES.some(value => value === mode))
throw new Error('Selecione um formato válido.');
const value = parseDecimal(input, 'Número');
const places = value.d.toString().length - 1;
const digits = (value.n < BigInt(0) ? -value.n : value.n).toString().padStart(places + 1, '0');
const integer = BigInt(digits.slice(0, digits.length - places));
const fraction = digits.slice(digits.length - places);
const prefix = value.n < BigInt(0) ? 'menos ' : '';
if (mode === NUMBER_WORDS_MODES[1]) {
if (/[^0]/.test(fraction.slice(2)))
throw new Error('Valor em reais aceita no máximo duas casas decimais (centavos); não há arredondamento.');
const cents = BigInt(fraction.slice(0, 2).padEnd(2, '0'));
const parts = [];
if (integer > BigInt(0))
parts.push(counted(integer, 'real', 'reais'));
if (cents > BigInt(0))
parts.push(counted(cents, 'centavo', 'centavos'));
return prefix + (parts.join(' e ') || 'zero real');
}
const numerator = BigInt(fraction || '0');
const whole = counted(integer, 'inteiro', 'inteiros');
if (numerator === BigInt(0))
return prefix + whole;
const name = FRACTIONS[places - 1];
return `${prefix}${whole} e ${counted(numerator, name, name.replace(/^\S+/, word => word + 's'))}`;
}
return numberToWords(input, mode);
}