A precisão começa na entrada
Se o contrato entrega o texto "0.1", lê esse texto diretamente como BigDecimal quando o domínio exige o decimal exato. Convertê-lo primeiro para double e só depois construir BigDecimal conserva a aproximação binária já introduzida. Aumentar a precisão depois da conversão não recupera informação perdida.
Define também o formato admitido. Um ponto decimal num contrato técnico não implica que uma vírgula local ou espaços sejam aceites automaticamente. Valida os limites de magnitude e escala apropriados à aplicação, em vez de assumir que precisão arbitrária significa custo ilimitado aceitável. Um valor numericamente válido ainda pode estar fora do domínio.
BigDecimal é imutável. Uma chamada add ou setScale devolve um resultado; é preciso usá-lo. Se amount.add(fee) for ignorado, amount conserva o valor anterior. Na revisão de um cálculo, acompanha tanto o valor como a variável que recebe o resultado. Isso identifica erros de fluxo que não são erros de arredondamento.
Valor numérico e representação
Os textos "2.0" e "2.00" descrevem o mesmo valor numérico com escalas diferentes. compareTo responde à comparação numérica; equals também considera a representação. Antes de comparar resultados, decide qual dessas perguntas corresponde ao requisito. Um teste de valor não deve falhar apenas por uma escala irrelevante, mas um contrato que exige duas casas deve validar essa escala.
Essa escolha afeta chaves e conjuntos. Se o domínio considera 2.0 e 2.00 o mesmo identificador numérico, não introduzas casualmente os dois como chaves sem uma política coerente. Define a igualdade pretendida e a representação usada no limite de entrada. Não alteres a escala de todos os dados apenas para esconder um teste mal definido.
Precisão e escala também são conceitos diferentes: limitar algarismos significativos não equivale a exigir um número fixo de casas decimais. Uma especificação útil diz o que medir, quando arredondar e como comparar o resultado. Regista essas decisões junto do cálculo para evitar interpretações diferentes entre produção, exportação e avaliação.
Arredondar é uma decisão do domínio
Uma divisão exata de um por três não termina numa expansão decimal finita. A sobrecarga exata de divide lança ArithmeticException; a aplicação tem de fornecer a política que admite aproximação. A API não deve escolher silenciosamente quantas casas o negócio aceita.
Num empate, HALF_EVEN escolhe o vizinho cujo último algarismo conservado é par. A duas casas, 1.245 passa a 1.24 e 1.255 passa a 1.26. HALF_UP resolve o empate afastando de zero; não é sinónimo de aumentar sempre o número algébrico. UNNECESSARY serve para exigir que não se perca informação e pode falhar quando a redução de escala exigiria arredondar.
A fase do cálculo importa tanto como o modo. Três contribuições de 0.045 somam exatamente 0.135. Arredondar cada uma antes de somar pode dar 0.15; arredondar apenas o total com HALF_UP dá 0.14. O caso define a regra que deve ser seguida. Não extrapoles este exemplo para uma regra fiscal, monetária ou contratual universal.
Escolher o significado do tempo
Uma data de nascimento e o timestamp de uma mensagem não pedem a mesma representação. LocalDate conserva ano, mês e dia, sem hora ou zona. Instant identifica um ponto temporal. LocalDateTime descreve campos locais; para chegar a um instante é necessário conhecer o offset aplicável. ZonedDateTime associa esses campos a regras de zona e a um offset resolvido.
Se a mensagem já inclui Z, mantém essa informação ao converter para Instant. Transformar o timestamp em campos locais e descartar o offset perde uma parte do contrato. Um ecrã pode apresentar o mesmo instante em várias zonas sem criar vários eventos. Pelo contrário, duas mensagens com a mesma hora local podem corresponder a instantes diferentes.
Os tipos temporais usados aqui são imutáveis. day.plusDays(2) não altera day. Quando a lógica parece ter ignorado uma atualização de data, verifica primeiro se guardou o resultado. Isso evita atribuir ao fuso ou ao calendário um problema de reatribuição que é inteiramente local ao código.
Intervalos inexistentes e horas repetidas
As regras de uma zona podem indicar zero, um ou dois offsets válidos para uma data e hora local. Num avanço de 02:00 para 03:00, 02:30 não ocorreu nessa linha local. A resolução predefinida de ZonedDateTime.of desloca essa hora pela duração do intervalo, para 03:30 no exemplo. Se o domínio exige rejeitar horas inexistentes, tem de tornar essa política explícita.
Quando uma hora se repete, escolher automaticamente um dos offsets produz um valor utilizável, mas não prova qual ocorrência originou uma mensagem. Se 02:30 admite +02:00 e +01:00, os candidatos são 00:30Z e 01:30Z. Sem offset, sequência ou outra evidência, a ingestão não pode recuperar com certeza o instante original.
Somar um dia local também difere de somar 24 horas decorridas. Num avanço de uma hora entre dois meios-dias, plusDays(1) preserva o meio-dia após 23 horas; plusHours(24) chega às 13:00. Define se o requisito é uma recorrência de calendário ou uma duração. Os exemplos fornecem as transições necessárias e não dependem de memorizar datas de mudança de hora.
Fronteiras e uma origem explícita do tempo
Uma janela que inclui start e exclui end pode ser escrita como !t.isBefore(start) && t.isBefore(end). A janela seguinte pode começar exatamente em end sem contar o evento duas vezes. Testa conceptualmente o início, o fim, um instante interior e os dois exteriores. Não substituas a condição por duas comparações estritas se o início tem de ser incluído.
O mesmo cuidado aplica-se a expiração: se o contrato considera expirado no instante limite, agora igual ao limite tem de produzir expirado. Esse é um requisito de domínio que deve aparecer na condição e nos exemplos. Evita obter agora várias vezes numa única decisão se o requisito pretende um ponto de referência único.
Uma dependência Clock permite fornecer o relógio real em produção e um instante fixo numa avaliação local da regra. Mudar a zona da JVM não fixa o tempo. Esperar alguns milissegundos também não garante que um teste observe o instante desejado. Tornar a origem do tempo explícita reduz dependências ocultas sem alterar o significado da regra.
Revisão guiada: uma janela de aceitação
O excerto recebe start e end como Instant e uma dependência Clock. O contrato exige referências não nulas e start anterior a end. A validação do intervalo impede que uma configuração vazia ou invertida seja tratada como uma janela normal. A função lê o relógio uma vez e avalia esse instante contra os dois limites.
Para start=12:00Z e end=13:00Z, um Clock.fixed em 12:00Z produz true; em 13:00Z produz false. Entre esses limites a decisão é true, e antes do início ou depois do fim é false. Não há comparação de textos formatados nem dependência da zona de apresentação.
A função apenas decide. Ela não garante que uma ação externa termine antes de end ou que outro processo use a mesma configuração. Se a aceitação tiver efeitos, é necessário definir quando a decisão se aplica e como ligar a prova temporal ao resultado. Na avaliação, interpreta o excerto e escolhe a condição correta, sem executar Java nem ajustar relógios.
import java.time.Clock;
import java.time.Instant;
final class AcceptanceWindow {
// Non-null arguments; an inclusive start and exclusive end.
static boolean accepts(Instant start, Instant end, Clock clock) {
if (!start.isBefore(end)) {
throw new IllegalArgumentException("Invalid interval");
}
Instant now = Instant.now(clock);
return !now.isBefore(start) && now.isBefore(end);
}
}
Janela [12:00Z, 13:00Z): aceita 12:00Z e rejeita 13:00Z. O relógio é lido uma vez; a função não executa a ação aceite.
Armadilhas comuns
Construir decimais a partir de uma aproximação involuntária; confundir escala com valor; ignorar resultados imutáveis; inventar offsets; contar o fim em duas janelas.
Tópicos relacionados: Tipos, conversões e validação · Coleções, generics e chaves · Streams, lambdas e ausência · Concorrência, estado e cancelamento
Precisão e tempo precisam de contratos explícitos: representação, arredondamento, zona, origem do relógio e limites de aceitação.
Referência: Java 25 API: BigDecimal · Java SE 25; no preview features