IRRF: por que ROUND_HALF_UP é a única forma certa de calcular em Python
Banker's rounding do Python round() erra cálculo de IRRF. Veja por que ROUND_HALF_UP do Decimal é obrigatório, com 414 linhas de testes do Odoo brasileiro.
Luis Felipe Miléo
A função round() do Python tem um comportamento que pega muita gente de surpresa: round(0.5) == 0 e round(1.5) == 2. Não é bug. É banker’s rounding (arredondamento bancário, ou ROUND_HALF_EVEN), o padrão IEEE 754. Em folha de pagamento brasileira, isso está errado — e o erro custa centavos por colaborador, milhares de reais por ano em folhas grandes, e infinitos problemas de auditoria. Este post explica por que a folha CLT da KMEE no Odoo usa decimal.ROUND_HALF_UP em todos os cálculos de IRRF, com 414 linhas de testes provando o comportamento correto.
O bug clássico
>>> round(2.675, 2)
2.67
Espera, o quê? Cinco arredonda para cima. Resposta deveria ser 2.68.
São dois problemas combinados:
floaté binário.2.675em IEEE 754 não é exatamente 2.675. É 2.67499999999999982236431605997495353221893310546875. Quando o Python arredonda, o número já não é mais 2.675.round()usa banker’s rounding. Mesmo com tipos exatos,round(0.5)arredonda para 0 (vizinho par mais próximo),round(1.5)arredonda para 2 (vizinho par mais próximo). Em uma distribuição grande de números, isso evita viés estatístico — útil em estatística, contraintuitivo em folha.
A folha brasileira segue regra explícita: 5 sempre arredonda para cima. Quem implementa folha e usa round() do Python entrega valor errado.
A regra do IRRF
O IRRF mensal de empregado CLT segue tabela progressiva semelhante ao INSS, com cinco faixas e parcela a deduzir:
| Base de cálculo (2026) | Alíquota | Parcela a deduzir |
|---|---|---|
| até R$ 2.428,80 | isento | — |
| 2.428,81 a 2.826,65 | 7,5% | 182,16 |
| 2.826,66 a 3.751,05 | 15% | 394,16 |
| 3.751,06 a 4.664,68 | 22,5% | 675,49 |
| acima de 4.664,68 | 27,5% | 908,73 |
A base de cálculo é o salário bruto menos INSS retido, menos R$ 189,59 por dependente, menos pensão alimentícia. O imposto é base × alíquota - parcela.
Esses números, multiplicados, geram dízimas. R$ 3.500 × 15% = R$ 525,00 — fácil. Mas R$ 3.124,33 × 15% = R$ 468,6495 — precisa arredondar. Para cima ou para baixo?
A IN RFB 2.060/2021 diz: arredondamento para a segunda casa decimal, com 5 arredondando para cima.
A solução com Decimal
from decimal import Decimal, ROUND_HALF_UP
DEDUCAO_DEPENDENTE = Decimal("189.59")
IRRF_FAIXAS_2026 = [
(Decimal("2428.80"), Decimal("0.000"), Decimal("0.00")),
(Decimal("2826.65"), Decimal("0.075"), Decimal("182.16")),
(Decimal("3751.05"), Decimal("0.15"), Decimal("394.16")),
(Decimal("4664.68"), Decimal("0.225"), Decimal("675.49")),
(Decimal("999999.99"), Decimal("0.275"), Decimal("908.73")),
]
def calc_irrf(salario_bruto: Decimal, inss: Decimal, dependentes: int = 0,
pensao: Decimal = Decimal("0")) -> Decimal:
base = salario_bruto - inss - (DEDUCAO_DEPENDENTE * dependentes) - pensao
if base <= Decimal("0"):
return Decimal("0.00")
for limite, aliquota, deducao in IRRF_FAIXAS_2026:
if base <= limite:
imposto = (base * aliquota - deducao)
if imposto < Decimal("0"):
return Decimal("0.00")
return imposto.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
return Decimal("0.00")
Tudo Decimal. Tudo ROUND_HALF_UP. Sem float em momento algum.
Os 414 testes
O arquivo tests/test_calc_irrf.py no PR kmee/kmee-odoo-addons#277 tem 414 linhas. Algumas amostras:
def test_irrf_isento():
assert calc_irrf(salario=Decimal("2000"), inss=Decimal("150")) == Decimal("0.00")
def test_irrf_limite_exato_faixa_1():
# base 2428.80 -> isento
assert calc_irrf(Decimal("2620.00"), Decimal("191.20")) == Decimal("0.00")
def test_irrf_um_centavo_acima_isencao():
# base 2428.81 * 0.075 - 182.16 = 0.00
assert calc_irrf(Decimal("2620.01"), Decimal("191.20")) == Decimal("0.00")
def test_irrf_arredondamento_meio_centavo_para_cima():
# base que gera 0.005 deve arredondar para 0.01
# exemplo: 525.005 -> 525.01 (nao 525.00 como round() faria)
base = Decimal("525.005")
assert base.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) == Decimal("525.01")
def test_irrf_diferenca_round_vs_decimal():
# Reproducao do bug: round() retorna 525.00, Decimal+HALF_UP retorna 525.01
assert round(525.005, 2) == 525.0 # Python comportamento padrao
assert Decimal("525.005").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) == Decimal("525.01")
E mais quase 400 linhas cobrindo: dependentes (de zero a oito), pensão alimentícia, faixa por faixa, limites das faixas, salários acima de R$ 100k (regra continua válida), regressões de bugs reportados em produção, e testes contra valores oficiais da Receita Federal.
Impacto em folha real
Numa folha de 100 colaboradores com salários médios entre R$ 5.000 e R$ 15.000, o erro de round() Python aparece em cerca de 30-40% dos cálculos mensais (sempre que a terceira casa decimal é exatamente 5). A diferença média é de R$ 0,01 por cálculo. Multiplicando: 35 colaboradores × R$ 0,01 × 12 meses = R$ 4,20 por ano de imposto a menor (ou maior). Parece pouco — até a auditoria da Receita pedir os memoriais de cálculo e perceber que o sistema não bate com a IN.
Em folhas de 1.000+ colaboradores, o problema escala. E em órgãos públicos, onde o memorial de cálculo é auditado a cada folha, o erro vira processo administrativo.
Conclusão
A folha CLT do Odoo da KMEE, doada à OCA via PR #277, usa exclusivamente decimal.Decimal e ROUND_HALF_UP. Os 414 asserts do test_calc_irrf.py rodam em segundos e blindam contra regressões. Se você está implementando ou auditando folha em Python, a regra é simples: float e round() ficam fora de qualquer cálculo monetário. Decimal com ROUND_HALF_UP é a única forma certa.
Veja mais sobre folha de pagamento Odoo ou compare Odoo vs sistemas proprietários.
Sobre o autor
Luis Felipe Miléo
Desenvolvedor Odoo · KMEE
Desenvolvedor especializado em localização fiscal e projetos open source no ecossistema Odoo/OCA, com foco em integrações para o mercado latino-americano.
Ver perfil no LinkedInArtigos relacionados
Open Finance regulado vs APIs proprietárias: qual usar para cada caso
7 de jul. de 2026
Gestão EmpresarialDDA no Odoo: contas a pagar 100% automatizadas
30 de jun. de 2026
Gestão EmpresarialTOTVS está descontinuando sua API bancária — Odoo é a alternativa neutra
9 de jun. de 2026