Precisão Numérica Excel para Java

Bom, estou fazendo uma app que subtitui uma planilha excel para cálculos.Basicamente eu pego dois números num dado período de tempo para calcular dois indicadores e depois pego cada indicador e multiplico por um peso(por exemplo 0.2 e 0.8) para obter o resultado de uma variação em % após subtrair um pelo outro.São 3 métodos:

[code]
public double calcularIndicador(double valorInicial,double valorFinal){
BigDecimal valor1=new BigDecimal(valorInicial);
BigDecimal valor2=new BigDecimal(valorFinal);
BigDecimal resultado=valor1.divide(valor2,6,BigDecimal.ROUND_UP);
return resultado.doubleValue();
}

public double calcularIndicadorComPeso(double valorDeCalcularIndicador,double peso){
BigDecimal valor1=new BigDecimal(valorDeCalcularIndicador);
BigDecimal valor2=new BigDecimal(peso);
BigDecimal resultado=valor1.multiply(valor2);
return resultado.setScale(6,RoundingMode.HALF_UP).doubleValue();
}

public double calcularResultado(double parte1,double parte2){
BigDecimal valor1=new BigDecimal(parte1);
BigDecimal valor2=new BigDecimal(parte2);
BigDecimal resultado=valor1.add(valor2);
double r=resultado.subtract(new BigDecimal(“1”)).setScale(6,RoundingMode.HALF_UP).doubleValue();
return r;
}[/code]
Exemplo de entradas:
double resultado_do_primeiro_indicador=sr.calcularIndicador(360.6400,341.0100);
double resultado_do_segundo_indicador=sr.calcularIndicador(396.9720,345.7240);
Aí eu tenho a sequência:

double parte1indicador=sr.calcularIndicadorComPeso(1.0576,0.2); double parte2indicador=sr.calcularIndicadorComPeso(1.1483,0.8);
E o final:
double resultado=sr.calcularResultado(parte1indicador,parte2indicador);
O que me dá:0.13016
Mas o correto deveria ser:
0.130106 que depois dará convertido: 13,0106% de variação.
Eu gostaria de saber se alguém sabe o “RANGE” ou o que governa a precisão do Excel para eu fazer o mesmo no java.

vc não pode usar double…

pq Double.MAX_VALUE = 1.7976931348623157E308

altere a assinatura public double

para

public BigDecimal

Focão, não preciso de precisão numérica, pois double vai até algo entre 15 ou 17 casas decimais.O que eu preciso é de EXATIDÂO.
Eu mudei os métodos para receberem e entregarem Strings, mas meu resultado é: 0.130100 e eu deveria receber isso:
0.130106, entendeu?

Alguma coisa está errada.

Eu sei que o Excel trabalha internamente com double ou “long double” (80 bits), até porque ele usa as instruções do processador para acelerar o cálculo.
(Ele só arredonda para visualização, não para cálculo. Isso você pode confirmar fazendo uma planilha que contenha 2 valores muito próximos, que apareçam iguais se você formatar com poucas casas depois da vírgula, e criar uma célula que seja a diferença desses dois valores, mas formatado com muitas casas depois da vírgula. Você vai ver que a diferença é calculada de acordo com o valor real e não o mostrado pela formatação.)

class TestePrecisao {

    public double calcularIndicador(double valorInicial,double valorFinal){       
        return valorInicial / valorFinal;
    }  

    public double calcularIndicadorComPeso(double valorDeCalcularIndicador,double peso){         
        return valorDeCalcularIndicador * peso;
    }  

    public double calcularResultado(double parte1,double parte2){        
        return parte1 + parte2 - 1;
    }  

    public static void main(String[] args) {
        TestePrecisao sr = new TestePrecisao();
        double parte1indicador=sr.calcularIndicadorComPeso(1.0576,0.2);  
        double parte2indicador=sr.calcularIndicadorComPeso(1.1483,0.8);
        double resultado=sr.calcularResultado(parte1indicador,parte2indicador); 
        System.out.printf ("%.6f", resultado);
    }
}

Mas executando o programa acima (que é basicamente o seu, sem usar essa frescura de BigDecimal) deu o resultado:

0,130160

Alguém está comendo bola nessa história.

E de qualquer maneira, mesmo que o Excel use “long double” (80 bits) internamente, as operações que você fez não estão em um intervalo “instável” - ou seja, onde qualquer modificação infinitesimal nos valores dos parâmetros se reflita com valores muito grandes no resultado. As contas que você fez, na verdade, dão aproximadamente mesmo resultado mesmo com “float” (32 bits), que tem uma precisão muito ruim para fazer contas.

