Come arrotondare un numero a n decimali in Java


1259

Quello che vorrei è un metodo per convertire un doppio in una stringa che arrotonda usando il metodo half-up - cioè se il decimale da arrotondare è 5, arrotonda sempre al numero successivo. Questo è il metodo standard di arrotondamento che molte persone si aspettano nella maggior parte delle situazioni.

Vorrei anche visualizzare solo cifre significative, ovvero non dovrebbero esserci zero finali.

So che un metodo per farlo è usare il String.formatmetodo:

String.format("%.5g%n", 0.912385);

ritorna:

0.91239

il che è fantastico, tuttavia mostra sempre numeri con 5 cifre decimali anche se non sono significativi:

String.format("%.5g%n", 0.912300);

ritorna:

0.91230

Un altro metodo consiste nell'utilizzare DecimalFormatter:

DecimalFormat df = new DecimalFormat("#.#####");
df.format(0.912385);

ritorna:

0.91238

Tuttavia, come puoi vedere, utilizza un arrotondamento a metà. Cioè arrotonderà se la cifra precedente è pari. Quello che mi piacerebbe è questo:

0.912385 -> 0.91239
0.912300 -> 0.9123

Qual è il modo migliore per raggiungere questo obiettivo in Java?

Risposte:


751

Utilizzare setRoundingMode, impostare RoundingModeesplicitamente per gestire il problema con il giro di metà uniforme, quindi utilizzare il modello di formato per l'output richiesto.

Esempio:

DecimalFormat df = new DecimalFormat("#.####");
df.setRoundingMode(RoundingMode.CEILING);
for (Number n : Arrays.asList(12, 123.12345, 0.23, 0.1, 2341234.212431324)) {
    Double d = n.doubleValue();
    System.out.println(df.format(d));
}

dà l'output:

12
123.1235
0.23
0.1
2341234.2125

EDIT : la risposta originale non riguarda l'accuratezza dei doppi valori. Va bene se non ti interessa molto se si arrotonda o si abbassa. Ma se si desidera un arrotondamento accurato, è necessario tenere conto dell'accuratezza prevista dei valori. I valori in virgola mobile hanno una rappresentazione binaria internamente. Ciò significa che un valore come 2.7735 non ha effettivamente quel valore esatto internamente. Può essere leggermente più grande o leggermente più piccolo. Se il valore interno è leggermente più piccolo, non arrotonderà a 2.7740. Per porre rimedio a tale situazione, è necessario essere consapevoli dell'accuratezza dei valori con cui si sta lavorando e aggiungere o sottrarre quel valore prima dell'arrotondamento. Ad esempio, quando sai che i tuoi valori sono precisi fino a 6 cifre, quindi per arrotondare i valori a metà strada, aggiungi quella precisione al valore:

Double d = n.doubleValue() + 1e-6;

Per arrotondare per difetto, sottrarre la precisione.


9
Questa è probabilmente la migliore soluzione presentata finora. Il motivo per cui non ho individuato questa funzione quando ho visto per la prima volta la classe DecimalFormat è che è stato introdotto solo in Java 1.6. Sfortunatamente sono limitato a usare 1.5 ma sarà utile sapere per il futuro.
Alex Spurling,

1
Ho provato questo con "#.##":, arrotondamento HALF_UP. 256.335f-> "256.33"... (l'esempio deriva dai commenti alla risposta di @ asterite).
bigstones,

6
Fai attenzione perché DecimalFormat dipende dalla tua configurazione locale corrente, potresti non ottenere un punto come separatore. Personalmente preferisco la risposta di Asterite di seguito
Gomino

1
Inoltre, tieni presente che non dovresti aspettarti che DecimalFormat sia thread-safe. Secondo i documenti Java : i formati decimali non sono generalmente sincronizzati. Si consiglia di creare istanze di formato separate per ogni thread. Se più thread accedono contemporaneamente a un formato, è necessario sincronizzarlo esternamente.
CGK,

1
come faccio in modo che

471

Supponendo che valuesia un double, puoi fare:

(double)Math.round(value * 100000d) / 100000d

Questo è per una precisione a 5 cifre. Il numero di zeri indica il numero di decimali.


71
AGGIORNAMENTO: Ho appena confermato che farlo È MODO più veloce dell'uso di DecimalFormat. Ho eseguito il loop utilizzando DecimalFormat 200 volte e questo metodo. DecimalFormat ha impiegato 14ms per completare i 200 loop, questo metodo ha richiesto meno di 1ms. Come sospettavo, questo è più veloce. Se vieni pagato dal ciclo dell'orologio, questo è ciò che dovresti fare. Sono sorpreso che Chris Cudmore abbia persino detto ciò che ha detto di essere onesto. l'allocazione di oggetti è sempre più costosa rispetto al casting di primitive e all'uso di metodi statici (Math.round () rispetto a decimalFormat.format ()).
Andi Jay,

99
Questa tecnica fallisce in oltre il 90% dei casi. -1.
Marchese di Lorne,

25
In effetti, questo non riesce: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck,

54
Fai molta attenzione quando usi questo metodo (o qualsiasi arrotondamento di punti fluttuanti). Non riesce per qualcosa di semplice come 265.335. Il risultato intermedio di 265.335 * 100 (precisione di 2 cifre) è 26533.49999999999996. Ciò significa che viene arrotondato per difetto a 265.33. Ci sono semplicemente problemi intrinseci durante la conversione da numeri in virgola mobile a numeri decimali reali. Vedi la risposta di EJP qui su stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek

6
@SebastiaanvandenBroek: Wow, non ho mai saputo che fosse così facile ottenere una risposta sbagliata. Tuttavia, se si lavora con numeri non esatti, si deve riconoscere che qualsiasi valore non è esatto . 265.335significa davvero 265.335 += tolerance, dove la tolleranza dipende dalle operazioni precedenti e dall'intervallo di valori di input. Non conosciamo il valore vero, esatto. Ai valori limite, entrambe le risposte sono probabilmente corrette. Se dobbiamo essere precisi, non dovremmo lavorare in doppio. Il failqui non sta convertendo nuovamente in doppio. È nel pensiero di OP che può fare affidamento sull'arrivo 265.335come esattamente questo.
ToolmakerSteve

191
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);

