Bug no jdk6se update 3

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!!!

ex:

double a = 0.2 double b= 0.1

O resultado será: 3,0000000004

O que é inconsistente matematicamente…

E ai pessoal, o que acham!!!

Isso não é um bug no Java; é um problema inerente ao fato que ele faz contas com binário, em vez de fazer contas em decimal.

Isso não aparece no VB ou no C++ (e talvez no Delphi) porque eles mostram menos casas depois da vírgula (cerca de 6).

Se seu inglês dá pro gasto, leia este artigo.

 http://docs.sun.com/source/806-3568/ncg_goldberg.html

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!!!

Alguém sabe se dá pra contornar esse problema!!

Olá

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.

[]s
Luca

Obs: “No python isso também não acontece”

Imagina, vc faz uma calculadora em java e soma 0.2+0.1 e ela te dá 0.30000004 ou também 0.29999999999, faça me o favor???

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.

Olá

[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

[]s
Luca

Somente corrigindo pra nao dar a impressao do erro gigantesco,

mas o resultádo será: 0.30000000000000004

Olá

Falso, acontece em Assembler, Erlang, Io e em TODAS as demais linguagens.

O que pode enganar o incauto é a linguagem que faz arredondamentos automáticos mas estas NÃO servem para cálculos artitméticos.

[]s
Luca

[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
	}
}

Bom trabalho.

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…

Olá

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.

[]s
Luca

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
    }
}

Por isso que Java é considerada a linguagem daquele-que-não-podemos-falar-o-nome!!!

Ora, mas se Java é a linguagem do sr. Voldemort, C++ é a linguagem de quem? De Gellert Grindenwald?

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…

Obrigado a todos que postaram…