O melhor livro técnico que comprei nos últimos tempos foi o Java Puzzlers, que inspira esse post. E digo isso nem tanto pelos corner cases obscuros do Java que você fica conhecendo ao ler o livro (que são divertidíssimos e assustadores ao mesmo tempo) e sim pelos princípios de design que se pode extrair dele. Fortemente recomendado.
Um dos puzzlers do livro mostra um problema com float
e double
que eu despercebi por todos esses anos, já que não uso esses tipos pra nada. Código como:
float f1 = 16777216f; float f2 = f1 + 1; System.out.println(f1 == f2);
imprime true
. Sim, true
, você não leu errado. O erro na representação da parte fracionária, na verdade, não está limitado a ela; ele atinge a parte inteira e vai ficando mais grave à medida que o número cresce. Com números maiores, você pode somar 50, 100 ou mais e simplesmente ver o valor permanecer o mesmo, porque o tipo de dado não permite representar esses valores.
Eu particularmente achava que o MAX_VALUE
e o MIN_VALUE
ocorriam justamente antes do erro se manifestar na parte inteira, mas pude comprovar que não. Na verdade, o padrão IEEE 754 dita que isso seja assim, o que significa que toda linguagem que suporta tipos flutuantes segundo esse padrão vai apresentar o mesmo comportamento – que em tese não é um bug, mas uma limitação esperada do design.
Obviamente existem situações específicas em que estes tipos de dados são apropriados e/ou podem-se utilizar técnicas para minimizar ou compensar o erro de representação. Para a maioria das aplicações do mundo, o uso de BigDecimal
– ou semelhantes, em outras linguages – é basicamente obrigatório.
Dada a aplicabilidade limitada desses tipos de dados, acho que talvez as linguagens que suportem float e double deveriam requerer que os tipos fossem explicitamente importados, para garantir que o desenvolvedor pelo menos tivesse que fazer esforço pra obter a arma antes de atirar no próprio pé…