Spostamento di cifre decimali in un doppio


97

Quindi ho un doppio set uguale a 1234, voglio spostare una cifra decimale per renderlo 12.34

Quindi per fare questo moltiplico da .1 a 1234 due volte, un po 'in questo modo

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

Questo stamperà il risultato, "12.340000000000002"

Esiste un modo, senza semplicemente formattarlo con due cifre decimali, per avere correttamente il doppio archivio 12.34?



43
C'è una ragione per cui non l'hai fatto x /= 100;?
Mark Ingram

Risposte:


189

Se usi doubleo float, dovresti usare l'arrotondamento o aspettarti di vedere alcuni errori di arrotondamento. Se non puoi farlo, usa BigDecimal.

Il problema che hai è che 0.1 non è una rappresentazione esatta e, eseguendo il calcolo due volte, stai aggravando quell'errore.

Tuttavia, 100 possono essere rappresentati accuratamente, quindi prova:

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

che stampa:

12.34

Funziona perché Double.toString(d)esegue una piccola quantità di arrotondamenti per tuo conto, ma non è molto. Se ti stai chiedendo come potrebbe essere senza arrotondamento:

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

stampe:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

In breve, l'arrotondamento è inevitabile per risposte sensate in virgola mobile, indipendentemente dal fatto che lo si faccia esplicitamente o meno.


Nota: x / 100e x * 0.01non sono esattamente gli stessi quando si tratta di errore di arrotondamento. Questo perché l'errore di arrotondamento per la prima espressione dipende dai valori di x, mentre quello 0.01della seconda ha un errore di arrotondamento fisso.

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

stampe

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
Non posso credere di non aver pensato di farlo in primo luogo! Grazie :-P
BlackCow

6
Sebbene 100 possa essere rappresentato esattamente in formato binario, la divisione per 100 non può essere rappresentata esattamente. Pertanto, la scrittura 1234/100, come hai fatto, in realtà non fa nulla per il problema sottostante: dovrebbe essere esattamente uguale alla scrittura 1234 * 0.01.
Brooks Moses,

1
@Peter Lawrey: puoi spiegare di più perché se il numero è dispari o pari influirebbe sull'arrotondamento? Penserei che / = 100 e * =. 01 sarebbe lo stesso perché anche se 100 è un int, verrà comunque convertito in 100.0 come risultato della coercizione del tipo.
eremzeit

1
/100e *0.01sono equivalenti tra loro, ma non agli OP *0.1*0.1.
Amadan

1
Sto solo dicendo che moltiplicando per 0,1 due volte in media introdurrà un errore maggiore rispetto alla moltiplicazione per 0,01 una volta; ma ammetto con gioia il punto di @ JasperBekkers sul fatto che 100 sia diverso, essendo esattamente rappresentabile in formato binario.
Amadan

52

No, se vuoi memorizzare accuratamente i valori decimali, usa BigDecimal. doublesemplicemente non puoi rappresentare esattamente un numero come 0.1, non più di quanto puoi scrivere il valore di un terzo esattamente con un numero finito di cifre decimali.


46

se è solo formattazione, prova printf

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

produzione

12.34

8
Le risposte con un punteggio più alto sono tecnicamente più approfondite, ma questa è la risposta corretta al problema di OP. In genere non ci interessa la leggera imprecisione del double, quindi BigDecimal è eccessivo, ma durante la visualizzazione spesso vogliamo assicurarci che il nostro output corrisponda alla nostra intuizione, quindi System.out.printf()è la strada giusta da percorrere.
dimo414

28

Nel software finanziario è comune utilizzare numeri interi per i penny. A scuola, ci è stato insegnato come usare il punto fisso invece di fluttuare, ma di solito si tratta di potenze di due. Anche la memorizzazione di penny in numeri interi potrebbe essere chiamata "punto fisso".

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

In classe, ci è stato chiesto in generale quali numeri possono essere rappresentati esattamente in una base.

Per base=p1^n1*p2^n2... puoi rappresentare qualsiasi N dove N = n * p1 ^ m1 * p2 ^ m2.