Thingol, o programa que eu tô fazendo é para substituir um feito em planilha excel, lembrando que as entradas originais:

double resultado_do_primeiro_indicador=sr.calcularIndicador(360.6400,341.0100);
double resultado_do_segundo_indicador=sr.calcularIndicador(396.9720,345.7240);

São valores oriundos de uma planilha Excel(claro, lá estão com vírgulas ao invés de double). E o resultado final: 13,0106% de variação, vem da mesma.
O que eu tõ fazendo é tentar “bater” com ela, entendeu? Olha ela aqui(ver FM1):
http://img217.imageshack.us/my.php?image=imagemexcelsb7.jpg

Eu fiz um pequeno “tira-teima” aqui, com o seguinte trecho:

double parte1indicador = sr.calcularIndicadorComPeso(1.0576, 0.2); double parte2indicador = sr.calcularIndicadorComPeso(1.1483, 0.8); double resultado = sr.calcularResultado(parte1indicador, parte2indicador);
Seguindo os métodos de cálculo que você colocou, eu fiz na calculadora:

[code]parte1indicador = calcularIndicadorComPeso(1.0576, 0.2)
parte1indicador = 1,0576 x 0,2
parte1indicador = 0,21152

parte2indicador = calcularIndicadorComPeso(1.1483, 0.8)
parte2indicador = 1,1483, 0,8
parte2indicador = 0,91864

resultado = calcularResultado(parte1indicador, parte2indicador)
resultado = 0,21152 + 0,91864 - 1
resultado = 0,13016[/code]

O que bateu com o resultado que vc obteve no programa em Java! E veja que não fiz nenhum arredondamento para não ter perigo de perda de precisão… Será que o problema não pode estar na planilha excel e não no seu programa?

cara tem q ser tudo BigDecimal

Já aconteceu comigo…

faz o teste aí…
o problema é precisão numérica do double mesmo

lembre-se precisão numérica = exatidão

se der dizima vc perde no excel

BigDecimal parte1indicador = sr.calcularIndicadorComPeso(1.0576, 0.2);
BigDecimal parte2indicador = sr.calcularIndicadorComPeso(1.1483, 0.8);
BigDecimal resultado = sr.calcularResultado(parte1indicador, parte2indicador);

se liga como

BigDecimal   parte1indicador=calcularIndicadorComPeso(new BigDecimal(1.0576),new BigDecimal(0.2));     
BigDecimal   parte2indicador=calcularIndicadorComPeso(new BigDecimal(1.1483),new BigDecimal(0.8));   
BigDecimal   resultado=calcularResultado(parte1indicador,parte2indicador);   

      System.out.println("parte1indicador " +parte1indicador);
      System.out.println("parte2indicador " +parte2indicador);
      System.out.println("resultado " +resultado);
public static BigDecimal   calcularIndicador(BigDecimal valorInicial,BigDecimal valorFinal){         
        return valorInicial.divide(valorFinal);   
    }     
  
    public static BigDecimal   calcularIndicadorComPeso(BigDecimal valorDeCalcularIndicador,BigDecimal peso){           
        return valorDeCalcularIndicador.multiply(peso);   
    }     
  
    public static BigDecimal   calcularResultado(BigDecimal   parte1,BigDecimal   parte2){           
    	 return parte1.add(parte2).subtract(new BigDecimal(-1));   
    }     

olha o tamanho das crianças… hehehe

Precisão é o numero de digitos, exatidão é a proximidade do resultado verdadeiro.(Lembre-se que toda máquina esbarra uma hora numa limitação binária)
Focão, não use o double no construtorBigDecimal, dá resultados bizarros.Prefira passar Strings.Passando só Strings, eu obtenho a série:

Cálculo do indicador 1(MO):1.057565
Cálculo do indicador 2(PI):1.148234
Cálculo do indicador 1(MO) com peso:0.211513
Cálculo do indicador 2(PI) com peso:0.918587
Resultado em final:0.130100

Thingol, esse trabalho que eu tô fazendo é para um engenheiro.Como você é engenheiro, eu pergunto para fins de exatidão, eu uso double(pode ser o simples como vc fz mesmo, sem usar BD),ou um BigDecimal passando Strings ao construtor?O primeiro dá o resultado que vc já conhece 0,130160 e o segundo 0.130100.Alguém tem uma HP12C aí? :lol:

Ironynx agora q não entendi o que vc quer de verdade…

