Submarino.com.br




Trabalhando com números em Java

Introdução

Trabalhar com números em Java pode ser uma tarefa complicada e frustrante para quem está começando.

Tipos numéricos

Java, tal como a maioria das linguagens modernas, tem vários tipos numéricos. Cada um caracterizado pelo número de bits que ocupa. A diferença entre java e algumas outras linguagens- especialmente as chamadas linguagens de script- é o suporte a tipos numéricos como tipos primitivos. Neste contexto, tipo primitivo significa que não é um objeto. O uso de tipos primitivos aumenta a eficiência da JVM e permite que ela funcione até em ambientes com pouca memória e poder de processamento ( como nas plataformas JME e JavaCard). Por isto se diz que Java não é uma linguagem puramente orientada a objetos já que é possível programar sem eles utilizando apenas tipos primitivos. É claro que isso é mais difícil e é como se faziam programas à 40 anos atrás ou mais.

Os tipos primitivos numéricos em Java são:

  • byte - ocupa 8 bits (1 byte ) : Representa apenas números inteiros. O menor número possível representar com um byte é -128 e o maior é 127. Repare que 8 bits podem produzir 256 números diferentes que exatamente quantos números existem entre -128 e 127.
  • short - ocupa 16 bits (2 bytes). Representa apenas números inteiros. O menor número possível representar com um short é -32.768 e o maior é 32.767
  • char - ocupa 16 bits (2 bytes) e representa apenas números inteiros, contudo ao contrário do short só aceita números positivos. O menor numero possível representar é 0 e o maior é 65.536. O char é um tipo numérico especial e veremos porquê, contudo ele é um tipo numérico. Isso tem, na prática, grandes implicações na programação Java.
  • int - ocupa 32 bits (4 bytes). Representa apenas números inteiros. O menor número possível representar é -2.147.483.648 e o maior é 2.147.483.647
  • long - ocupa 64 bits (8 bytes). Representa apenas números inteiros. O menor número possível representar é -9.223.372.036.854.775.808 e o maior 9.223.372.036.854.775.807
  • float - ocupa 32 bits (4 bytes). Pode representar números inteiros ou não. O menor valor possível representar é 1.40239846x10-46 e o maior é 3.40282347x1038. Em java o float é garantidamente implementado conforme a norma IEEE 754-1985 o que significa que o calculo pode ser repetido em qualquer hardware ou sistema operacional.
  • double - ocupa 64 bits ( 8 bytes). Pode representar números inteiros ou não. O menor valor possível representar é 4.94065645841246544 x10-324 e o maior é 1.7976931348623157×10308. Em java o double é garantidamente implementado conforme a norma IEEE 754-1985 o que significa que o calculo pode ser repetido em qualquer hardware ou sistema operacional.

Representações de inteiros

Em java os literais numéricos ( os números que você escreve diretamente no código) podem ser escritos em quatro bases diferentes: decimal, octal, hexadecimal e binária.

Decimal é a representação padrão e aquela a que estamos habituados. Simplesmente escrevemos 42, por exemplo, e pronto. Para especificar a representação em hexadecimal (base 16) começamos o numero com um zero e um 'x' e fica 0x42 ( que não é a mesma coisa que 42) Para a representação octal (base 8) começamos o número com um zero apenas e fica 042 ( que é diferente de 42 e 0x42). Para a representação binária (base 2) começamos o número com um zero um b e fica 0b011101. A representação binária só aceita 0 e 1 e é uma representação possível apenas no Java 7 e posteriores.

Cuidado! Em Java um zero à esquerda significa que está sendo usada uma base diferente da decimal. Felizmente raramente você irá necessitar usar esta representação.

Operações

As quatro operações básicas : soma, subtração, multiplicação e divisão estão disponíveis diretamente através dos operadores +, ?, * e / respectivamente. Além destas temos ainda a operação modulo através do operador % que apenas se aplica a números inteiros.

Embora todos os tipos possam ser utilizados em cálculos existem regras especiais que são necessárias conhecer para não ter surpresas.

Operações aritméticas sobre inteiros

A JVM sempre irá converter o número para um int para fazer um cálculo, exceto no caso do long em que o long será utilizado. Experimente fazer este calculo em Java:

byte a = 1;
byte b = 2;
byte c = a + b;
				
Código 1:

