Movendo as casas decimais em dobro


97

Portanto, tenho um conjunto duplo igual a 1234, quero mover uma casa decimal para 12,34

Então, para fazer isso, multiplico 0,1 a 1234 duas vezes, mais ou menos assim

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.println(x);

Isso imprimirá o resultado, "12.340000000000002"

Existe uma maneira, sem simplesmente formatá-lo com duas casas decimais, de ter o armazenamento duplo 12,34 corretamente?



43
Existe uma razão para você não ter feito x /= 100;?
Mark Ingram

Respostas:


189

Se você usar doubleou float, deverá usar arredondamento ou poderá ver alguns erros de arredondamento. Se você não pode fazer isso, use BigDecimal.

O problema que você tem é que 0,1 não é uma representação exata e, ao realizar o cálculo duas vezes, você está agravando esse erro.

No entanto, 100 pode ser representado com precisão, então tente:

double x = 1234;
x /= 100;
System.out.println(x);

que imprime:

12.34

Isso funciona porque Double.toString(d)executa um pequeno arredondamento por você, mas não é muito. Se você está se perguntando como seria a aparência sem arredondamentos:

System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal(x));

estampas:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

Em suma, o arredondamento é inevitável para respostas sensatas em ponto flutuante, esteja você fazendo isso explicitamente ou não.


Nota: x / 100e x * 0.01não são exatamente os mesmos quando se trata de erros de arredondamento. Isso ocorre porque o erro de arredondamento para a primeira expressão depende dos valores de x, enquanto o 0.01da segunda tem um erro de arredondamento fixo.

for(int i=0;i<200;i++) {
    double d1 = (double) i / 100;
    double d2 = i * 0.01;
    if (d1 != d2)
        System.out.println(d1 + " != "+d2);
}

estampas

0.35 != 0.35000000000000003
0.41 != 0.41000000000000003
0.47 != 0.47000000000000003
0.57 != 0.5700000000000001
0.69 != 0.6900000000000001
0.7 != 0.7000000000000001
0.82 != 0.8200000000000001
0.83 != 0.8300000000000001
0.94 != 0.9400000000000001
0.95 != 0.9500000000000001
1.13 != 1.1300000000000001
1.14 != 1.1400000000000001
1.15 != 1.1500000000000001
1.38 != 1.3800000000000001
1.39 != 1.3900000000000001
1.4 != 1.4000000000000001
1.63 != 1.6300000000000001
1.64 != 1.6400000000000001
1.65 != 1.6500000000000001
1.66 != 1.6600000000000001
1.88 != 1.8800000000000001
1.89 != 1.8900000000000001
1.9 != 1.9000000000000001
1.91 != 1.9100000000000001

26
Não acredito que não pensei em fazer isso em primeiro lugar! Obrigado :-P
BlackCow

6
Embora 100 possa ser representado exatamente em formato binário, a divisão por 100 não pode ser representada exatamente. Portanto, escrever 1234/100, como você fez, realmente não faz nada sobre o problema subjacente - deve ser exatamente igual a escrever 1234 * 0.01.
Brooks Moses

1
@Peter Lawrey: Você pode explicar mais por que o fato de o número ser ímpar ou par afetaria o arredondamento? Eu pensaria que / = 100 e * =. 01 seriam iguais porque, embora 100 seja um int, ele será convertido em 100,0 de qualquer forma como resultado da coerção de tipo.
eremzeit

1
/100e *0.01são equivalentes entre si, mas não aos OPs *0.1*0.1.
Amadan

1
Tudo o que estou dizendo é que multiplicar por 0,1 duas vezes, em média, apresentará um erro maior do que multiplicar por 0,01 uma vez; mas felizmente reconhecerei o ponto de @JasperBekkers sobre 100 ser diferente, sendo exatamente representável por binários.
Amadan

52

Não - se você quiser armazenar valores decimais com precisão, use BigDecimal. doublesimplesmente não pode representar um número exatamente como 0,1, mais do que você pode escrever o valor de um terço exatamente com um número finito de dígitos decimais.


46

se for apenas formatação, tente printf

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.printf("%.2f",x);

resultado

12.34

8
As respostas com pontuação mais alta são mais perspicazes do ponto de vista técnico, mas esta é a resposta correta para o problema de OP. Em geral, não nos importamos com a ligeira imprecisão de double, então BigDecimal é um exagero, mas, ao exibir, geralmente queremos garantir que nossa saída corresponda à nossa intuição, então System.out.printf()é o caminho certo a seguir.
dimo414

28