Posso ter me expressado mal igualdade em Precisão = Exatidão

Na verdae pelo contexto está implicito que quiz dizer que quanto maior a precisão maior a exatidão por isso sugeri o BigDecimal

Agora passar String pra Cálculo não concordo… prefiro os resultado bizarros… como o que te passei olha a precisão:

parte1indicador 0.2115200000000000308553183003823516324714888418409564037749203053474789104626552216359414160251617431640625
parte2indicador 0.91864000000000012972289908930179517954823355080921057472625166663571238956365050398744642734527587890625
resultado 2.1301600000000001605782173896841468120197223926501669785011719719831913000263057256233878433704376220703125

Agora em conceito…

Precisão é o grau de variação das medidas que vc faz. Ou seja, se vc tirar 10 medidas com cada um de dois equipamentos diferentes, o que
apresentar menos medidas diferentes é o que tera maior precisão. Exatidão é quanto a medida se aproxima da realidade, ou seja, a parte que é constante no erro. Por exemplo, se vc tem uma diferença de 2 milimetros em uma regua, com relação ao valor exato, vc tera um erro sistematico de 2 militros em todas as medidas. Esse valor fornece a exatidão da regua

Agora em Dic Michaelis

e.xa.ti.dão
(z), s. f. 1. Caráter ou qualidade de exato. 2. Rigor na determinação de medida, peso, valor etc. 3. Atenção minuciosa no cálculo. 4. Cumprimento rigoroso, observância à risca de ajuste, contrato etc. 5. Verdade na exposição dos fatos.
pre.ci.são
s. f. 1. Falta ou insuficiência de algo necessário ou útil. 2. Urgência, necessidade. 3. Rigor sóbrio de linguagem. 4. Caráter do que é nítido, exato. 5. Justeza, regularidade material.

Agora nossa praia Java Doc

A classe BigDecimal dá o seu usuário o controle completo sobre arredondamento comportamento, forçando o usuário a especificar explicitamente um comportamento arredondamento para operações capaz de desfazer precisão

Lembrando que SUA PERGUNTA ERA:

Eu gostaria de saber se alguém sabe o “RANGE” do Excel ?

então a resposta é :

30 casas decimais…

quero ver isso em double.

BOA SORTE ! na sua implementação.

Hum… como acho que o Excel não esteja fazendo contas erradas, o que acho é que você está passando para sua rotina Java dados que você copiou da planilha Excel com apenas as 6 casas de precisão.

De qualquer maneira, em análise numérica há duas formas de proceder:

  • Uma delas é calcular com o máximo de precisão possível (double ou long double) os cálculos intermediários e efetuar apenas na saída o arredondamento - isso é mais fácil, mas exige alguma análise para determinar se pequenos erros nos dados de entrada não causarão grandes erros na saída;

  • A outra, que é mais complicada, é usar “interval arithmetic”.
    Ou seja, se você tem um dado que é 1.23 +/- 0.01 e vai somar com outro que é 34.56 +/- 0.1, você intuitivamente sabe que o resultado é (1.23 + 34.56) +/- (0.01 + 0.01), que é 35.79 +/- 0.02.
    Se você tivesse o dado 35.79 +/- 0.01 e subtraísse de 34.56 +/- 0.01, o resultado é 1.23 +/- 0.02.
    A multiplicação é um pouco mais complicada. Por exemplo, (1.23 +/- 0.01) * (34.56 +/- 0.01) dá (1.23 * 34.56) +/- (1.23 + 34.56) * 0.01 + 0.01 * 0.01) = 42.5088 +/- 0.3580. O resultado é surpreendente, já que você pensaria que 1.23 * 34.56 daria algo com 2 casas depois da vírgula (mais ou menos), mas na verdade nem dá a precisão de 1 casas depois da vírgula.
    Para isso, use uma biblioteca que implementa “interval arithmetic”, como a http://interval.sourceforge.net/interval/java/ia_math/README.html

Ou seja, você vê que à medida que você vai acumulando dados, as suas inexatidões vão se acumulando também.

EDIT - Hum… fiz uma conta errado. Mas a conclusão é a mesma.

Fiz um teste aqui e cheguei no seu resultado…
O problema que eu achei é no metodo calcularIndicador

Vc estava passando com quatro casas decimais assim

double resultado_do_primeiro_indicador=sr.calcularIndicador(360.6400,341.0100);
double resultado_do_segundo_indicador=sr.calcularIndicador(396.9720,345.7240); 