ti porterà a BigDecimal. Per ottenere la stringa di fuori di esso, basta chiamare che BigDecimal's toStringmetodo o il toPlainStringmetodo per Java 5+ per una stringa di formato normale.

Programma di esempio:

package trials;
import java.math.BigDecimal;

public class Trials {

    public static void main(String[] args) {
        int yourScale = 10;
        System.out.println(BigDecimal.valueOf(0.42344534534553453453-0.42324534524553453453).setScale(yourScale, BigDecimal.ROUND_HALF_UP));
    }

38
Questa è la mia soluzione preferita. Ancora più breve: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) in realtà chiama Double.toString () sotto il cofano;)
Etienne Neveu

4
Bello. Non tagliare gli angoli e usarli new BigDecimal(doubleVar)perché potresti riscontrare problemi con l'arrotondamento dei punti fluttuanti
Edd,

8
@Edd, è interessante notare che il problema di arrotondamento si verifica nel caso in cui SebastiaanvandenBroek menzioni in commento alla risposta dell'asterite. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, ma (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
ToolmakerSteve

7
@ToolmakerSteve Questo perché l'uso del nuovo BigDecimal con il doppio prende direttamente il doppio valore e tenta di usarlo per creare il BigDecimal, mentre quando si utilizza BigDecimal.valueOf o il modulo tostring lo analizza prima in una stringa (una rappresentazione più esatta) prima della conversione .
MetroidFan2002,

116

Puoi anche usare il

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

per assicurarti di avere gli 0 finali.


25
Credo che uno degli obiettivi della questione è che "ci dovrebbe non essere alcun zeri finali".
Lunchbox,

7
Per questa domanda, l'op non voleva zeri, ma questo è esattamente quello che volevo. Se hai un elenco di numeri con 3 cifre decimali, vuoi che abbiano tutte le stesse cifre anche se è 0.
Tom Kincaid

Hai dimenticato di specificareRoundingMode.
IgorGanapolsky,

1
@IgorGanapolsky Decimal modeusa di defaultRoundingMode.HALF_EVEN.
EndermanAPM

87

Come altri hanno notato, la risposta corretta è usare DecimalFormato BigDecimal. Il virgola mobile non ha posizioni decimali, quindi non è possibile arrotondare / troncare ad un numero specifico di esse in primo luogo. Devi lavorare con una radice decimale, ed è quello che fanno queste due classi.

Sto postando il seguente codice come contro-esempio a tutte le risposte in questo thread e in effetti su StackOverflow (e altrove) che raccomandano la moltiplicazione seguita dal troncamento seguito dalla divisione. Spetta ai sostenitori di questa tecnica spiegare perché il codice seguente produce un output errato in oltre il 92% dei casi.

public class RoundingCounterExample
{

    static float roundOff(float x, int position)
    {
        float a = x;
        double temp = Math.pow(10.0, position);
        a *= temp;
        a = Math.round(a);
        return (a / (float)temp);
    }

    public static void main(String[] args)
    {
        float a = roundOff(0.0009434f,3);
        System.out.println("a="+a+" (a % .001)="+(a % 0.001));
        int count = 0, errors = 0;
        for (double x = 0.0; x < 1; x += 0.0001)
        {
            count++;
            double d = x;
            int scale = 2;
            double factor = Math.pow(10, scale);
            d = Math.round(d * factor) / factor;
            if ((d % 0.01) != 0.0)
            {
                System.out.println(d + " " + (d % 0.01));
                errors++;
            }
        }
        System.out.println(count + " trials " + errors + " errors");
    }
}