Em software financeiro, é comum usar números inteiros por centavos. Na escola, fomos ensinados a usar ponto fixo em vez de flutuar, mas isso geralmente é potências de dois. O armazenamento de moedas de um centavo em números inteiros também pode ser chamado de "ponto fixo".

int i=1234;
printf("%d.%02d\r\n",i/100,i%100);

Em aula, fomos questionados em geral sobre quais números podem ser representados exatamente em uma base.

Para base=p1^n1*p2^n2... você pode representar qualquer N onde N = n * p1 ^ m1 * p2 ^ m2.

Deixe base=14=2^1*7^1... você pode representar 1/7 1/14 1/28 1/49, mas não 1/3

Eu sei sobre software financeiro - converti os relatórios financeiros da Ticketmaster do VAX asm para PASCAL. Eles tinham seu próprio formatln () com códigos para centavos. O motivo da conversão foi que inteiros de 32 bits não eram mais suficientes. +/- 2 bilhões de centavos é $ 20 milhões e isso transbordou para a Copa do Mundo ou as Olimpíadas, esqueci.

Eu jurei segredo. Ah bem. Na academia, se for bom você publica; na indústria, você mantém isso em segredo.


12

você pode tentar a representação de um número inteiro

int i =1234;
int q = i /100;
int r = i % 100;

System.out.printf("%d.%02d",q, r);

5
@Dan: Por quê? Esta é a abordagem correta para aplicativos financeiros (ou qualquer outro aplicativo em que até mesmo um pequeno erro de arredondamento seja inaceitável), enquanto ainda mantém a velocidade do hardware. (Claro, seria encerrado em uma classe, normalmente, não escrito todas as vezes)
Amadan

7
Há um pequeno problema com essa solução - se o restante rfor menor que 10, nenhum preenchimento de 0 ocorrerá e 1204 produzirá um resultado de 12,4. A string de formatação correta é mais semelhante a "% d.% 02d"
jakebman

10

Isso é causado pela maneira como os computadores armazenam números de ponto flutuante. Eles não fazem exatamente isso. Como programador, você deve ler este guia de ponto flutuante para se familiarizar com as tentativas e tribulações de lidar com números de ponto flutuante.


Argh, eu estava escrevendo uma explicação com link para o mesmo lugar. +1.
Aparece em

@Lord Haha, desculpe. Eu fui Skeeted de qualquer maneira. :-)
CanSpice

Eu descobri que é por isso, mas eu me pergunto se há alguma maneira criativa de mover a casa decimal? Como é possível armazenar 12,34 de forma limpa em um duplo, ele simplesmente não gosta de multiplicar por 0,1
BlackCow

1
Se fosse possível armazenar 12.34 de forma limpa em um double, você não acha que o Java teria feito isso? Não é. Você terá que usar algum outro tipo de dados (como BigDecimal). Além disso, por que você simplesmente não divide por 100 em vez de fazer isso em um loop?
CanSpice de

Ah ... sim, dividir por 100 resulta em 12,34 limpo ... obrigado :-P
BlackCow de

9

Engraçado que vários posts mencionam o uso de BigDecimal, mas ninguém se preocupa em dar a resposta correta com base em BigDecimal? Porque mesmo com BigDecimal, você ainda pode dar errado, como demonstrado por este código

String numstr = "1234";
System.out.println(new BigDecimal(numstr).movePointLeft(2));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal(0.01)));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal("0.01")));

Dá esta saída

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

O construtor BigDecimal menciona especificamente que é melhor usar o construtor String do que um construtor numérico. A precisão máxima também é influenciada pelo MathContext opcional.

De acordo com o Javadoc BigDecimal , é possível criar um BigDecimal que seja exatamente igual a 0,1, desde que você use o construtor String.


5

Sim existe. Com cada operação dupla, você pode perder a precisão, mas a quantidade de precisão difere para cada operação e pode ser minimizada escolhendo a sequência correta de operações. Por exemplo, ao multiplicar um conjunto de números, é melhor classificar o conjunto pelo expoente antes de multiplicar.

Qualquer livro decente sobre análise de números descreve isso. Por exemplo: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

E para responder à sua pergunta:

Use dividir em vez de multiplicar, dessa forma você obterá o resultado correto.

double x = 1234;
for(int i=1;i<=2;i++)
{
  x =  x / 10.0;
}
System.out.println(x);

3

Não, como os tipos de ponto flutuante Java (na verdade, todos os tipos de ponto flutuante) são uma compensação entre tamanho e precisão. Embora sejam muito úteis para muitas tarefas, se você precisa de precisão arbitrária, deve usar BigDecimal.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.