Decimal no Python: cálculos precisos

Publicado em: 30/07/2026
Tempo de leitura: 8 minutos
Calculadora e documentos representando cálculos decimais precisos no Python

Valores monetários, impostos, taxas, medições e regras contábeis exigem resultados previsíveis. O tipo float é rápido e adequado para muitas tarefas científicas, mas representa números em base binária. Por isso, frações decimais simples como 0,1 nem sempre são armazenadas exatamente. O módulo Decimal no Python oferece aritmética decimal com precisão configurável, regras explícitas de arredondamento e mecanismos para detectar operações inexatas.

Neste guia, você aprenderá a criar valores corretamente, usar quantize(), configurar contextos, escolher modos de arredondamento, ativar traps, validar entradas e integrar Decimal com APIs e bancos de dados. O conteúdo complementa nossos artigos sobre float em Python, f-strings para números e moedas, tipos de dados, UUIDs e cópias de objetos.

Por que float surpreende

Em ponto flutuante binário, vários valores decimais são aproximações:

print(0.1 + 0.2)          # 0.30000000000000004
print(0.1 + 0.1 + 0.1 == 0.3)  # False

Isso não é um defeito específico do Python. A documentação oficial sobre ponto flutuante explica que 0,1 possui expansão infinita em base 2, assim como 1/3 possui expansão infinita em base 10. O computador armazena a fração binária representável mais próxima.

Para simulações e gráficos, o erro costuma ser aceitável. Para contabilidade, preços e invariantes de igualdade decimal, uma representação em base 10 é frequentemente mais apropriada.

Criando Decimal a partir de strings

Importe Decimal e passe uma string com o valor pretendido:

from decimal import Decimal

preco = Decimal("19.90")
taxa = Decimal("0.075")
total = preco * (Decimal("1") + taxa)

print(total)

A string preserva exatamente os dígitos informados, inclusive zeros finais significativos. Para valores vindos de formulários, JSON textual ou configurações, essa é a forma preferida.

Evite Decimal construído de float

Construir diretamente com um float converte exatamente a aproximação binária já armazenada:

from decimal import Decimal

print(Decimal(0.1))
# 0.100000000000000005551115123125782...

Isso pode ser útil para inspecionar o valor real de um float, mas não para representar o decimal pretendido pelo usuário. Prefira Decimal("0.1"). Quando uma biblioteca entrega float e você precisa apenas da representação exibida, uma estratégia deliberada é converter por str(), documentando a perda controlada:

valor = Decimal(str(0.1))

Inteiros e Decimal.from_number()

Inteiros são convertidos exatamente:

quantidade = Decimal(3)
subtotal = Decimal("9.90") * quantidade

No Python 3.14, Decimal.from_number() aceita int, float ou outro Decimal, mas não strings. Um float continua sendo convertido com todos os seus dígitos binários equivalentes.

valor = Decimal.from_number(314)
print(valor)  # 314

Significância e zeros finais

Decimal conserva zeros finais para indicar significância:

from decimal import Decimal

print(Decimal("1.30") + Decimal("1.20"))  # 2.50
print(Decimal("1.30") * Decimal("1.20"))  # 1.5600

O valor numérico de 2,50 é igual ao de 2,5, mas a representação pode comunicar duas casas decimais. Essa característica é útil em relatórios financeiros e medições.

Arredondando com quantize()

quantize() arredonda para o expoente de outro Decimal. Para duas casas:

from decimal import Decimal, ROUND_HALF_UP

CENTAVO = Decimal("0.01")
valor = Decimal("7.325")
resultado = valor.quantize(CENTAVO, rounding=ROUND_HALF_UP)
print(resultado)  # 7.33

Use uma constante para evitar repetir a escala. O arredondamento deve acontecer no ponto definido pela regra de negócio: por item, por linha, por imposto ou apenas no total. Arredondar em etapas diferentes pode produzir resultados diferentes.

ROUND_HALF_EVEN e ROUND_HALF_UP

O contexto padrão usa ROUND_HALF_EVEN, também conhecido como arredondamento para o par. Em empates exatos, escolhe o último dígito par e reduz viés acumulado.

from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP

valor = Decimal("2.5")
print(valor.quantize(Decimal("1"), rounding=ROUND_HALF_EVEN))  # 2
print(valor.quantize(Decimal("1"), rounding=ROUND_HALF_UP))    # 3

Muitas regras comerciais exigem ROUND_HALF_UP. Outras usam truncamento, teto ou piso. Não escolha pelo nome mais familiar; siga a legislação ou especificação do domínio.

Outros modos de arredondamento