Uscita di questo programma:

10001 trials 9251 errors

EDIT: Per rispondere ad alcuni commenti qui sotto ho rifatto la parte del modulo del loop di test usando BigDecimale new MathContext(16)per l'operazione del modulo come segue:

public static void main(String[] args)
{
    int count = 0, errors = 0;
    int scale = 2;
    double factor = Math.pow(10, scale);
    MathContext mc = new MathContext(16, RoundingMode.DOWN);
    for (double x = 0.0; x < 1; x += 0.0001)
    {
        count++;
        double d = x;
        d = Math.round(d * factor) / factor;
        BigDecimal bd = new BigDecimal(d, mc);
        bd = bd.remainder(new BigDecimal("0.01"), mc);
        if (bd.multiply(BigDecimal.valueOf(100)).remainder(BigDecimal.ONE, mc).compareTo(BigDecimal.ZERO) != 0)
        {
            System.out.println(d + " " + bd);
            errors++;
        }
    }
    System.out.println(count + " trials " + errors + " errors");
}

Risultato:

10001 trials 4401 errors

8
Il trucco è che in tutti i tuoi errori 9251, il risultato stampato è ancora corretto.
Didier L

7
@DidierL Non mi sorprende. Ho avuto la fortuna di fare "Metodi numerici" come il mio primo corso di informatica e di essere stato introdotto all'inizio di ciò che può e non può fare in virgola mobile. La maggior parte dei programmatori è piuttosto vaga al riguardo.
Marchese di Lorne,

15
Tutto quello che stai facendo è confutare che il floating non rappresenta esattamente molti valori decimali, che spero capiremo tutti. Non che l'arrotondamento causi un problema. Come ammetti, i numeri continuano a essere stampati come previsto.
Peter Lawrey,

8
Il test è interrotto, arrotondare () e il test fallisce il 94% delle volte. ideone.com/1y62CY stampe 100 trials 94 errorsDovresti iniziare con un test che passa e mostrare che l'introduzione dell'arrotondamento interrompe il test.
Peter Lawrey,

6
Confutazione, confutata qui. Utilizzo di Math.round per questa gamma di doublecome nessun errore ideone.com/BVCHh3
Peter Lawrey

83

Supponi di avere

double d = 9232.129394d;

Puoi usare BigDecimal

BigDecimal bd = new BigDecimal(d).setScale(2, RoundingMode.HALF_EVEN);
d = bd.doubleValue();

o senza BigDecimal

d = Math.round(d*100)/100.0d;

con entrambe le soluzioni d == 9232.13


2
Penso che questa sia la migliore soluzione per gli utenti Java 1.5 (e inferiori). Un commento, tuttavia, non utilizzare la modalità di arrotondamento HALF_EVEN poiché ha un comportamento diff per i numeri pari e dispari (2,5 round a 2 mentre 5,5 round a 6, ad esempio), a meno che questo non sia quello che desideri.
IcedDante,

4
La prima soluzione è corretta: la seconda non funziona. Vedi qui per la prova.
Marchese di Lorne,

1
@EJP: anche la prima soluzione con RoundingMode.HALF_UPè sbagliata. Provalo con 1.505. Il modo giusto è usare BigDecimal.valueOf(d).
Matthias Braun,

Matthias Braun, la soluzione va bene, quindi 31 up .. 1.505 decimale è memorizzato in virgola mobile doppio come 1.50499998 se vuoi prendere 1.505 e convertire da doppio a decimale, quindi devi prima convertirlo in Double.toString (x) quindi inseriscilo in un BigDecimal (), ma è estremamente lento e in primo luogo sconfigge lo scopo di usare il doppio per la velocità.
hamish

1
Ho eseguito un ciclo di 100k con BigDecimal (impiegata 225 ms) e Math.round (2 ms) e qui è il tempo ... Tempo impiegato: 225 milli secondi per la conversione usando in: 9232.13 Tempo impiegato: 2 milli secondi per la conversione in : 9232.13 techiesinfo.com
user1114134

59

È possibile utilizzare la classe DecimalFormat.

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));

Qualche motivo per cui è Double.valueOf()stato scelto Double.parseDouble()? Il valueOf()metodo restituisce un Doubleoggetto, mentre parseDouble()restituirà una doubleprimitiva. Con il modo in cui viene scritto il codice corrente, si applica anche l'auto-unboxing al ritorno per lanciarlo sulla primitiva che la twoDoublevariabile si aspetta, un'operazione extra bytecode. parseDouble()Invece cambierei la risposta da usare .
ecbrodie,

Double.parseDouble()necessita di Stringinput.
Ryde,

38

Il Java How-to di Real pubblica questa soluzione, che è anche compatibile con le versioni precedenti a Java 1.6.

BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();

33
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;

2
sì, questo è esattamente ciò che math.round fa per i numeri positivi, ma ci hai provato con i numeri negativi? le persone usano math.round nelle altre soluzioni per coprire anche il caso di numeri negativi.
hamish

