PDA

View Full Version : java come non troncare numeri


hello
09-05-2007, 13:05
leggendo dei numeri da un file e convertendoli in double usando num = Double.parseDouble(linea); ho notato che quando li stampo questi non sono uguali ad esempio
-0.01976446502439700405
diventa
-0.0197644650243970040

oppure
4512014.1482265731234567890
diventa
4512014.148226573


come posso risolvere, mi servono come numeri senza essere troncati perchè poi devo sommarli/sottrarli???

:help: :help: :help:

PGI-Bis
09-05-2007, 13:17
Usa BigDecimal

Per le operazioni ha dei metodi al posto degli operatori. Sono immutable quindi i metodi-operatori non alterano il valore dell'istanza ma restituiscono nuovi BigDecimal che contengono il risultato. A parte questo sono una gioia :D.

hello
09-05-2007, 18:05
grazie!!! ho provato ma ho alcuni problemi, ecco il codice:

import java.math.BigDecimal;

public class Grandi {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("6666665.2222222224");
BigDecimal num2 = new BigDecimal("2222223.2222222224");
BigDecimal num3 = new BigDecimal("1234567.0987654321");
BigDecimal num4 = new BigDecimal("2.0");
System.out.println("num1 = " +num1);
System.out.println("num2 = " +num2);

BigDecimal somma = num1.add(num2);
System.out.println("somma = " +somma);

BigDecimal moltiplica = num1.multiply(num2);
System.out.println("moltiplicazione = " +moltiplica);

BigDecimal divisione_1 = somma.divide(num4);
System.out.println("divisione_1 = " +divisione_1);

BigDecimal test = moltiplica;
System.out.println("test deve essere uguale a moltiplica = " +test);

BigDecimal divisione_2 = num1.divide(num2);
System.out.println("divisione_2 = " +divisione_2);
}
}

ho questo errore:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1513)
at Grandi.main(Grandi.java:24)


inoltre se volessi fare (num1+num2+test )/3 come dovrei fare ??? :help:

grazie ;)

PGI-Bis
09-05-2007, 18:19
Se dividi allora devi specificare un tipo di arrotondamento perchè il meccanismo predefinito è ultra-conservativo: se non può calcolare un risultato esatto allora spara un'eccezione.

C'è un metodo ad hoc in BigDecimal, il divide che accetta un RoundingMode. In pratica anzichè:

BigDecimal r = a.divide(b);

fai

BigDecimal r = a.divide(b, RoundingMode.HALF_EVEN);

O un altro RoundingMode, devi vedere tu qual'è il più adatto.

hello
09-05-2007, 18:57
ho provato con
BigDecimal divisione_2 = num1.divide(num2, BigDecimal.ROUND_HALF_UP);
che ha arrotondato il risultato a 3 senza avere nessuna eccezione :D

ma per fare (num1+num2+test+ ...)/3 come dovrei fare perchè tutti gli esempi che ho trovato fanno sempre operazioni soltanto tra due valori, inoltre posso dividere direttamente per 3 oppure questo dovrà essere inizializzato in qualche altra variabile. grazie mille!!! :)

PGI-Bis
09-05-2007, 19:45
Supponendo che a, b, c e d siano BigDecimal dirai:

BigDecimal r = a.add(b).add(c).divide(d, RoundingMode.HALF_EVEN);

che tradotto in simboli sarebbe:

(a + b + c) / d

hello
09-05-2007, 20:42
grazie!!! :)

hello
14-05-2007, 19:39
se a, b, c sono BigDecimal e vorrei dividerli per 3

posso fare soltanto così:

BigDecimal tre_ = new BigDecimal("3");
BigDecimal risultato = a.add(b).add(c).divide(tre_, RoundingMode.HALF_EVEN);


cioè non c'è un modo più veloce, tipo come per i float
float a=1;
float b=2;
float tot=(a+b)/2;
System.out.println(tot);

:help:

andbin
14-05-2007, 19:55
cioè non c'è un modo più veloce, tipo come per i floatNo ... i metodi sono quelli!

