Bom pessoal ,ontem eu enviei um tópico relatando sobre um erro que eu estava tendo ao rodar um simples programa que somava dois números armazenados em váriáveis double, ninguém opinou ou soube responder , então eu dei uma olhada num programa já feito, um exemplo de calculadora…
Ai, verifiquei o mesmo erro: façam o teste!!
Façam um programa para somar 0.2+0.1, armazenando estes dois números em variáveis double!!!
Respostas e artigos a parte, acho isso uma falha de altissimo grau do Java… sei que com outros tipos de dados isso não contece, mas isso deveria ser “corrigido”… e o strictfp não garante esses erros?
Bom, pra mim isso é uma falha, aqui na faculdade eu programo em FORTRAN, que tem uma excelente parte numérica, assim vão poderia substituir ele pelo Java, o que é uma pena , pois eu gostaria muito de fazer programas de cálculo numérico no Java!!!
Estude um tiquinho sobre computadores digitais e entenderá claramente que este problema não é de linguagem nenhuma. É apenas fruto da da representação digital de um número de ponto flutuante que ocorre desde o primeiro computador em uso no mundo.
Ë problema? É óbvio que não. Se não houvesse modos de contornar isto, engenheiros como eu não usariam computador para nada.
Não é coisa para ser corrigida.
Eles mostram essa quantidade absurda de casas depois da vírgula para você deixar de ser preguiçoso e formatar adequadamente os números para visualização.
O que acho que deveria ser posto na linguagem é um tipo de dados primitivo “decimal”, que seguisse o padrão IEEE_754r. Infelizmente o tal padrão ainda não está pronto.
Aí poderíamos ter:
decimal d = 1.0;
decimal e = 3.0;
decimal f = d - e * (d / e); // deveria dar algo como 0.0000000000001, não um valor maluco binário.
[quote=ndiegow]Bom, pra mim isso é uma falha, aqui na faculdade eu programo em FORTRAN, que tem uma excelente parte numérica, assim vão poderia substituir ele pelo Java, o que é uma pena , pois eu gostaria muito de fazer programas de cálculo numérico no Java!!!
Alguém sabe se dá pra contornar esse problema!![/quote]
Programo em Fortran desde 1969 e isto SEMPRE aconteceu.
Ninguém com um mínimo de bom senso compara números de ponto flutuante usando computadores digitais sem verificar se os números são iguais a menos de um número muito pequeno.
Exemplo em Fortran:
eps = 1.0D-07
if (abs(a - b) < eps) // Os números são iguais
[quote=ndiegow]Bom, pra mim isso é uma falha, aqui na faculdade eu programo em FORTRAN, que tem uma excelente parte numérica, assim vão poderia substituir ele pelo Java, o que é uma pena , pois eu gostaria muito de fazer programas de cálculo numérico no Java!!!
Alguém sabe se dá pra contornar esse problema!![/quote]
Acho que você nunca percebeu esse problema em Fortran, e você é que está reclamando à toa.
Experimente fazer algo como (faz mais de 20 anos que não mexo com Fortran, preciso instalar um F77 em algum lugar):
DOUBLE PRECISION A
DOUBLE PRECISION B
A = 0.2
B = 0.1
PRINT *, 10.0 * (A + B) - 3.0
Ele deve imprimir algo como esse lixo que você está reclamando.
De fato, Java não é muito adequado para cálculos numéricos, mas é por outras razões:
Os cálculos intermediários, em vez de usar a precisão de 80 bits (no caso de um processador Intel, que permite fazer esses cálculos intermediários com essa precisão absurda), costumam usar a precisão de 64 bits para que os resultados possam ser repetidos em qualquer implementação do Java em qualquer lugar;
Ele não tem um tipo numérico COMPLEX como no Fortran;
Arrays multidimensionais são muito lentos no Java, obrigando você a implementá-los como arrays unidimensionais mas com os índices calculados manualmente via multiplicações;
É complicado você chamar diretamente uma rotina escrita em outra linguagem, como Fortran.
Justamente por esse motivo e outros tanto de arredondamento,
que cálculos precisos ( principalmente monterários ) devem ser
feitos usando o java.math.BigDecimal.
import java.math.BigDecimal;
public class Calculos {
public static void main(String[] args) {
double d = 1.0;
double e = 3.0;
double f = (d - e) * (d / e);
System.out.println( f ); // -0.6666666666666666
BigDecimal dd = new BigDecimal( d );
BigDecimal ee = new BigDecimal( e );
BigDecimal de = dd.divide( ee, 50, BigDecimal.ROUND_HALF_EVEN );
BigDecimal ed = dd.subtract( ee );
BigDecimal valor = ed.multiply( de );
System.out.println( valor ); // -0.66666666666666666666666666666666666666666666666666
}
}
O strictfp serve para uma outra coisa: ele serve para garantir a repetibilidade de contas feitas com ponto-flutuante entre plataformas.
Por exemplo, se você não especificar strictfp e você estiver usando uma JVM da BEA (a Sun sempre trabalha como se tudo fosse strictfp, o que a torna lenta para cálculo numérico), então os cálculos intermediários, em plataforma Intel x86 (e acho que em x64, mas não tenho aqui uma máquina para comprovar isso), podem ser feitos em 80 bits em vez de sê-los feitos em 64 bits.
Pois é, gente da velha guarda e gente nova mexe com essa máquina de fazer doidos.
A velha guarda já apanhou bastante com esses problemas de ponto flutuante…
Certo, o Java, assim como C, C++ (no modo padrão) e 99,999% das linguagens, não usa o coprocessador aritmético e seu registrador de 80 bits. Mas mesmo que usasse, o problema da dificuldade de representação digital continuaria existindo, só que mais adiante.
Os cálculos monetários não precisam de números de ponto flutuante porque são cálculos simples. Eles precisam de exatidão na segunda casa que pode ser conseguida representando números como Strings, como BigDecimal ou como faz o COBOL e outras linguagens. As classes BigDecimal não servem para cálculos matemáticos. Experimente resolver umas míseras 1000 equações representando os elementos da matriz com BigDecimal.
O strictfp serve para uma outra coisa: ele serve para garantir a repetibilidade de contas feitas com ponto-flutuante entre plataformas.
Por exemplo, se você não especificar strictfp e você estiver usando uma JVM da BEA (a Sun sempre trabalha como se tudo fosse strictfp, o que a torna lenta para cálculo numérico), então os cálculos intermediários, em plataforma Intel x86 (e acho que em x64, mas não tenho aqui uma máquina para comprovar isso), podem ser feitos em 80 bits em vez de sê-los feitos em 64 bits.
[/quote]
Hummm… eu sabia que isso serviria pra alguma coisa!!! :twisted: :twisted:
Uma vez eu estava pegando um erro de um programa que “abendava”… era um objeto de negocio que utilizava JCA pra chamar o CICS… nele, havia um retorno que deveria ser dividido pelo numero de registros retornados, tipo para uma paginação. Mas o retorno era zero e o programador fez um Math.round(X/Y), onde Y era igual a zero e retornava um valor totalmente maluco! um numero com 3 casas inteiras e 7 decimais!!! Loucura!
O apelido dele ficou Chuck Noris, porque ele conseguiu dividir por zero! auhauhauh
Ora, mas a divisão por zero é um resultado definido em Java, e seu valor é +Infinity, -Infinity ou NaN (depende do valor do dividendo). Além disso, em Java existe o zero negativo. Isso tudo para ser compatível com IEEE 754.
Exemplo (este código funciona até com o J++, o antigo Java 1.1 da Microsoft);
class DivisaoPorZero {
public static void main(String[] args) {
double um = 1.0;
double menosUm = -1.0;
double zero = 0.0;
double menosZero = -0.0;
System.out.println (um / zero); // +Infinity
System.out.println (menosUm / zero); // -Infinity
System.out.println (menosUm / menosZero); // +Infinity - note que os sinais se cancelam!
System.out.println (zero / zero); // NaN
System.out.println (zero / menosZero); // NaN
}
}
Testei o programa abaixo em MS VC++ 14 (Visual Studio 2005) e em g++ 3.3.6 (gcc). Ele faz as mesmas contas e dá o mesmo resultado daquele programa em Java que mostrei antes.
#include <iostream>
using namespace std;
int main (int argc, char*argv[]) {
double um = 1.0;
double menosUm = -1.0;
double zero = 0.0;
double menosZero = -0.0;
cout << (um / zero) << endl; // imprime 1.#INF(MSVC++) ou inf(g++)
cout << (menosUm / zero) << endl; // imprime -1.#INF ou -inf
cout << (menosUm / menosZero) << endl; // imprime 1.#INF ou inf
cout << (zero / zero) << endl; // imprime -1.#IND ou nan
cout << (zero / menosZero) << endl; // imprime -1.#IND ou nan
}
Bom, muito obrigado pelos comentários pessoal, é muito bom ouvir a opinião de pessoas que entendem do assunto…
Estou iniciando o aprendizado de Java e concerteza preciso estudar mais, mas como não sou da área de engenharia ou ciências da computação eu não sabia de teoria tão afundo…