Nota: Math.floor(x + 0.5)eMath.round(x)
Peter Lawrey,

30

@Milhous: il formato decimale per l'arrotondamento è eccellente:

Puoi anche usare il

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

per assicurarti di avere gli 0 finali.

Aggiungo che questo metodo è molto efficace nel fornire un vero meccanismo numerico di arrotondamento, non solo visivamente, ma anche durante l'elaborazione.

Ipotetico: devi implementare un meccanismo di arrotondamento in un programma GUI. Per modificare l'accuratezza / precisione di un risultato risultato è sufficiente cambiare il formato del punto di inserimento (cioè tra parentesi). Così che:

DecimalFormat df = new DecimalFormat("#0.######");
df.format(0.912385);

ritornerebbe come output: 0.912385

DecimalFormat df = new DecimalFormat("#0.#####");
df.format(0.912385);

ritornerebbe come output: 0.91239

DecimalFormat df = new DecimalFormat("#0.####");
df.format(0.912385);

ritornerebbe come output: 0.9124

[MODIFICA: anche se il formato del punto di inserimento è simile ("# 0. ############") e si immette un decimale, ad esempio 3.1415926, per l'argomento, DecimalFormat non produce alcun rifiuto ( es. zero finali) e restituirà: 3.1415926.. se sei così incline. Certo, è un po 'prolisso per il gradimento di alcuni sviluppatori - ma hey, ha un basso ingombro di memoria durante l'elaborazione ed è molto facile da implementare.]

In sostanza, la bellezza di DecimalFormat è che gestisce contemporaneamente l'aspetto delle corde e il livello di precisione di arrotondamento impostato. Ergo: ottieni due vantaggi al prezzo di un'implementazione del codice. ;)


2
Se vuoi davvero numeri decimali per il calcolo (e non solo per l'output), non utilizzare un formato a virgola mobile basato su binario come double. Usa BigDecimal o qualsiasi altro formato basato su decimali.
Paŭlo Ebermann,

20

Ecco un riepilogo di ciò che è possibile utilizzare se si desidera il risultato come stringa:

  1. DecimalFormat # setRoundingMode () :

    DecimalFormat df = new DecimalFormat("#.#####");
    df.setRoundingMode(RoundingMode.HALF_UP);
    String str1 = df.format(0.912385)); // 0.91239
  2. BigDecimal # setScale ()

    String str2 = new BigDecimal(0.912385)
        .setScale(5, BigDecimal.ROUND_HALF_UP)
        .toString();

Ecco un suggerimento su quali librerie è possibile utilizzare se si desidera doublecome risultato. Non lo consiglierei per la conversione di stringhe, tuttavia, poiché double potrebbe non essere in grado di rappresentare esattamente ciò che vuoi (vedi ad esempio qui ):

  1. Precisione dalla matematica di Apache Commons

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
  2. Funzioni di Colt

    double rounded = Functions.round(0.00001).apply(0.912385)
  3. Utensili da Weka

    double rounded = Utils.roundDouble(0.912385, 5)

18

È possibile utilizzare il seguente metodo di utilità-

public static double round(double valueToRound, int numberOfDecimalPlaces)
{
    double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
    double interestedInZeroDPs = valueToRound * multipicationFactor;
    return Math.round(interestedInZeroDPs) / multipicationFactor;
}

@mariolpantunes: fallirà. Prova questo: round(1.005,2);oppureround(0.50594724957626620092, 20);
Matthias Braun,

Funziona. Ma float e doppi non informativi sono approssimazioni. Consideriamo il tuo primo esempio. Se si stampa l'output degli interessatiInZeroDPs prima di Math.round, verrà stampato 100.49999999999999. Hai perso precisione come Math.round intorno ad esso come 100. A causa della natura o dei galleggianti e dei doppi ci sono casi limite quando non funziona correttamente (maggiori informazioni qui en.wikipedia.org/wiki/Floating_point#Accuracy_problems )
mariolpantunes

il doppio è un veloce! il decimale è lento. i computer non si preoccupano di elaborare il loro pensiero in notazione decimale. devi rinunciare alla precisione decimale per mantenere il virgola mobile doppio veloce.
hamish

@hamish La domanda riguarda la precisione, non la velocità.
Marchese di Lorne,



7

Prova questo: org.apache.commons.math3.util.Precision.round (doppia x, scala int)

Vedi: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html

La homepage della Biblioteca di matematica di Apache Commons è: http://commons.apache.org/proper/commons-math/index.html

L'implementazione interna di questo metodo è:

public static double round(double x, int scale) {
    return round(x, scale, BigDecimal.ROUND_HALF_UP);
}

public static double round(double x, int scale, int roundingMethod) {
    try {
        return (new BigDecimal
               (Double.toString(x))
               .setScale(scale, roundingMethod))
               .doubleValue();
    } catch (NumberFormatException ex) {
        if (Double.isInfinite(x)) {
            return x;
        } else {
            return Double.NaN;
        }
    }
}