Per costruire BigDecimal con valori predefiniti (costanti), parti da un int/long/double, non da una stringa (se vai a vedere BigDecimal ha una caterva di costruttori!)

hello
14-05-2007, 21:15
se faccio così e corretto

import java.math.BigDecimal;

public class Prova {
public static void main(String[] args) {
int i = 8;
BigDecimal bd = new BigDecimal(String.valueOf(i));

System.out.println(bd);
}
}

andbin
14-05-2007, 21:19
int i = 8;
BigDecimal bd = new BigDecimal(String.valueOf(i));Non fai prima con new BigDecimal(8) (con Java 5), altrimenti new BigDecimal(8.0)???
Ma sei andato a vedere i costruttori di BigDecimal???

PGI-Bis
14-05-2007, 21:43
Usa sempre il costruttore che richiede una stringa e sei a posto.

BigDecimal bd = new BigDecimal("1");
BigDecimal bg = new BigDecimal("9.0");

eccetera.

hello
14-05-2007, 21:43
Non fai prima con new BigDecimal(8) (con Java 5), altrimenti new BigDecimal(8.0)???
Ma sei andato a vedere i costruttori di BigDecimal???

no scusa, avevo cercato qualche esempio sulla rete

ma
BigDecimal bd = new BigDecimal("8");
BigDecimal bd = new BigDecimal(8);
int i=8;
BigDecimal bd = new BigDecimal(String.valueOf(i));
è la stessa cosa oppure no?

puoi suggerirmi un link per lo studio dei costruttori?

grazie ;)

andbin
14-05-2007, 21:51
ma
BigDecimal bd = new BigDecimal("8");
BigDecimal bd = new BigDecimal(8);
int i=8;
BigDecimal bd = new BigDecimal(String.valueOf(i));
è la stessa cosa oppure no?Nel primo caso usi il costruttore di BigDecimal che prende un String, nel secondo caso quello che prende un int (Java 5) e nel terzo caso come il primo solo che prima da un intero ottieni una stringa e poi la passi a BigDecimal (inutile quindi).

puoi suggerirmi un link per lo studio dei costruttori?Sui concetti relativi ai costruttori?? Qualunque buon libro o tutorial online!
http://www.google.it/search?hl=it&q=java+constructors&btnG=Cerca+con+Google&meta=

PGI-Bis
14-05-2007, 21:55
Per valori interi l'uso di un primitivo (byte, short, int, long) o di una stringa è uguale.

Per valori che hanno una parte frazionaria (float, double) l'uso di un valore double o float, sia esso una variabile piuttosto che un letterale, è problematico perchè BigDecimal assume l'esatto valore del numero che gli passi. Tale valore può non corrispondere a quello espresso dal letterale numerico.

BigDecimal(8.1)

è all'incirca 8.1 mentre

BigDecimal("8.1") è esattamente 8.1.

Considera inoltre l'ovvio rilievo che inizializzare un numero a precisione arbitraria con un valore a precisione limitata è un caso non banale di schizofrenia :D:

hello
14-05-2007, 22:04
Considera inoltre l'ovvio rilievo che inizializzare un numero a precisione arbitraria con un valore a precisione limitata è un caso non banale di schizofrenia :D:

cosa vuol dire inizializzare un numero a precisione arbitraria con un valore a precisione limitata ? mi fai un esempio?

PGI-Bis
14-05-2007, 22:30
Era una facezia. Ammazza, tutti allegri eh? :D

Un double ha 16 cifre significative. Prendi il numero:

4512014.1482265731234567890

e conta fino a 16, a partire da sinistra (la prima cifra diversa da zero). Quello è il double.

4512014.148226573

il resto

1234567890

è fuffa.

Puoi dire:

double x = 4512014.1482265731234567890;
BigDecimal v = new BigDecimal(x);

ma è "schizofrenico" perchè tra il valore di x e il valore di v non c'è differenza, dal punto di vista della coincidenza tra il letterale scritto (4512014.1482265731234567890) ed il valore assunto. Solo 16 cifre del double sono significative: le altre sai già (perchè hai usato un double) che non saranno rappresentate correttamente.