No teste que eu fiz passei com duas casas decimais e no calculo do peso estou arrendondo para baixo.

Segue o exemplo


package teste;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Snippet {
	public double calcularIndicador(double valorInicial,double valorFinal){		
		BigDecimal valor1 = new BigDecimal(valorInicial);
		BigDecimal valor2 = new BigDecimal(valorFinal);				
		BigDecimal resultado=valor1.divide(valor2,6,BigDecimal.ROUND_UP);		
		return resultado.doubleValue(); 
	}

	public double calcularIndicadorComPeso(double valorDeCalcularIndicador,double peso){		
		BigDecimal valor1 = new BigDecimal(valorDeCalcularIndicador);
		BigDecimal valor2 = new BigDecimal(peso);				
		BigDecimal resultado=valor1.multiply(valor2);		
		return resultado.setScale(6,RoundingMode.DOWN).doubleValue(); 
	}

	public double calcularResultado(double parte1,double parte2){		
		BigDecimal valor1 = new BigDecimal(parte1);
		BigDecimal valor2 = new BigDecimal(parte2);				
		BigDecimal resultado=valor1.add(valor2);
		return resultado.subtract(new BigDecimal("1")).setScale(6,RoundingMode.HALF_UP).doubleValue();
	}


	public static void main(String[] args) {
		Snippet sr = new Snippet();;
		
		double resultado_do_primeiro_indicador = sr.calcularIndicador(360.64,341.01);
		double resultado_do_segundo_indicador = sr.calcularIndicador(396.97,345.72); 
		
		double parte1indicador = sr.calcularIndicadorComPeso(resultado_do_primeiro_indicador,0.2);
		double parte2indicador = sr.calcularIndicadorComPeso(resultado_do_segundo_indicador,0.8);
		double resultado = sr.calcularResultado(parte1indicador,parte2indicador);

		System.out.println(resultado);
	}
}

Resultado: 0.130106

Espero ter ajudado…

thingol, ótimos toques.O cara deve ter vacilado em alguma coisa.Os dados das entradas foram salvos todos com 4 casas na base.Eu uso 6 para poder obter a % de cada indicador com 4 casas(logico que depois eu multiplico por 100).
correainfo, valeu pelo teste.Mas não sei se posso considerar tão valido pois há numeros assim 341.9113, que jamais poderiam ser desprezados, e o estranho é que eles estão usando maior que 5, arredonda para cima.

Thingol, vc tava certo.A planilha que eu recebi, tinha os dados cortados.Olha os tipos de valores da planilha original:
8,54709767571% e -2,20100744472017% :shock:
Bom, conversei com o cara e como a base já foi toda gravada com 4 casas, vou usar o BigDecimal com Strings para evitar comportamnetos bizarros com o double.Logo, vai valer o 0.130100(ou 13,0100%).Um abraço á todos. (OBS.: seria bom se java tivesse uma classe que pudéssemos armazenar denominador/numerador com umas 20,30 casas.Se não me engano, acho que tem um projeto chamado JScience que faz algo assim usando uma implementação especial de BigInteger, acho eu.

[quote=Ironlynx]Thingol, vc tava certo.A planilha que eu recebi, tinha os dados cortados.Olha os tipos de valores da planilha original:
8,54709767571% e -2,20100744472017% :shock:
Bom, conversei com o cara e como a base já foi toda gravada com 4 casas, vou usar o BigDecimal com Strings para evitar comportamnetos bizarros com o double.Logo, vai valer o 0.130100(ou 13,0100%).Um abraço á todos. (OBS.: seria bom se java tivesse uma classe que pudéssemos armazenar denominador/numerador com umas 20,30 casas.Se não me engano, acho que tem um projeto chamado JScience que faz algo assim usando uma implementação especial de BigInteger, acho eu.
[/quote]

Hum? Mas aí é que vai dar resultado diferente do Excel. O Excel faz cálculos intermediários com precisão do double, mas se você usar BigDecimal com precisão de 6 casas nos cálculos intermediários, vai ter problemas.

[quote]
Hum? Mas aí é que vai dar resultado diferente do Excel. O Excel faz cálculos intermediários com precisão do double, mas se você usar BigDecimal com precisão de 6 casas nos cálculos intermediários, vai ter problemas. [/quote]
Vou usar 6 casas porque ele quer a porcentagem com 4.Sempre dá um resultado em complemetno ao anterior que é do tipo 1.057565, que é a razão real de crescimento do índice, ou seja,5,7565% de crescimento no período dado.