7

Dato che non ho trovato una risposta completa su questo tema, ho messo insieme una classe che dovrebbe gestirlo correttamente, con il supporto per:

  • Formattazione : formatta facilmente un doppio in stringa con un certo numero di cifre decimali
  • Analisi : analizzare nuovamente il valore formattato per raddoppiare
  • Impostazioni internazionali : formattare e analizzare utilizzando le impostazioni internazionali predefinite
  • Notazione esponenziale : iniziare a utilizzare la notazione esponenziale dopo una determinata soglia

L'utilizzo è piuttosto semplice :

(Per il bene di questo esempio sto usando una localizzazione personalizzata)

public static final int DECIMAL_PLACES = 2;

NumberFormatter formatter = new NumberFormatter(DECIMAL_PLACES);

String value = formatter.format(9.319); // "9,32"
String value2 = formatter.format(0.0000005); // "5,00E-7"
String value3 = formatter.format(1324134123); // "1,32E9"

double parsedValue1 = formatter.parse("0,4E-2", 0); // 0.004
double parsedValue2 = formatter.parse("0,002", 0); // 0.002
double parsedValue3 = formatter.parse("3423,12345", 0); // 3423.12345

Ecco la classe :

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;

public class NumberFormatter {

    private static final String SYMBOL_INFINITE           = "\u221e";
    private static final char   SYMBOL_MINUS              = '-';
    private static final char   SYMBOL_ZERO               = '0';
    private static final int    DECIMAL_LEADING_GROUPS    = 10;
    private static final int    EXPONENTIAL_INT_THRESHOLD = 1000000000; // After this value switch to exponential notation
    private static final double EXPONENTIAL_DEC_THRESHOLD = 0.0001; // Below this value switch to exponential notation

    private DecimalFormat decimalFormat;
    private DecimalFormat decimalFormatLong;
    private DecimalFormat exponentialFormat;

    private char groupSeparator;

    public NumberFormatter(int decimalPlaces) {
        configureDecimalPlaces(decimalPlaces);
    }

    public void configureDecimalPlaces(int decimalPlaces) {
        if (decimalPlaces <= 0) {
            throw new IllegalArgumentException("Invalid decimal places");
        }

        DecimalFormatSymbols separators = new DecimalFormatSymbols(Locale.getDefault());
        separators.setMinusSign(SYMBOL_MINUS);
        separators.setZeroDigit(SYMBOL_ZERO);

        groupSeparator = separators.getGroupingSeparator();

        StringBuilder decimal = new StringBuilder();
        StringBuilder exponential = new StringBuilder("0.");

        for (int i = 0; i < DECIMAL_LEADING_GROUPS; i++) {
            decimal.append("###").append(i == DECIMAL_LEADING_GROUPS - 1 ? "." : ",");
        }

        for (int i = 0; i < decimalPlaces; i++) {
            decimal.append("#");
            exponential.append("0");
        }

        exponential.append("E0");

        decimalFormat = new DecimalFormat(decimal.toString(), separators);
        decimalFormatLong = new DecimalFormat(decimal.append("####").toString(), separators);
        exponentialFormat = new DecimalFormat(exponential.toString(), separators);

        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        decimalFormatLong.setRoundingMode(RoundingMode.HALF_UP);
        exponentialFormat.setRoundingMode(RoundingMode.HALF_UP);
    }

    public String format(double value) {
        String result;
        if (Double.isNaN(value)) {
            result = "";
        } else if (Double.isInfinite(value)) {
            result = String.valueOf(SYMBOL_INFINITE);
        } else {
            double absValue = Math.abs(value);
            if (absValue >= 1) {
                if (absValue >= EXPONENTIAL_INT_THRESHOLD) {
                    value = Math.floor(value);
                    result = exponentialFormat.format(value);
                } else {
                    result = decimalFormat.format(value);
                }
            } else if (absValue < 1 && absValue > 0) {
                if (absValue >= EXPONENTIAL_DEC_THRESHOLD) {
                    result = decimalFormat.format(value);
                    if (result.equalsIgnoreCase("0")) {
                        result = decimalFormatLong.format(value);
                    }
                } else {
                    result = exponentialFormat.format(value);
                }
            } else {
                result = "0";
            }
        }
        return result;
    }

    public String formatWithoutGroupSeparators(double value) {
        return removeGroupSeparators(format(value));
    }