Ao executar este código terá um surpresa. A JVM irá informá-lo que não é possível atribuir um int a um byte. Porquê ? Como foi dito, a e b serão convertidos para int para poderem ser somados. O resultado será portanto um int, e um int ( 2 bytes) não cabe em um byte.

Se tivermos a certeza que cabe, então podemos forçar a operação com um cast. Assim:

byte c = (byte) (a + b);
				
Código 2:

Contudo, imagine que a é 70 e b é 80. O resultado será 150 que é maior que o maior número que um byte pode representar (127) . Ao fazer o cast o resultado é inesperado. Vejamos com mais atenção como se processa. O numero 70 em binário se representa como 01000110 e 80 como 01010000. Para somar o java transforma para int com 32 bits.70 fica então 0000000001000110 e 80 fica 0000000001010000. A soma é então efetuada e o resultado é 150, em binário 0000000010010110. Ok. Agora como fazemos isso caber em um byte de volta ? Sim, cortamos os zeros à esquerda e ficamos como o valor 10010110 em byte. Hummm? mas que valor está aqui representado ?

Como vimos, à exceção do char todos os tipos numéricos em java têm sinal, ou seja, podem representar números negativos ou positivos. Como o java controla o sinal ? Através do ultimo bit (o mais à esquerda). Se 0 é positivo, se 1 é negativo. Repare que 70 e 80 têm um 0 no bit mais à esquerda (porque são positivos). Contudo, o resultado da nossa soma começa com 1. Isso significa que é um numero negativo. É o numero -22. Humm? com certeza não é 150. Não é o número que esperávamos.

A lição a aprender daqui é que para fazer cálculos temos que escolher um tipo numérico apropriado. Como a JVM sempre converte para int para fazer a conta, usar int é o mais natural e comum. Exceto é claro quando é necessário usar o long. Na prática não são muitas as vezes que precisamos de um long.

Uma outra armadilha é a divisão de inteiros, chamada obviamente : divisão inteira. Quanto é 5/2 ?

Se respondeu 2,5 está enganado. Na divisão inteira nunca o resultado será um número não inteiro. Na realidade o resultado, 5/2 é 2. A divisão inteira representa o quociente entre dois inteiros que também é um inteiro. Para conhecermos o resto da divisão usamos o operador modulo (%). Assim, 5%2 é 1.

O que acontece se dividirmos por zero ? Uma ArimethicException é o que acontece. Em matemática de inteiros não é possível dividir por zero. Cuidado com isto.

Operações aritméticas sobre decimais

Para resolver as "limitações" com a matemática de divisão de inteiros os primitivos float e double podem ser utilizados para obter cálculos com vírgula ( ponto flutuante). Assim 5d/2d = 2,5 ( o d é para indicar "double").

Se um das parcelas for um inteiro ( byte, short, int , etc.. ) e outro um double , o inteiro será promovido a double com o mesmo valor antes da conta ser feita. Lembre-se que float e double podem representar os mesmos valores int e long respectivamente. Assim teremos 5 /2d = 2,5 ( onde 5 é inteiro)

E o que acontece se dividirmos um double ou float por zero ? Nenhuma exceção será lançada. Um valor será obtido. O valor especial chamado Infinity (infinito) é o resultado. Este valor é estranho, mas matematicamente aceitável como resultado. Operações com Infinity resulta em NaN ( Not A Number = não é um número). Este ainda mais estranho valor é aceite na especificação IEEE 754-1985. Este valor significa que fizemos uma operação que leva a perder a conexão lógica das operações.

A lição a tirar daqui é que utilizando double ou float um cálculo nunca lançará exceções. Contudo, isso não significa que o resultado esteja certo ou sequer que seja um número.Um problema grave com o double é que ele não é bom para representar potências negativas de dez, ou seja, números como 0,1 ou 0,01 ... Para provar isso execute estes dois códigos:

double a = 0.1;
double r = a * 10;
System.out.println ( r ); // imprime 10
				
Código 3:

e por outro lado

double a = 0.1;
double c = 0;
for (int i=0 ; i<10 ; i++){
c = c + a;
}
System.out.println ( r ); // imprime 0.9999999999999999
				
Código 4:

Matematicamente , somar 0.1 dez vezes é a mesma coisa que multiplicar 0.1 por dez. Contudo, na prática, com double, não é. Imagine, agora, que são centavos de dólar que estamos somando. Estamos perdendo dinheiro na soma. Nada bom. Para resolver este problemas precisamos apelar para a criação de objetos que seja inteligentes e consigam driblar este problema.