O módulo oferece ROUND_DOWN, ROUND_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_HALF_DOWN e ROUND_05UP. A diferença entre down e floor aparece em valores negativos: ROUND_DOWN aproxima de zero, enquanto ROUND_FLOOR aproxima de menos infinito.

Escreva testes com números positivos, negativos e empates. Um conjunto que testa apenas 1,235 pode esconder erros em -1,235.

O contexto decimal

A documentação oficial de decimal organiza o módulo em números, contextos e sinais. O contexto define precisão, arredondamento, limites de expoente, flags e traps.

from decimal import Decimal, getcontext

contexto = getcontext()
print(contexto.prec)      # 28 por padrão
print(contexto.rounding)

contexto.prec = 10
print(Decimal(1) / Decimal(7))

A precisão controla o número de dígitos significativos das operações, não quantas casas decimais o resultado terá. A criação a partir de string conserva todos os dígitos; o contexto entra em ação durante cálculos.

Use localcontext() para mudanças temporárias

Alterar o contexto global no meio de uma biblioteca pode surpreender outros códigos. Use localcontext() para isolar a configuração:

from decimal import Decimal, localcontext

with localcontext(prec=50) as ctx:
    resultado = Decimal(1) / Decimal(7)
    print(resultado)

# contexto anterior restaurado

Esse padrão é ideal para uma etapa de alta precisão sem alterar o restante da aplicação. Em Python 3.11 ou posterior, atributos podem ser informados diretamente como argumentos nomeados.

Flags e sinais

Operações podem sinalizar condições como Inexact, Rounded, DivisionByZero, InvalidOperation, Overflow e Underflow. As flags ficam marcadas até serem limpas.

from decimal import Decimal, getcontext

ctx = getcontext()
ctx.clear_flags()
Decimal(1) / Decimal(7)
print(ctx.flags)

Flags permitem auditar um lote sem interrompê-lo. Limpe-as antes da etapa monitorada, execute os cálculos e inspecione as condições no final.

Traps transformam sinais em exceções

Uma trap ativa faz o sinal lançar exceção. Isso é útil quando a aplicação não pode aceitar arredondamento silencioso.

from decimal import Decimal, Inexact, localcontext

with localcontext() as ctx:
    ctx.traps[Inexact] = True
    try:
        Decimal("1") / Decimal("3")
    except Inexact:
        print("Resultado inexato não permitido")

Você pode usar traps em validações financeiras, importações e testes. Em produção, capture exceções específicas e apresente mensagens adequadas, sem esconder dados inconsistentes.

Detectando mistura com float

O sinal FloatOperation ajuda a impedir que floats entrem acidentalmente no fluxo decimal.

from decimal import Decimal, FloatOperation, localcontext

with localcontext() as ctx:
    ctx.traps[FloatOperation] = True
    Decimal(3.14)  # levanta FloatOperation

Essa proteção é valiosa em módulos financeiros. Ela força a equipe a decidir conscientemente como converter cada fonte.

Exemplo de preço, desconto e imposto

from decimal import Decimal, ROUND_HALF_UP

CENTAVO = Decimal("0.01")
preco = Decimal("149.90")
quantidade = Decimal("3")
desconto = Decimal("0.10")
imposto = Decimal("0.075")

subtotal = preco * quantidade
apos_desconto = subtotal * (Decimal("1") - desconto)
total = apos_desconto * (Decimal("1") + imposto)
total = total.quantize(CENTAVO, rounding=ROUND_HALF_UP)

print(total)

Em uma aplicação real, confirme se desconto e imposto são calculados por item ou sobre o total. A biblioteca executa a regra; ela não escolhe a regra contábil.

Percentuais e taxas

Converta “7,5%” para Decimal("0.075"). Evite dividir um float por 100 antes da conversão. Para entrada textual localizada, normalize separadores com uma função explícita, rejeitando formatos ambíguos.

def percentual(texto: str) -> Decimal:
    valor = Decimal(texto.replace(",", "."))
    return valor / Decimal("100")

Essa função simples não deve aceitar milhares e moedas sem uma biblioteca de localização. Uma string como “1.234,56” exige regras de locale e validação mais rigorosa.

Comparações e tipos mistos

Somar Decimal e float gera TypeError, o que evita mistura silenciosa:

Decimal("1.2") + 1.2  # TypeError

Inteiros podem participar normalmente. Comparações com floats são suportadas, mas podem refletir a aproximação binária. Em um domínio decimal, normalize os dois operandos antes de comparar.

NaN, Infinity e zero negativo

Decimal suporta valores especiais:

from decimal import Decimal

valores = [
    Decimal("NaN"),
    Decimal("Infinity"),
    Decimal("-Infinity"),
    Decimal("-0"),
]