E' la stringa il pane del BigDecimal. Perchè è coerente con l'intenzione. Io voglio un numero che vale esattamente questo: e vomiti un improperio di cifre. E' ok, BigDecimal serve (anche) a quello: a prescindere dal numero di cifre che scrivi sai già che tutto il numero che hai scritto sarà rappresentato, che ogni cifra sarà correttamente assunta perchè la precisione di un BigDecimal è arbitraria.

Perchè tu hai chiesto di BigDecimal? Be', perchè vuoi che un numero con una quantità arbitraria di cifre sia correttamente rappresentato e double non può farlo. Se mi usi un double per inizializzare il BigDecimal siamo punto e a capo :D.

hello
15-05-2007, 00:07
grazie per la spiegazione!!! :)

hello
21-05-2007, 23:56
ma se faccio 3/2 con i BigDecimal come faccio ad ottenere 1.5

perchè se faccio così arrotonda a 1 oppure a 2:

BigDecimal num1 = new BigDecimal("3");
BigDecimal num2 = new BigDecimal("2");
BigDecimal divisione_1 = num1.divide(num2, BigDecimal.ROUND_UP);
BigDecimal divisione_2 = num1.divide(num2, BigDecimal.ROUND_DOWN);

:help:

PGI-Bis
22-05-2007, 10:50
Devi specificare quante cifre dopo la virgola vuoi che siano rappresentate. Puoi farlo in costruzione (con la stringa):

BigDecimal num1 = new BigDecimal("3.0"); //1 cifra dopo la virgola

o con il metodo setScale, che restituisce un nuovo BigDecimal:

BigDecimal num1 = new BigDecimal("3").setScale(1); //1 cifra dopo la virgola

hello
22-05-2007, 13:37
import java.math.BigDecimal;

public class Operazioni_2 {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("3");
BigDecimal num2 = new BigDecimal("2");

BigDecimal divisione = num1.divide(num2, BigDecimal.ROUND_HALF_EVEN).setScale(3);

// se faccio 10/99 questo crea eccezione
BigDecimal divisione_2 = num1.divide(num2).setScale(3);

System.out.println("risultato divisione = " +divisione);
System.out.println("risultato divisione_2 = " +divisione_2);
}
}

se faccio 3/2 il primo arrotonda a 2.000 e il secondo da 1.500 quindi il secondo è un risultato buono ma il problema sorge se faccio 10/99 dove BigDecimal divisione_2 = num1.divide(num2).setScale(3); non riesce ad approssimare e causa l'eccezione!!! c'è un modo per avere il secondo che crea un approssimazione giusta (tipo quella 3/2=1.500) ma senza eccezione :help:

PGI-Bis
22-05-2007, 13:49
Per un milione di marenghi d'argento, cosa c'è di diverso tra la divisione che fa a buon fine e la divisione che causa l'eccezione?

BigDecimal divisione = num1.divide(num2, BigDecimal.ROUND_HALF_EVEN).setScale(3);
BigDecimal divisione_2 = num1.divide(num2).setScale(3);

Cinque, quattro, tre, due...

E sento che ti verrà un altro dubbio. Lo sento.

hello
22-05-2007, 19:51
Per un milione di marenghi d'argento, cosa c'è di diverso tra la divisione che fa a buon fine e la divisione che causa l'eccezione?

BigDecimal divisione = num1.divide(num2, BigDecimal.ROUND_HALF_EVEN).setScale(3);
BigDecimal divisione_2 = num1.divide(num2).setScale(3);

Cinque, quattro, tre, due...

E sento che ti verrà un altro dubbio. Lo sento.


PGI-Bis l'unica differenza credo che sia solo nel fatto che in uno chiedo di arrotondare e nell'altro no :D
il mio "dubbio" è come fare ad avere una divisione che non approssima in maniera drastica (cioè 1.5-->1 oppure 1.5-->2) ma che faccia un "arrotondamento giusto" tipo 3/2=1.5 ... senza causarmi eccezioni !!! c'è una soluzione? ho provato anche BigDecimal.ROUND_UP e BigDecimal.ROUND_DOWN

grazie!!! ;)

