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

    Código digital representando identificadores UUID únicos e ordenáveis no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    uuid no Python: IDs únicos e ordenáveis

    Aprenda uuid no Python: versões 4, 5, 6 e 7, validação, bancos de dados, IDs ordenáveis e cuidados de segurança.

    Ler mais

    Tempo de leitura: 8 minutos
    30/07/2026
    Arquivos organizados representando armazenamento temporário seguro no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    tempfile no Python: arquivos temporários

    Aprenda tempfile no Python para criar arquivos e pastas temporárias com segurança, limpeza automática e suporte a Windows e Unix.

    Ler mais

    Tempo de leitura: 8 minutos
    29/07/2026
    Relógios representando fusos horários internacionais com zoneinfo no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    zoneinfo no Python: fusos horários

    Aprenda zoneinfo no Python para converter fusos, lidar com horário de verão, fold, UTC e tzdata sem erros de agendamento.

    Ler mais

    Tempo de leitura: 8 minutos
    29/07/2026
    Ícone de documentos duplicados representando cópia rasa e profunda no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    copy no Python: cópia rasa e profunda

    Aprenda copy no Python para criar cópias rasas, profundas e substituir campos sem compartilhar objetos mutáveis por engano.

    Ler mais

    Tempo de leitura: 8 minutos
    28/07/2026
    Monitor com busca binária e listas ordenadas no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    bisect no Python: listas sempre ordenadas

    Aprenda bisect no Python para manter listas ordenadas, encontrar intervalos e inserir valores com busca binária eficiente.

    Ler mais

    Tempo de leitura: 8 minutos
    27/07/2026
    Desenvolvedor implementando fila de prioridade com heapq no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    heapq no Python: filas de prioridade

    Aprenda heapq no Python para criar filas de prioridade, encontrar menores valores e processar tarefas com heaps eficientes.

    Ler mais

    Tempo de leitura: 7 minutos
    26/07/2026