    public double parse(String value, double defValue) {
        try {
            return decimalFormat.parse(value).doubleValue();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return defValue;
    }

    private String removeGroupSeparators(String number) {
        return number.replace(String.valueOf(groupSeparator), "");
    }

}

7

Se vuoi davvero numeri decimali per il calcolo (e non solo per l'output), non utilizzare un formato a virgola mobile basato su binario come double.

Use BigDecimal or any other decimal-based format.

Uso BigDecimal per i calcoli, ma tieni presente che dipende dalla dimensione dei numeri con cui hai a che fare. Nella maggior parte delle mie implementazioni, trovo che l'analisi da doppio o intero a Long sia sufficiente per calcoli con numeri molto grandi.

In effetti, di recente ho usato parsed-to-Long per ottenere rappresentazioni accurate (anziché risultati esadecimali) in una GUI per numeri grandi come ################### ############### caratteri (come esempio).


6

Per raggiungere questo obiettivo possiamo usare questo formattatore:

 DecimalFormat df = new DecimalFormat("#.00");
 String resultado = df.format(valor)

o:

DecimalFormat df = new DecimalFormat("0.00"); :

Utilizzare questo metodo per ottenere sempre due decimali:

   private static String getTwoDecimals(double value){
      DecimalFormat df = new DecimalFormat("0.00"); 
      return df.format(value);
    }

Definire questi valori:

91.32
5.22
11.5
1.2
2.6

Usando il metodo possiamo ottenere questi risultati:

91.32
5.22
11.50
1.20
2.60

demo online.


5

Nel caso in cui qualcuno abbia ancora bisogno di aiuto con questo. Questa soluzione funziona perfettamente per me.

private String withNoTrailingZeros(final double value, final int nrOfDecimals) {
return new BigDecimal(String.valueOf(value)).setScale(nrOfDecimals,  BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();

}

restituisce a String con l'output desiderato.


Si prega di includere il motivo del downvoting nel commento, altrimenti è ciò che chiamiamo intimidazione.
Asher. M. O

4

Sono d'accordo con la risposta scelta da usare DecimalFormat--- o in alternativaBigDecimal .

Si prega di leggere prima l' aggiornamento sotto!

Tuttavia, se si fa desidera arrotondare il valore doppio e ottenere un doublerisultato di valore, è possibile utilizzare org.apache.commons.math3.util.Precision.round(..)come detto sopra. L'implementazione utilizza BigDecimal, è lenta e crea immondizia.

Un metodo simile ma rapido e privo di immondizia viene fornito DoubleRounderdall'utilità nella libreria decimal4j:

 double a = DoubleRounder.round(2.0/3.0, 3);
 double b = DoubleRounder.round(2.0/3.0, 3, RoundingMode.DOWN);
 double c = DoubleRounder.round(1000.0d, 17);
 double d = DoubleRounder.round(90080070060.1d, 9);
 System.out.println(a);
 System.out.println(b);
 System.out.println(c);
 System.out.println(d);

Verrà emesso

 0.667
 0.666
 1000.0
 9.00800700601E10

Vedi https://github.com/tools4j/decimal4j/wiki/DoubleRounder-Utility

Disclaimer: sono coinvolto nel progetto decimal4j.

Aggiornamento: come ha sottolineato @iaforek, DoubleRounder a volte restituisce risultati controintuitivi. Il motivo è che esegue arrotondamenti matematicamente corretti. Per esempioDoubleRounder.round(256.025d, 2) verrà arrotondato per difetto a 256.02 perché il doppio valore rappresentato come 256.025d è leggermente inferiore al valore razionale 256.025 e quindi verrà arrotondato per difetto.

Appunti:

  • Questo comportamento è molto simile a quello del BigDecimal(double)costruttore (ma non a quello valueOf(double)che utilizza il costruttore di stringhe).
  • Il problema può essere aggirato con una doppia fase di arrotondamento per prima una maggiore precisione, ma è complicato e non entrerò nei dettagli qui

Per quei motivi e tutto quanto sopra menzionato in questo post non posso raccomandare di usare DoubleRounder .


Hai metriche che mostrano quanto è efficiente la tua soluzione rispetto alle altre?
iaforek,

Non l'ho confrontato con altre soluzioni ma esiste un benchmark jmh disponibile nel codice sorgente: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… Ho eseguito il benchmark su una VM , i risultati sono disponibili come file CSV qui: github.com/tools4j/decimal4j/wiki/Performance
marco

1
DoubleRounder ha esito negativo per i seguenti casi: DoubleRounder.round (256.025d, 2) - previsto: 256.03, effettivo: 256.02 o per DoubleRounder.round (260.775d, 2) - previsto: 260.78, effettivo: 260.77.
iaforek,

@iaforek: questo è corretto, perché DoubleRounder esegue arrotondamenti matematicamente corretti. Tuttavia, ammetto che ciò è in qualche modo controintuitivo e quindi aggiornerò la mia risposta di conseguenza.
marco,

3

Lo snippet di codice seguente mostra come visualizzare n cifre. Il trucco è impostare la variabile pp su 1 seguita da n zeri. Nell'esempio seguente, il valore di variabile PP ha 5 zeri, quindi verranno visualizzate 5 cifre.

double pp = 10000;

double myVal = 22.268699999999967;
String needVal = "22.2687";

double i = (5.0/pp);

String format = "%10.4f";
String getVal = String.format(format,(Math.round((myVal +i)*pp)/pp)-i).trim();

3

Se stai usando DecimalFormatper convertire doublein String, è molto semplice:

DecimalFormat formatter = new DecimalFormat("0.0##");
formatter.setRoundingMode(RoundingMode.HALF_UP);

double num = 1.234567;
return formatter.format(num);

Esistono diversi RoundingModevalori enum tra cui scegliere, a seconda del comportamento richiesto.


3

Sono venuto qui solo per avere una semplice risposta su come arrotondare un numero. Questa è una risposta supplementare per fornirlo.

Come arrotondare un numero in Java

Il caso più comune è usare Math.round().

Math.round(3.7) // 4

I numeri sono arrotondati al numero intero più vicino. Un .5valore viene arrotondato per eccesso. Se è necessario un comportamento di arrotondamento diverso da quello, è possibile utilizzare una delle altre funzioni matematiche . Vedi il confronto di seguito.

il giro

Come indicato sopra, questo arrotonda al numero intero più vicino. .5i decimali arrotondano per eccesso. Questo metodo restituisce un int.

Math.round(3.0); // 3
Math.round(3.1); // 3
Math.round(3.5); // 4
Math.round(3.9); // 4

Math.round(-3.0); // -3
Math.round(-3.1); // -3
Math.round(-3.5); // -3 *** careful here ***
Math.round(-3.9); // -4

ceil

Qualsiasi valore decimale viene arrotondato per eccesso al numero intero successivo. Va al soffitto . Questo metodo restituisce a double.

Math.ceil(3.0); // 3.0
Math.ceil(3.1); // 4.0
Math.ceil(3.5); // 4.0
Math.ceil(3.9); // 4.0

Math.ceil(-3.0); // -3.0
Math.ceil(-3.1); // -3.0
Math.ceil(-3.5); // -3.0
Math.ceil(-3.9); // -3.0

pavimento

Qualsiasi valore decimale viene arrotondato per difetto all'intero successivo. Questo metodo restituisce a double.

Math.floor(3.0); // 3.0
Math.floor(3.1); // 3.0
Math.floor(3.5); // 3.0
Math.floor(3.9); // 3.0

Math.floor(-3.0); // -3.0
Math.floor(-3.1); // -4.0
Math.floor(-3.5); // -4.0
Math.floor(-3.9); // -4.0

Rint

Questo è simile al arrotondamento in quei valori decimali arrotondati al numero intero più vicino. Tuttavia, a differenza dei valori round, .5arrotondare all'intero pari. Questo metodo restituisce a double.

Math.rint(3.0); // 3.0
Math.rint(3.1); // 3.0
Math.rint(3.5); // 4.0 ***
Math.rint(3.9); // 4.0
Math.rint(4.5); // 4.0 ***
Math.rint(5.5); // 6.0 ***

Math.rint(-3.0); // -3.0
Math.rint(-3.1); // -3.0
Math.rint(-3.5); // -4.0 ***
Math.rint(-3.9); // -4.0
Math.rint(-4.5); // -4.0 ***
Math.rint(-5.5); // -6.0 ***

1
stai solo risolvendo il caso particolare dell'arrotondamento a 0 decimali. La domanda originale è più generica.
lukas84,

3

Se stai usando una tecnologia che ha un JDK minimo. Ecco un modo senza librerie Java:

double scale = 100000;    
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;

Ciò fallirebbe nei casi in cui myVal non sia inferiore a 1 e con zeri dopo i decimali oltre il valore di scala. Supponi di avere myVal = 9.00000000912385; Quanto sopra restituirà 9.0. Penso che dovremmo fornire una soluzione che funzioni in tutti i casi di myVal. Non specificamente per il valore che hai dichiarato.
Tavalendo,

@ user102859 Nel tuo esempio, 9.0 è il risultato corretto. Non capisco come questo fallirebbe.
Craigo,

2

DecimalFormat è il modo migliore per produrre, ma non lo preferisco. Lo faccio sempre tutto il tempo, perché restituisce il doppio valore. Quindi posso usarlo più di un semplice output.

Math.round(selfEvaluate*100000d.0)/100000d.0;

O

Math.round(selfEvaluate*100000d.0)*0.00000d1;

Se hai bisogno di grandi cifre decimali, puoi usare BigDecimal. Comunque .0è importante. Senza di essa, l'arrotondamento di 0,33333d5 restituisce 0,33333 e sono consentite solo 9 cifre. La seconda funzione senza .0ha problemi con 0.30000 ritorno 0.30000000000000004.


2

ecco la mia risposta:

double num = 4.898979485566356;
DecimalFormat df = new DecimalFormat("#.##");      
time = Double.valueOf(df.format(num));

System.out.println(num); // 4.89

1

Quindi, dopo aver letto la maggior parte delle risposte, mi sono reso conto che la maggior parte di esse non sarebbe precisa, infatti l'utilizzo BigDecimalsembra la scelta migliore, ma se non capisci come RoundingModefunziona, perderai inevitabilmente la precisione. L'ho capito lavorando con grandi numeri in un progetto e ho pensato che potesse aiutare gli altri ad avere problemi ad arrotondare i numeri. Per esempio.

BigDecimal bd = new BigDecimal("1363.2749");
bd = bd.setScale(2, RoundingMode.HALF_UP);
System.out.println(bd.doubleValue());

Ti aspetteresti di ottenere 1363.28come output, ma finirai con 1363.27, il che non è previsto, se non sai cosa RoundingModesta facendo. Quindi, esaminando Oracle Docs , troverai la seguente descrizione per RoundingMode.HALF_UP.

Modalità di arrotondamento per arrotondare al "vicino più vicino" a meno che entrambi i vicini non siano equidistanti, nel qual caso arrotondare per eccesso.

Quindi, sapendo questo, ci siamo resi conto che non avremmo ottenuto un arrotondamento esatto, a meno che non vogliamo arrotondare verso il vicino più vicino . Quindi, per realizzare un giro adeguato, avremmo bisogno di passare dal n-1decimale alle cifre decimali desiderate. Per esempio.

private double round(double value, int places) throws IllegalArgumentException {

    if (places < 0) throw new IllegalArgumentException();

    // Cast the number to a String and then separate the decimals.
    String stringValue = Double.toString(value);
    String decimals = stringValue.split("\\.")[1];

    // Round all the way to the desired number.
    BigDecimal bd = new BigDecimal(stringValue);
    for (int i = decimals.length()-1; i >= places; i--) {
        bd = bd.setScale(i, RoundingMode.HALF_UP);
    }

    return bd.doubleValue();
}

Questo finirà per darci il risultato atteso, che sarebbe 1363.28.


0

Dove dp = decimale si desidera e il valore è un doppio.

    double p = Math.pow(10d, dp);

    double result = Math.round(value * p)/p;

1
Produce 1.0per value = 1.005e dp = 2. Usa questo invece.
Matthias Braun,

va bene Matt, il tuo esempio non è valido. perché 1.005 non può essere rappresentato in doppio in virgola mobile comunque. deve essere memorizzato veramente sopra o sotto 1.005 cioè è memorizzato come doppio quando compili: 1.0049998 (non è memorizzato come decimale nel codice compilato come faresti credere ai lettori) l'obiettivo è corretto, sta memorizzando i valori come virgola mobile doppio, in cui i casi marginali come i tuoi sono comunque insignificanti. se lo fosse, allora useresti 3dp e poi lo convertiresti in decimale, quindi esegui una funzione rotonda decimale, proprio come il link che hai pubblicato.
hamish

1
@hamish Non vedo dove Matthias "vorrebbe far credere ai lettori" qualcosa del genere che il valore è compilato come decimale. Non mettere le parole nella bocca degli altri.
Marchese di Lorne,

0

Tenere presente che String.format () e DecimalFormat producono string utilizzando le impostazioni internazionali predefinite. Quindi possono scrivere un numero formattato con punto o virgola come separatore tra numeri interi e parti decimali. Per assicurarsi che String sia arrotondato nel formato desiderato, utilizzare java.text.NumberFormat come segue:

  Locale locale = Locale.ENGLISH;
  NumberFormat nf = NumberFormat.getNumberInstance(locale);
  // for trailing zeros:
  nf.setMinimumFractionDigits(2);
  // round to 2 digits:
  nf.setMaximumFractionDigits(2);

  System.out.println(nf.format(.99));
  System.out.println(nf.format(123.567));
  System.out.println(nf.format(123.0));

Stampa in locale inglese (non importa quale sia il locale): 0,99 123,57 123,00

L'esempio è tratto da Farenda: come convertire correttamente double in String .


0

In generale, l'arrotondamento viene eseguito ridimensionando: round(num / p) * p

/**
 * MidpointRounding away from zero ('arithmetic' rounding)
 * Uses a half-epsilon for correction. (This offsets IEEE-754
 * half-to-even rounding that was applied at the edge cases).
 */
double RoundCorrect(double num, int precision) {
    double c = 0.5 * EPSILON * num;
//  double p = Math.pow(10, precision); //slow
    double p = 1; while (precision--> 0) p *= 10;
    if (num < 0)
        p *= -1;
    return Math.round((num + c) * p) / p;
}

// testing edge cases
RoundCorrect(1.005, 2);   // 1.01 correct
RoundCorrect(2.175, 2);   // 2.18 correct
RoundCorrect(5.015, 2);   // 5.02 correct

RoundCorrect(-1.005, 2);  // -1.01 correct
RoundCorrect(-2.175, 2);  // -2.18 correct
RoundCorrect(-5.015, 2);  // -5.02 correct
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.