Pular para o conteúdo

Cálculo financeiro

Folha de pagamento com faixas progressivas — em Decimal, e com o centavo que sobra repartido.

Dinheiro tem duas regras que float quebra: somar tem de ser exato, e dividir tem de fechar. Um salário de R$ 1000,00 dividido em três parcelas não são três de 333,33: é 333,34 + 333,33 + 333,33. Este projeto calcula uma folha com desconto progressivo por faixa e reparte sem perder centavo.

PeçaO que ela exercita
19.99do literal decimal, construído do texto
Dec.arredondararredondar com modo declarado
Dec.repartirdividir e o centavo fechar
tabela de faixaso imposto progressivo como dado

Estrutura#

text
folha/
  src/
    faixas.df     a tabela do ano — so dados
    calculo.df    bruto -> descontos -> liquido
    holerite.df   o texto que vai para a pessoa
  tests/
    calculo_test.df  com os exemplos da tabela oficial
forge.toml
[project]
name = "folha"
version = "0.1.0"
description = "Folha de pagamento"
entry = "src/main.df"
dataforge = ">=1.1"

[dependencies]

[scripts]
start = "run src/main.df"
test = "test tests/"

O núcleo#

Este bloco roda sozinho — copie para um arquivo e rode dataforge run. Ele termina com assert, e é assim que esta página é conferida a cada build.

src/calculo.df
adopt Arcane.Decimal as Dec

// Faixas de exemplo (NAO sao as oficiais): 'ate' e o teto da faixa.
FAIXAS := [
    {"ate": 2000.00d, "aliquota": 0.075d},
    {"ate": 3500.00d, "aliquota": 0.09d},
    {"ate": 5000.00d, "aliquota": 0.12d},
    {"ate": void,     "aliquota": 0.14d}
]

// Progressivo: cada faixa cobra so o PEDACO do salario que cai nela.
action desconto(bruto, faixas):
    total := Dec.zero()
    piso := Dec.zero()
    cycle f in faixas:
        teto := f["ate"] ?? bruto
        given bruto bigger piso:
            base := min(bruto, teto) - piso
            given base bigger Dec.zero():
                total := total + base * f["aliquota"]
        piso := teto
    yield Dec.arredondar(total, 2)

action holerite(nome, bruto):
    d := desconto(bruto, FAIXAS)
    yield {"nome": nome, "bruto": bruto, "desconto": d, "liquido": bruto - d}

h := holerite("Ana", 4200.00d)
out $"{h['nome']}: bruto {Dec.texto(h['bruto'])}, desconto {Dec.texto(h['desconto'])}, liquido {Dec.texto(h['liquido'])}"

// 2000 * 7,5% + 1500 * 9% + 700 * 12% = 150 + 135 + 84 = 369
assert Dec.texto(h["desconto"]) is "369.00"
assert Dec.texto(h["liquido"]) is "3831.00"

// A parcela que fecha: tres de 333,33 perderiam um centavo.
parcelas := Dec.repartir(1000.00d, 3)
out parcelas >> morph p: Dec.texto(p)
assert Dec.texto(Dec.soma(parcelas)) is "1000.00"

O teste#

No projeto, a regra mora em src/ e o teste a importa pelo caminho relativo — dataforge test tests/ descobre o arquivo sozinho.

tests/nucleo_test.df
adopt Arcane.Decimal as Dec
adopt ../src/calculo as C

crucible "desconto progressivo":
    trial "abaixo da primeira faixa":
        expect Dec.texto(C.desconto(1000.00d, C.FAIXAS)) is "75.00"

    trial "a soma das parcelas fecha":
        expect Dec.texto(Dec.soma(Dec.repartir(10.00d, 3))) is "10.00"

As decisões#

DecisãoSem ela
o literal dDecimal(0.1) já nasce com o erro do float
a faixa cobra só o pedaço delao salário de 2001 paga 9% sobre tudo, e ganhar um real a mais reduz o líquido
repartir em vez de dividiro parcelamento some com um centavo por cliente, e a conciliação nunca fecha
as faixas são um arquivo de dadosa atualização anual vira uma mudança de código

Para ir além#

  • Compare com Arcane.Decimal e Decimal.
  • Gere o holerite em planilha: Planilhas.
  • Um teste por linha da tabela oficial — quando a lei muda, o teste muda junto.

Volte para todos os tipos de projeto.