Não permita que esses valores entrem em preços e saldos sem uma política explícita. Use is_finite(), is_nan() e is_infinite() na validação.

Serialização em JSON

O módulo json padrão não serializa Decimal automaticamente. Converter para float reintroduz aproximação. Para APIs financeiras, envie string:

registro = {
    "total": str(Decimal("19.90")),
    "currency": "BRL",
}

Documente o contrato para que o consumidor interprete a string como decimal. Outra opção é usar um encoder ou framework que preserve o tipo.

Banco de dados

Use colunas DECIMAL ou NUMERIC com precisão e escala definidas. Não armazene dinheiro em coluna binária FLOAT. ORMs normalmente convertem esses campos para Decimal.

Valide os limites antes da gravação. Um campo NUMERIC(10,2) não aceita qualquer magnitude, e o banco pode arredondar ou rejeitar conforme a configuração.

Desempenho

Decimal costuma ser mais lento que float porque oferece regras e precisão adicionais. Não substitua todos os floats automaticamente. Use float para gráficos, física e cálculos numéricos tolerantes; Decimal para valores decimais exatos e arredondamento regulamentado.

Meça pipelines grandes. Às vezes, armazenar centavos como inteiros é mais simples, desde que a moeda e a escala sejam fixas. Juros, câmbio e quantidades fracionárias frequentemente exigem Decimal.

Testando cálculos decimais

Testes devem cobrir empates de arredondamento, números negativos, limites, entradas inválidas e ordem das operações.

from decimal import Decimal, ROUND_HALF_UP

assert Decimal("2.675").quantize(
    Decimal("0.01"), rounding=ROUND_HALF_UP
) == Decimal("2.68")

Compare Decimal com Decimal, não com float. Também teste invariantes: total das parcelas, soma de impostos e reconciliação com o banco.

Erros frequentes

  • Criar Decimal(0.1) esperando exatamente 0,1.
  • Arredondar apenas na apresentação quando a regra exige arredondamento contábil.
  • Modificar o contexto global dentro de uma função reutilizável.
  • Misturar Decimal e float.
  • Converter para float antes de serializar.
  • Escolher o modo de arredondamento sem consultar a regra do domínio.
  • Ignorar flags de inexatidão.
  • Usar Decimal em todo cálculo sem medir desempenho.

Boas práticas

  • Crie valores de strings ou inteiros.
  • Centralize constantes como centavo e modos de arredondamento.
  • Use localcontext() para precisão temporária.
  • Ative FloatOperation em módulos críticos.
  • Valide valores finitos.
  • Armazene em tipos DECIMAL/NUMERIC.
  • Serializar como string quando a precisão deve ser preservada.
  • Teste regras financeiras com exemplos oficiais.

Conclusão

O módulo Decimal no Python oferece controle que o ponto flutuante binário não foi projetado para fornecer: representação decimal exata, zeros significativos, precisão configurável, regras de arredondamento e sinais auditáveis.

O uso correto começa na entrada: crie valores a partir de strings, defina a escala com quantize(), isole contextos e impeça misturas acidentais com float. Quando regras de negócio e armazenamento seguem o mesmo contrato, cálculos de dinheiro, taxas e medições tornam-se reproduzíveis, testáveis e muito menos sujeitos a diferenças de centavos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Documento e caixa de entrada representando caixas de e-mail com mailbox no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    mailbox no Python: caixas de e-mail

    Aprenda mailbox no Python para ler, criar e migrar caixas Maildir, mbox e MH com locking, mensagens, flags e tratamento

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Editor de texto representando formatação com textwrap no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    textwrap no Python: formate textos

    Aprenda textwrap no Python para quebrar, preencher, encurtar, indentar e remover recuos de textos com controle de largura e espaços.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Pasta e lupa representando filtros de nomes com fnmatch no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    fnmatch no Python: filtre nomes de arquivos

    Aprenda fnmatch no Python para filtrar nomes de arquivos com curingas, controlar maiúsculas, excluir padrões e evitar confundir glob com

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor com dados binários representando arrays numéricos compactos no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    array no Python: números compactos

    Aprenda array no Python para armazenar números compactos, manipular bytes, arquivos binários e buffers com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Círculo cromático representando conversões RGB, HSV e HLS com colorsys no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys no Python: RGB, HSV e HLS

    Aprenda colorsys no Python para converter cores entre RGB, HSV, HLS e YIQ, gerar paletas e evitar erros com escalas

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Ícone de configuração representando arquivos plist com plistlib no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib no Python: arquivos plist

    Aprenda plistlib no Python para ler e gravar arquivos plist XML e binários, validar dados e integrar configurações Apple com

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026