Lascia base=14=2^1*7^1... puoi rappresentare 1/7 1/14 1/28 1/49 ma non 1/3

Conosco il software finanziario: ho convertito i rapporti finanziari di Ticketmaster da VAX asm a PASCAL. Avevano il loro formatln () con codici per pochi centesimi. Il motivo della conversione era che gli interi a 32 bit non erano più sufficienti. +/- 2 miliardi di centesimi sono 20 milioni di dollari e questo è andato in overflow per la Coppa del Mondo o le Olimpiadi, dimenticavo.

Ho giurato di mantenere il segreto. Oh bene. In academea, se va bene pubblichi; nell'industria, lo mantieni segreto.


12

puoi provare la rappresentazione di numeri interi

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

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

5
@ Dan: Perché? Questo è l'approccio corretto per le app finanziarie (o qualsiasi altra app in cui anche un piccolo errore di arrotondamento è inaccettabile), pur mantenendo la velocità a livello di hardware. (Ovviamente, sarebbe racchiuso in una classe, normalmente, non scritto ogni volta)
Amadan

7
C'è un piccolo problema con questa soluzione: se il resto rè inferiore a 10, non si verifica alcun riempimento 0 e 1204 produrrebbe un risultato di 12,4. La stringa di formattazione corretta è più simile a "% d.% 02d"
jakebman

10

Ciò è causato dal modo in cui i computer memorizzano i numeri in virgola mobile. Non lo fanno esattamente. Come programmatore, dovresti leggere questa guida in virgola mobile per familiarizzare con le prove e le tribolazioni della gestione dei numeri in virgola mobile.


Argh, stavo solo scrivendo una spiegazione che collegava esattamente lo stesso posto. +1.
Pop l'

@ Lord Haha, mi dispiace. Comunque ho ottenuto Skeeted. :-)
CanSpice

Ho pensato che fosse per questo, ma mi chiedo se ci sia un modo creativo per spostare la cifra decimale? Poiché è possibile memorizzare 12,34 in modo pulito in un doppio, semplicemente non gli piace la moltiplicazione per
0,1

1
Se fosse stato possibile memorizzare 12.34 in modo pulito in un doppio, non credi che Java lo avrebbe fatto? Non è. Dovrai usare un altro tipo di dati (come BigDecimal). Inoltre, perché non dividi per 100 invece di farlo in un ciclo?
CanSpice

Do'h ... sì, dividendolo per 100 si ottiene un 12.34 pulito ... grazie :-P
BlackCow

9

Divertente che numerosi post menzionino l'uso di BigDecimal ma nessuno si preoccupi di dare la risposta corretta basata su BigDecimal? Perché anche con BigDecimal puoi comunque sbagliare, come dimostrato da questo codice

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")));

Fornisce questo output

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

Il costruttore BigDecimal menziona specificamente che è meglio usare il costruttore String piuttosto che un costruttore numerico. La massima precisione è anche influenzata dal MathContext opzionale.

Secondo il BigDecimal Javadoc è possibile creare un BigDecimal che è esattamente uguale a 0.1, a patto di utilizzare il costruttore String.


5

Si C'è. Con ogni doppia operazione si può perdere la precisione, ma la quantità di precisione è diversa per ogni operazione e può essere ridotta al minimo scegliendo la giusta sequenza di operazioni. Ad esempio, quando si moltiplica un insieme di numeri, è meglio ordinare l'insieme per esponente prima di moltiplicare.

Qualsiasi libro decente sullo scricchiolio dei numeri lo descrive. Ad esempio: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

E per rispondere alla tua domanda:

Usa la divisione invece di moltiplicare, in questo modo ottieni il risultato corretto.

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

3

No, poiché i tipi a virgola mobile Java (in effetti tutti i tipi a virgola mobile) sono un compromesso tra dimensione e precisione. Sebbene siano molto utili per molte attività, se hai bisogno di una precisione arbitraria, dovresti usare BigDecimal.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.