PGI-Bis
22-05-2007, 20:08
Non è che tu chieda di arrotondare. Senza specificare il RoundingMode BigDecimal spara un'eccezione se l'operazione che conduce genera un risultato che non è esattamente rappresentabile con il numero di cifre adottate.

Se vuoi evitare di dover specificare le operazioni di arrotondamento ad ogni operazioni puoi usare MathContext nell'inizializzazione del numero.

MathContext ctx = new MathContext(1, RoundingMode.UP);
BigDecimal a = new BigDecimal(..., ctx);
BigDecimal b = new BigDecimal(..., ctx);
BigDecimal c = a.divide(b);

andbin
22-05-2007, 20:17
Prova ad esempio:
BigDecimal num1 = new BigDecimal ("2");
BigDecimal num2 = new BigDecimal ("3");
System.out.println (num1.setScale (3).divide (num2, BigDecimal.ROUND_DOWN));

hello
22-05-2007, 22:45
grazie a entrambi per la risposta, ho provato tutti e due

import java.math.BigDecimal;
import java.math.*;


public class Operazioni_2 {
public static void main(String[] args) {

BigDecimal num1 = new BigDecimal ("15");
BigDecimal num2 = new BigDecimal ("7");
System.out.println (num1.setScale (10).divide (num2, BigDecimal.ROUND_DOWN));

MathContext ctx = new MathContext(1, RoundingMode.UP);
BigDecimal a = new BigDecimal("15", ctx);
BigDecimal b = new BigDecimal("7", ctx);
BigDecimal c = a.divide(b);
System.out.println (c);
}
}

questo è il risultato:
2.1428571428
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1513)
at Operazioni_2.main(Operazioni_2.java:15)

andbin
23-05-2007, 08:35
MathContext ctx = new MathContext(1, RoundingMode.UP);
BigDecimal a = new BigDecimal("15", ctx);
BigDecimal b = new BigDecimal("7", ctx);
BigDecimal c = a.divide(b);
System.out.println (c);Il MathContext specificato nel costruttore serve solo per la inizializzazione del BigDecimal, non per fare altre operazioni sul numero!

Quindi se vuoi usare un MathContext, applicalo alla divisione:

BigDecimal c = a.divide(b, ctx);

PGI-Bis
23-05-2007, 09:36
E' colpa mia, ho dimenticato un ctx nell'esempio.

hello
23-05-2007, 09:45
grazie ho provato!!! ctx serve per fissare il numero di cifre dopo la virgola eseguendo un arrotondamento se queste superano il numero specificato in MathContext ctx = new MathContext(10, RoundingMode.UP); ? ho capito bene?

import java.math.BigDecimal;
import java.math.*;


public class Operazioni_2 {
public static void main(String[] args) {

BigDecimal num1 = new BigDecimal ("7");
BigDecimal num2 = new BigDecimal ("8");
System.out.println (num1.setScale (10).divide (num2, BigDecimal.ROUND_DOWN));

MathContext ctx = new MathContext(10, RoundingMode.UP);
BigDecimal a = new BigDecimal("7");
BigDecimal b = new BigDecimal("8");
//BigDecimal c = a.divide(b);
BigDecimal c = a.divide(b, ctx); // <--------
System.out.println (c);
}
}

nell'esempio ho tolto ctx da
BigDecimal a = new BigDecimal("7", ctx);
BigDecimal b = new BigDecimal("8", ctx);
lasciandolo solo in BigDecimal c = a.divide(b, ctx);

è corretto, cioè posso fare anche così oppure potrebbe modifica il risultato?

grazie!!!

PGI-Bis
23-05-2007, 14:27
In effetti il MathContext in costruzione non serve a niente se il valore è una stringa nota, perchè in quel caso non è possibile che ci siano "decimali pazzi" (float o double) o che il valore sia espresso con una precisione eccessiva.

Se invece i valori arrivano da fonti non controllate allora è meglio specificare il grado di precisione con cui saranno fatti i conti e, dunque, quanti decimali tra quelli eventualmente presenti saranno effettivamente considerati.

hello
23-05-2007, 21:41
grazie x tutto!!! ;)