Número exato
Arcane.Decimal — para quando 0,1 + 0,2 precisa dar 0,3: dinheiro, imposto, e todo número que alguém confere na mão.
out 0.1 + 0.2 # 0.30000000000000004Float é IEEE 754 de 64 bits, e ele não representa 0,1. Representa o binário mais próximo, e a diferença aparece na soma.
Isso não é defeito do DataForge — é assim em toda linguagem com ponto flutuante binário, e é o certo para física, estatística e gráficos, onde a precisão relativa importa mais que o dígito decimal exato.
A resposta: o sufixo `d`#
assert 0.1 + 0.2 is not 0.3 // Float: o classico
assert 0.1d + 0.2d is 0.3d // Decimal: exato
preco := 19.99d
out typeof(preco) // Decimal
out preco * 3 // 59.97O sufixo `d` faz o literal nascer Decimal, construído a partir do texto. É a diferença que decide: Decimal("0.1") é exato, e Decimal(0.1) já carrega o erro do float que veio antes dele.
Antes do sufixo, a exatidão exigia escrever Dec.de("19.99") — e quem escrevia 19.99 não era avisado de nada. O literal e o módulo produzem o mesmo valor:
adopt Arcane.Decimal as Dec
assert 19.99d is Dec.de("19.99")
assert Dec.e_decimal(19.99d) is yes
assert Dec.centavos(19.99d) is 1999
out "o literal e Dec.de sao o mesmo valor"O módulo, para o resto#
adopt Arcane.Decimal as Dec
a := Dec.de("0.1")
b := Dec.de("0.2")
out a + b # 0.3Decimal guarda o número em base dez, do jeito que foi escrito. A aritmética é a da própria linguagem — +, -, *, /, bigger, is funcionam — porque o interpretador trata valor por protocolo, e não por tipo.
preco := Dec.de("19.99")
out preco * 3 # 59.97
out typeof(preco) # Decimal
out max([Dec.de("3.10"), Dec.de("1.05")]) # 3.10Arredondar é meio-para-cima#
out Dec.arredondar(Dec.de("2.5")) # 3
out round(2.5) # 2.0O round embutido faz arredondamento bancário: 0,5 vira 0 e 2,5 vira 2. É o certo para estatística, porque não enviesa uma série longa.
Para dinheiro é errado: 2,5 centavos precisam virar 3, sempre, ou o cliente reclama do extrato. É a mesma decisão que o pacote moeda já tinha tomado.
Os outros modos continuam à mão: MEIO_PAR, MEIO_PARA_BAIXO, CIMA, BAIXO, TRUNCA, LONGE_DO_ZERO.
Repartir sem perder centavo#
partes := Dec.repartir(Dec.de("10.00"), 3)
out partes # [3.34, 3.33, 3.33]
out Dec.soma(partes) # 10.00Três vezes 3,33 são 9,99, e o décimo real sumiu. Dividir dinheiro em partes iguais quase nunca dá partes iguais — o resto é distribuído um centavo por parte, que é como uma conta é rateada de verdade.
Misturar exato com aproximado é recusado#
erro[DF0301]: '+' entre um Decimal e um Float e recusado.
= nota: a esquerda e exata e a direita e aproximada; o resultado
seria aproximado, e a garantia se perderia sem aviso
= dica: converta o lado que falta:
Decimal.de(x) + exato para seguir exato
Decimal.float(exato) + x para aceitar o floatSomar um número exato com um aproximado devolve um aproximado. A recusa é deliberada: sem ela, a garantia que se veio buscar desapareceria em silêncio, e o erro só apareceria três somas depois.
Com inteiro funciona sem conversão — inteiro é exato, então não há nada a perder.
`Dec.de(0.1)` devolve `0.1`#
E não 0.1000000000000000055511151231257827…, que é o binário cru. A conversão passa pelo texto do número: é o que a pessoa escreveu e o que ela quer dizer. Converter o binário seria tecnicamente mais fiel e praticamente inútil — ninguém digita 0.1 querendo o binário mais próximo dele.
A tabela inteira#
| Faz | |
|---|---|
Dec.de(x) | texto, inteiro ou float viram exato — aceita vírgula |
Dec.de_centavos(1999) | 19.99 — a ponte com quem guarda dinheiro em inteiro |
Dec.zero() | o zero exato |
Dec.texto(d, casas) | como texto, arredondando se casas vier |
Dec.float(d) / Dec.inteiro(d) | de volta — a exatidão acaba aqui |
Dec.centavos(d) | 19.99 vira 1999 |
Dec.arredondar(d, casas, modo) | meio-para-cima por padrão |
Dec.soma(xs) / Dec.media(xs) | sobre um cluster, exato |
Dec.repartir(d, partes, casas) | divide sem perder centavo |
Dec.abs(d) / Dec.sinal(d) | valor absoluto e −1/0/1 |
Dec.casas(d) / Dec.e_decimal(x) / Dec.modos() | perguntar |
Quando não usar#
Para física, estatística, geometria e gráficos, use Float. Decimal é mais lento, e a exatidão decimal não é a garantia que esses domínios precisam — ali o que importa é precisão relativa, e o ponto flutuante binário é a ferramenta certa.
Para dinheiro guardado em banco de dados, considere também o pacote `moeda`, que trabalha em centavos inteiros e traz a formatação em BRL.