troncamento - troncare decimali in java




Come arrotondare un numero a n decimali in Java (20)

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

Vorrei anche solo visualizzare cifre significative - cioè non dovrebbero esserci zero finali.

So che un metodo per farlo è utilizzare il metodo String.format :

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

ritorna:

0.91239

che è ottimo, tuttavia visualizza sempre i numeri con 5 cifre decimali anche se non sono significativi:

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

ritorna:

0.91230

Un altro metodo è utilizzare DecimalFormatter :

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

ritorna:

0.91238

Tuttavia, come puoi vedere, utilizza arrotondamenti pari a metà. Cioè si arrotonderà se la cifra precedente è pari. Quello che vorrei è questo:

0.912385 -> 0.91239
0.912300 -> 0.9123

Qual è il modo migliore per ottenere questo in Java?


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

È possibile utilizzare la classe DecimalFormat.

double d = 3.76628729;

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

Come altri hanno notato, la risposta corretta è usare DecimalFormat o BigDecimal . Il virgola mobile non ha cifre decimali, quindi non è possibile arrotondare / troncare a un numero specifico di esse in primo luogo. Devi lavorare in un decimale decimale, e questo è ciò che fanno queste due classi.

Sto postando il seguente codice come controesempio a tutte le risposte in questo thread e in effetti su (e altrove) che consiglia la moltiplicazione seguita da troncamento seguito da divisione. Spetta ai sostenitori di questa tecnica spiegare perché il seguente codice produce l'output sbagliato 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 indirizzare alcuni commenti sotto ho ridimensionato la parte modulo del loop di test utilizzando BigDecimal e new MathContext(16) per l'operazione 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

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

  • Formattazione : formatta facilmente un doppio in una stringa con un determinato numero di posizioni decimali
  • Parsing : ripassa il valore formattato per raddoppiare
  • Locale : formatta e analizza usando le impostazioni locali predefinite
  • Notazione esponenziale : inizia a utilizzare la notazione esponenziale dopo una certa soglia

L'utilizzo è piuttosto semplice :

(Per il bene di questo esempio sto usando una locale 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), "");
    }

}

Dove dp = posizione decimale desiderata e valore è un doppio.

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

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

Ecco un riepilogo di cosa puoi usare se vuoi che il risultato sia String:

  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 puoi usare se vuoi double di conseguenza. Non lo consiglierei per la conversione delle stringhe, tuttavia, poiché il doppio potrebbe non essere in grado di rappresentare esattamente quello che vuoi (vedi ad esempio here ):

  1. Precision da Apache Commons Math

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

    double rounded = Functions.round(0.00001).apply(0.912385)
    
  3. Utils di Weka

    double rounded = Utils.roundDouble(0.912385, 5)
    

Nel caso 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 una String con l'output desiderato.


Per ottenere questo possiamo usare questo formattatore:

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

o:

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

Usa 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.


Puoi anche usare il

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

per assicurarti di avere gli 0 di coda.



Se si desidera realmente i numeri decimali per il calcolo (e non solo per l'output), non utilizzare un formato in virgola mobile basato su binario come il doppio.

Use BigDecimal or any other decimal-based format.

Io uso BigDecimal per i calcoli, ma tenete a mente che dipende dalla dimensione dei numeri con cui avete a che fare. Nella maggior parte delle mie implementazioni, trovo che l'analisi da doppio o intero a Long è sufficiente per calcoli numerici molto grandi.

In effetti, recentemente ho usato parsed-to-Long per ottenere rappresentazioni accurate (al contrario dei risultati hex) in una GUI per numeri grandi come ################## ############### caratteri (come esempio).


Se stai utilizzando DecimalFormat per convertire il double in String , è molto semplice:

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

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

Esistono diversi valori di RoundingMode RoundingMode da cui selezionare, in base al comportamento richiesto.


Sono venuto qui solo volendo una semplice risposta su come arrotondare un numero. Questa è una risposta supplementare per fornire questo.

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 valore .5 viene arrotondato. Se è necessario un diverso comportamento di arrotondamento, è possibile utilizzare una delle altre funzioni Math . Vedi il confronto qui sotto.

round

Come detto sopra, questo round al numero intero più vicino. .5 decimali arrotondati 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 al numero intero successivo. Va al soffitto . Questo metodo restituisce un 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

floor

Qualsiasi valore decimale viene arrotondato al numero intero successivo. Questo metodo restituisce un 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 round in quei valori decimali arrotondati al numero intero più vicino. Tuttavia, a differenza dei valori arrotondati, .5 attorno al numero intero uniforme. Questo metodo restituisce un 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 ***

Supponendo che il value sia double , puoi fare:

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

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


Tieni presente che String.format () e DecimalFormat producono string utilizzando le impostazioni internazionali predefinite. In tal modo possono scrivere il numero formattato con punto o virgola come separatore tra parti intere e decimali. Per assicurarti che la stringa arrotondata sia nel formato, devi utilizzare java.text.NumberFormat in questo modo:

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

Stamperà in locale in inglese (indipendentemente dalle impostazioni locali): 0,99 123,57 123,00

L'esempio è tratto da Farenda: come convertire correttamente il doppio in stringa .


Una soluzione succinta:

   public static double round(double value, int precision) {
      int scale = (int) Math.pow(10, precision);
      return (double) Math.round(value * scale) / scale;
  }

Vedi anche, https://.com/a/22186845/212950 Grazie a jpdymond per aver offerto questo.


In generale, l'arrotondamento viene eseguito mediante ridimensionamento: 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

Se si considera 5 o n numero decimale. Potrebbe essere questa risposta a risolvere il tuo problema.

    double a = 123.00449;
    double roundOff1 = Math.round(a*10000)/10000.00;
    double roundOff2 = Math.round(roundOff1*1000)/1000.00;
    double roundOff = Math.round(roundOff2*100)/100.00;

    System.out.println("result:"+roundOff);

L'output sarà: 123.0 1
questo può essere risolto con loop e funzione ricorsiva.


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

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

ti porterà un BigDecimal . Per ottenere la stringa, basta chiamare il metodo toString toPlainString o il metodo toPlainString 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));
    }




digits