Pergunta

Atualmente tenho esse método:

static boolean checkDecimalPlaces(double d, int decimalPlaces){
    if (d==0) return true;

    double multiplier = Math.pow(10, decimalPlaces); 
    double check  =  d * multiplier;
    check = Math.round(check);      
    check = check/multiplier; 
    return (d==check);      
}

Mas este método falhar por checkDecmialPlaces(649632196443.4279, 4) provavelmente porque eu faço base 10 de matemática em um número base 2.

Assim como pode essa verificação ser feito corretamente?

Eu pensei de obter uma representação de string do valor duplo e depois verificar que, com uma expressão regular -, mas que me senti estranho

.

EDIT: Obrigado por todas as respostas. Há casos em que eu realmente obter um casal e para aqueles casos em que implementou o seguinte:

private static boolean checkDecimalPlaces(double d, int decimalPlaces) {
    if (d == 0) return true;

    final double epsilon = Math.pow(10.0, ((decimalPlaces + 1) * -1));

    double multiplier = Math.pow(10, decimalPlaces);
    double check = d * multiplier;
    long checkLong = (long) Math.abs(check);
    check = checkLong / multiplier;

    double e = Math.abs(d - check);
    return e < epsilon;
}

Eu mudei o round a um truncamento. Parece que o cálculo feito em round aumenta a imprecisão muito. Pelo menos no testcase falhando.
Como alguns de vocês apontou se eu poderia chegar à entrada 'real' string que eu deveria usar BigDecimal para verificar e então eu fiz:

BigDecimal decimal = new BigDecimal(value);
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces);
return checkDecimal.scale() == 0;

O valor double eu recebo vem da API Apache POI que lê arquivos do Excel. Eu fiz alguns testes e descobriu que, embora a API retorna double valores para células numéricas posso obter uma representação precisa quando eu formatar imediatamente que double com o DecimalFormat:

DecimalFormat decimalFormat = new DecimalFormat();
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE);
// don't use grouping for numeric-type cells
decimalFormat.setGroupingUsed(false);
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
value = decimalFormat.format(numericValue);

Isso também funciona para valores que não podem ser representados exatamente em formato binário.

Foi útil?

Solução

O teste falhar, porque você atingiu a precisão da representação de ponto flutuante binário, que é de aproximadamente 16 dígitos com IEEE754 dupla precisão. Multiplicando por 649632196443.4279 por 10000 irá truncar a representação binária, levando a erros ao arredondamento e dividindo depois, invalidando, assim, o resultado de sua função completamente.

Para mais detalhes veja http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

A melhor maneira seria para verificar se os lugares n+1 decimais estão abaixo de um determinado limiar. Se d - round(d) é inferior a epsilon (veja limite ), a representação decimal de d não tem casas decimais significativas. Da mesma forma se (d - round(d)) * 10^n é inferior a epsilon, d pode ter no máximo lugares significativos n.

Use Jon Skeet 's DoubleConverter para verificar os casos em que d não é suficiente preciso para manter as casas decimais que você está procurando.

Outras dicas

Se o seu objetivo é representar um número com exatamente n algarismos significativos à direita do decimal, BigDecimal é a classe para uso.

imutável, de precisão arbitrária assinado números decimais. Um BigDecimal consiste de um inteiro de precisão arbitrária valor fora de escala e um número inteiro de 32-bit escala. Se zero ou positivo, a escala é o número de dígitos à direita do ponto decimal. Se negativo, o valor sem escala do número é multiplicado por dez para o poder da negação da escala. O valor de o número representado pela BigDecimal é, portanto, (unscaledValue × 10-scale).

scale pode ser definido através de setScale (int)

Tal como acontece com todos os aritmética de ponto flutuante, você não deve verificar a igualdade, mas sim que o erro (epsilon) é suficientemente pequeno.

Se você substituir:

return (d==check);

com algo como

return (Math.abs(d-check) <= 0.0000001);

ele deve funcionar. Obviamente, o epsilon deve ser selecionado para ser pequeno o suficiente em comparação com o número de casas decimais que você está verificando para.

O tipo double é um número de ponto flutuante binário. Há sempre aparentes imprecisões em lidar com eles como se fossem números de ponto flutuante decimal. Eu não sei que você nunca vai ser capaz de escrever sua função para que ele funciona da maneira que quiser.

Você provavelmente vai ter que voltar para a fonte original do número (a entrada para a cadeia talvez) e manter a representação decimal se é importante para você.

Se você pode alternar para BigDecimal, então, como Ken G explica, é isso que você deve usar.

Se não, então você tem que lidar com uma série de questões, como mencionado em outras respostas. Para mim, você está lidando com um número binário (duplo) e fazer uma pergunta sobre uma representação decimal desse número; ou seja, você está perguntando sobre uma String. Eu acho que sua intuição está correta.

Eu não estou certo de que este é realmente factível em geral. Por exemplo, quantas casas decimais não 1.0e-13 tem? E se ela resultou de algum erro de arredondamento ao fazer aritmética e realmente é apenas 0 disfarçado? Se no, por outro lado você está perguntando se há alguma dígitos diferentes de zero nos primeiros n casas decimais que você pode fazer algo como:

   static boolean checkDecimalPlaces(double d, unsigned int decimalPlaces){
      // take advantage of truncation, may need to use BigInt here
      // depending on your range
      double d_abs = Math.abs(d);
      unsigned long d_i = d_abs; 
      unsigned long e = (d_abs - d_i) * Math.pow(10, decimalPlaces);
      return e > 0;
   }

Eu acho que isso é melhor Converter para string e interrogar o valor para o expoente

 public int calcBase10Exponet (Number increment)
 {
  //toSting of 0.0=0.0
  //toSting of 1.0=1.0
  //toSting of 10.0=10.0
  //toSting of 100.0=100.0
  //toSting of 1000.0=1000.0
  //toSting of 10000.0=10000.0
  //toSting of 100000.0=100000.0
  //toSting of 1000000.0=1000000.0
  //toSting of 1.0E7=1.0E7
  //toSting of 1.0E8=1.0E8
  //toSting of 1.0E9=1.0E9
  //toSting of 1.0E10=1.0E10
  //toSting of 1.0E11=1.0E11
  //toSting of 0.1=0.1
  //toSting of 0.01=0.01
  //toSting of 0.0010=0.0010  <== need to trim off this extra zero
  //toSting of 1.0E-4=1.0E-4
  //toSting of 1.0E-5=1.0E-5
  //toSting of 1.0E-6=1.0E-6
  //toSting of 1.0E-7=1.0E-7
  //toSting of 1.0E-8=1.0E-8
  //toSting of 1.0E-9=1.0E-9
  //toSting of 1.0E-10=1.0E-10
  //toSting of 1.0E-11=1.0E-11
  double dbl = increment.doubleValue ();
  String str = Double.toString (dbl);
//  System.out.println ("NumberBoxDefaultPatternCalculator: toSting of " + dbl + "=" + str);
  if (str.contains ("E"))
  {
   return Integer.parseInt (str.substring (str.indexOf ("E") + 1));
  }
  if (str.endsWith (".0"))
  {
   return str.length () - 3;
  }
  while (str.endsWith ("0"))
  {
   str = str.substring (0, str.length () - 1);
  }
  return - (str.length () - str.indexOf (".") - 1);
 }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top