So runden Sie eine Zahl in Java auf n Dezimalstellen


1259

Was ich möchte, ist eine Methode, um ein Double in eine Zeichenfolge umzuwandeln, die mit der Half-Up-Methode rundet. Wenn also die zu rundende Dezimalstelle 5 ist, wird immer auf die nächste Zahl gerundet. Dies ist die Standardmethode zum Runden, die die meisten Menschen in den meisten Situationen erwarten.

Ich möchte auch, dass nur signifikante Ziffern angezeigt werden - dh es sollten keine nachgestellten Nullen vorhanden sein.

Ich weiß, dass eine Methode, dies zu tun, darin besteht, die String.formatMethode zu verwenden:

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

kehrt zurück:

0.91239

Das ist großartig, zeigt jedoch immer Zahlen mit 5 Dezimalstellen an, auch wenn sie nicht signifikant sind:

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

kehrt zurück:

0.91230

Eine andere Methode ist die Verwendung von DecimalFormatter:

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

kehrt zurück:

0.91238

Wie Sie jedoch sehen können, wird eine halbgleichmäßige Rundung verwendet. Das heißt, es wird abgerundet, wenn die vorherige Ziffer gerade ist. Was ich möchte, ist Folgendes:

0.912385 -> 0.91239
0.912300 -> 0.9123

Was ist der beste Weg, um dies in Java zu erreichen?

Antworten:


751

Verwenden Sie setRoundingMode, legen Sie die RoundingModeexplizite Einstellung fest , um Ihr Problem mit der halben geraden Runde zu behandeln, und verwenden Sie dann das Formatmuster für die gewünschte Ausgabe.

Beispiel:

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

gibt die Ausgabe:

12
123.1235
0.23
0.1
2341234.2125

BEARBEITEN : Die ursprüngliche Antwort bezieht sich nicht auf die Genauigkeit der Doppelwerte. Das ist in Ordnung, wenn es Ihnen egal ist, ob es auf- oder abrundet. Wenn Sie jedoch eine genaue Rundung wünschen, müssen Sie die erwartete Genauigkeit der Werte berücksichtigen. Gleitkommawerte haben intern eine binäre Darstellung. Das bedeutet, dass ein Wert wie 2.7735 intern nicht genau diesen Wert hat. Es kann etwas größer oder etwas kleiner sein. Wenn der interne Wert etwas kleiner ist, wird er nicht auf 2,7740 aufgerundet. Um diese Situation zu beheben, müssen Sie sich der Genauigkeit der Werte bewusst sein, mit denen Sie arbeiten, und diesen Wert vor dem Runden addieren oder subtrahieren. Wenn Sie beispielsweise wissen, dass Ihre Werte bis zu 6 Stellen genau sind, addieren Sie diese Genauigkeit zum Wert, um die Werte auf halber Strecke aufzurunden:

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

Subtrahieren Sie zum Abrunden die Genauigkeit.


9
Dies ist wahrscheinlich die beste Lösung, die bisher vorgestellt wurde. Der Grund, warum ich diese Funktion beim ersten Blick auf die DecimalFormat-Klasse nicht erkannt habe, ist, dass sie nur in Java 1.6 eingeführt wurde. Leider kann ich nur 1.5 verwenden, aber es wird nützlich sein, dies für die Zukunft zu wissen.
Alex Spurling

1
Ich habe es versucht mit : "#.##", Rundung HALF_UP. 256.335f-> "256.33"... (Beispiel stammt aus Kommentaren zur Antwort von @ asterite).
Bigstones

6
Bitte seien Sie vorsichtig, da DecimalFormat von Ihrer aktuellen lokalen Konfiguration abhängt. Möglicherweise erhalten Sie keinen Punkt als Trennzeichen. Ich persönlich bevorzuge die Antwort von Asterite unten
Gomino

1
Beachten Sie auch, dass Sie nicht erwarten sollten, dass DecimalFormat threadsicher ist. Wie pro Java - Dokumentation : Dezimalformate synchronisiert sind im Allgemeinen nicht. Es wird empfohlen, für jeden Thread separate Formatinstanzen zu erstellen. Wenn mehrere Threads gleichzeitig auf ein Format zugreifen, muss es extern synchronisiert werden.
CGK

1
Wie mache ich es so, dass es eine richtige Rundung macht, damit es nicht 0,0004 auf 0,001

471

Unter der Annahme , valueein double, können Sie tun:

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

Das entspricht einer Genauigkeit von 5 Stellen. Die Anzahl der Nullen gibt die Anzahl der Dezimalstellen an.


71
UPDATE: Ich habe gerade bestätigt, dass dies viel schneller ist als die Verwendung von DecimalFormat. Ich habe 200 Mal mit DecimalFormat und dieser Methode eine Schleife durchgeführt. DecimalFormat benötigte 14 ms, um die 200 Schleifen abzuschließen. Diese Methode dauerte weniger als 1 ms. Wie ich vermutet habe, geht das schneller. Wenn Sie durch den Taktzyklus bezahlt werden, sollten Sie dies tun. Ich bin überrascht, dass Chris Cudmore sogar sagen würde, was er gesagt hat, um ehrlich zu sein. Das Zuweisen von Objekten ist immer teurer als das Umwandeln von Grundelementen und die Verwendung statischer Methoden (Math.round () im Gegensatz zu decimalFormat.format ()).
Andi Jay

99
Diese Technik schlägt in über 90% der Fälle fehl. -1.
Marquis von Lorne

25
Dies schlägt in der Tat fehl : Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck

54
Seien Sie sehr vorsichtig, wenn Sie diese Methode (oder eine Rundung von Gleitkommawerten) verwenden. Es scheitert an etwas so Einfachem wie 265.335. Das Zwischenergebnis von 265.335 * 100 (Genauigkeit von 2 Ziffern) ist 26533.499999999996. Dies bedeutet, dass es auf 265,33 abgerundet wird. Es gibt einfach inhärente Probleme bei der Konvertierung von Gleitkommazahlen in reelle Dezimalzahlen. Siehe EJPs Antwort hier unter stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek

6
@SebastiaanvandenBroek: Wow, ich wusste nie, dass es so einfach ist, eine falsche Antwort zu bekommen. Wenn man jedoch mit nicht exakten Zahlen arbeitet, muss man erkennen, dass ein Wert nicht exakt ist . 265.335bedeutet wirklich 265.335 += tolerance, wo die Toleranz von früheren Operationen und dem Bereich der Eingabewerte abhängt. Wir kennen den wahren, genauen Wert nicht. Bei den Kantenwerten ist jede Antwort wohl richtig. Wenn wir genau sein müssen, sollten wir nicht doppelt arbeiten. Das failhier ist nicht in der Umstellung auf Double. Es ist im OP-Denken, dass er sich darauf verlassen kann 265.335, dass der Eingang genau das ist.
ToolmakerSteve

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

bekommst du ein BigDecimal. Um die Zeichenfolge aus ihm heraus, rufen Sie einfach , dass BigDecimal‚s toStringMethode oder das toPlainStringVerfahren für Java 5+ für ein einfaches Format - String.

Beispielprogramm:

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
Das ist meine bevorzugte Lösung. Noch kürzer: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) ruft tatsächlich Double.toString () unter der Haube auf;)
Etienne Neveu

4
Nett. Schneiden Sie keine Ecken und verwenden new BigDecimal(doubleVar)Sie sie, da dies zu Problemen mit der Rundung von Gleitkommazahlen führen kann
Edd

8
@Edd, interessanterweise tritt das Rundungsproblem in dem Fall auf, in dem SebastiaanvandenBroek die Antwort von Asterite kommentiert. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, aber (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
ToolmakerSteve

7
@ToolmakerSteve Dies liegt daran, dass bei Verwendung von new BigDecimal mit dem double der double-Wert direkt verwendet wird und versucht wird, daraus den BigDecimal zu erstellen, während bei Verwendung von BigDecimal.valueOf oder dem tostring-Formular dieser vor der Konvertierung zuerst in einen String (eine genauere Darstellung) analysiert wird .
MetroidFan2002

116

Sie können auch die verwenden

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

um sicherzustellen, dass Sie die nachgestellten Nullen haben.


25
Ich glaube , eines der Ziele der Frage war , dass „es sollte nicht alle nachgestellten Nullen sein“.
Lunchbox

7
Für diese Frage wollte die Operation keine Nullen, aber genau das wollte ich. Wenn Sie eine Liste von Zahlen mit 3 Dezimalstellen haben, möchten Sie, dass alle die gleichen Ziffern haben, auch wenn es 0 ist.
Tom Kincaid

Sie haben vergessen anzugebenRoundingMode.
IgorGanapolsky

1
@IgorGanapolsky Decimal modeverwendet standardmäßigRoundingMode.HALF_EVEN.
EndermanAPM

87

Wie einige andere angemerkt haben, ist die richtige Antwort, entweder DecimalFormatoder zu verwenden BigDecimal. Floating-Point nicht hat Dezimalstellen so dass Sie nicht möglicherweise Aufrunden / Abrunden auf eine bestimmte Anzahl von ihnen an erster Stelle. Sie müssen in einem Dezimalradix arbeiten, und genau das tun diese beiden Klassen.

Ich poste den folgenden Code als Gegenbeispiel zu allen Antworten in diesem Thread und in der Tat überall in StackOverflow (und anderswo), die eine Multiplikation gefolgt von einer Kürzung gefolgt von einer Division empfehlen. Befürworter dieser Technik müssen erklären, warum der folgende Code in über 92% der Fälle die falsche Ausgabe erzeugt.

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

Ausgabe dieses Programms:

10001 trials 9251 errors

BEARBEITEN: Um einige Kommentare unten anzusprechen, habe ich den Modulteil der Testschleife mit BigDecimalund new MathContext(16)für die Moduloperation wie folgt überarbeitet :

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

Ergebnis:

10001 trials 4401 errors

8
Der Trick ist, dass bei allen 9251-Fehlern das Druckergebnis immer noch korrekt ist.
Didier L

7
@DidierL Es überrascht mich nicht. Ich hatte das große Glück, 'Numerical Methods' als meinen allerersten Computerkurs zu absolvieren und gleich zu Beginn eingeführt zu werden, was Gleitkomma kann und was nicht. Die meisten Programmierer sind ziemlich vage.
Marquis von Lorne

15
Alles, was Sie tun, ist zu widerlegen, dass Floating nicht viele Dezimalwerte genau darstellt, was ich hoffe, dass wir alle verstehen. Nicht dass Rundungen ein Problem verursachen. Wie Sie zugeben, werden die Zahlen weiterhin wie erwartet gedruckt.
Peter Lawrey

8
Ihr Test ist fehlerhaft, nehmen Sie round () und der Test schlägt 94% der Zeit fehl. ideone.com/1y62CY druckt 100 trials 94 errorsSie sollten mit einem Test beginnen, der erfolgreich ist, und zeigen, dass das Einführen von Rundungen den Test unterbricht .
Peter Lawrey

6
Widerlegung, hier widerlegt. Verwenden von Math.round für diesen Bereich doubleals keine Fehler ideone.com/BVCHh3
Peter Lawrey

83

Angenommen, Sie haben

double d = 9232.129394d;

Sie können verwenden BigDecimal

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

oder ohne BigDecimal

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

mit beiden Lösungen d == 9232.13


2
Ich denke, dies ist die beste Lösung für Java 1.5-Benutzer (und darunter). Ein Kommentar: Verwenden Sie nicht den Rundungsmodus HALF_EVEN, da er ein unterschiedliches Verhalten für ungerade und gerade Zahlen aufweist (z. B. 2,5 Runden auf 2, 5,5 Runden auf 6), es sei denn, Sie möchten dies.
IcedDante

4
Die erste Lösung ist richtig: Die zweite funktioniert nicht. Siehe hier für den Beweis.
Marquis von Lorne

1
@EJP: Schon die erste Lösung mit RoundingMode.HALF_UPist falsch. Probieren Sie es mit 1.505. Der richtige Weg ist zu verwenden BigDecimal.valueOf(d).
Matthias Braun

Matthias Braun, die Lösung ist in Ordnung, daher 31 Ups. 1,505 Dezimalstellen werden im Gleitkomma-Doppel als 1,50499998 gespeichert. Wenn Sie 1,505 nehmen und von Doppel- in Dezimalzahlen konvertieren möchten, müssen Sie diese zuerst in Double.toString (x) konvertieren Setzen Sie es dann in ein BigDecimal (), aber das ist extrem langsam und vereitelt den Zweck, Double für Geschwindigkeit zu verwenden.
Hamish

1
Lief eine Schleife von 100k mit BigDecimal (dauerte 225 ms) und Math.round (2 ms) und hier ist das Timing ... Zeitaufwand: 225 Millisekunden für die Konvertierung mit: 9232.13 Zeitaufwand: 2 Millisekunden für die Konvertierung in : 9232.13 techiesinfo.com
user1114134

59

Sie können die DecimalFormat-Klasse verwenden.

double d = 3.76628729;

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

Gibt es einen Grund, warum Double.valueOf()gewählt wurde Double.parseDouble()? Die valueOf()Methode gibt ein DoubleObjekt zurück, während parseDouble()ein doubleGrundelement zurückgegeben wird. Mit der Art und Weise, wie der aktuelle Code geschrieben wird, wenden Sie auch das automatische Entpacken auf die Rückgabe an, um ihn in das von Ihrer twoDoubleVariablen erwartete Grundelement umzuwandeln, eine zusätzliche Bytecode-Operation. Ich würde die Antwort ändern, um sie parseDouble()stattdessen zu verwenden .
Ecbrodie

Double.parseDouble()braucht StringInput.
Ryde

38

In der Java- Anleitung von Real wird diese Lösung veröffentlicht, die auch für Versionen vor Java 1.6 kompatibel ist.

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
Ja, genau das macht math.round für positive Zahlen, aber haben Sie dies mit negativen Zahlen versucht? In den anderen Lösungen verwenden die Leute math.round, um auch den Fall negativer Zahlen abzudecken.
Hamish

Anmerkung: Math.floor(x + 0.5)undMath.round(x)
Peter Lawrey

30

@Milhous: Das Dezimalformat für die Rundung ist ausgezeichnet:

Sie können auch die verwenden

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

um sicherzustellen, dass Sie die nachgestellten Nullen haben.

Ich würde hinzufügen, dass diese Methode sehr gut darin ist, einen tatsächlichen numerischen Rundungsmechanismus bereitzustellen - nicht nur visuell, sondern auch bei der Verarbeitung.

Hypothetisch: Sie müssen einen Rundungsmechanismus in ein GUI-Programm implementieren. Um die Genauigkeit / Präzision einer Ergebnisausgabe zu ändern, ändern Sie einfach das Caret-Format (dh innerhalb der Klammern). Damit:

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

würde als Ausgabe zurückkehren: 0.912385

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

würde als Ausgabe zurückkehren: 0.91239

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

würde als Ausgabe zurückkehren: 0.9124

[BEARBEITEN: Auch wenn das Caret-Format so ist ("# 0. #############") und Sie eine Dezimalzahl eingeben, z. B. 3.1415926, erzeugt DecimalFormat aus Gründen des Arguments keinen Müll ( zB nachgestellte Nullen) und wird zurückgegeben: 3.1415926.. wenn Sie so geneigt sind. Zugegeben, es ist ein wenig ausführlich für den Geschmack einiger Entwickler - aber hey, es hat einen geringen Speicherbedarf während der Verarbeitung und ist sehr einfach zu implementieren.]

Das Schöne an DecimalFormat ist also, dass es gleichzeitig das Erscheinungsbild der Zeichenfolge sowie die eingestellte Rundungsgenauigkeit behandelt. Ergo: Sie erhalten zwei Vorteile zum Preis einer Code-Implementierung. ;)


2
Wenn Sie wirklich Dezimalzahlen für die Berechnung (und nicht nur für die Ausgabe) möchten, verwenden Sie kein binärbasiertes Gleitkommaformat wie double. Verwenden Sie BigDecimal oder ein anderes dezimalbasiertes Format.
Paŭlo Ebermann

20

Hier ist eine Zusammenfassung dessen, was Sie verwenden können, wenn Sie das Ergebnis als String erhalten möchten:

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

Hier ist ein Vorschlag, welche Bibliotheken Sie verwenden können, wenn Sie dies möchten double. Ich würde es jedoch nicht für die Zeichenfolgenkonvertierung empfehlen, da double möglicherweise nicht genau das darstellen kann, was Sie möchten (siehe z. B. hier ):

  1. Präzision von Apache Commons Math

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

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

    double rounded = Utils.roundDouble(0.912385, 5)

18

Sie können die folgende Dienstprogrammmethode verwenden:

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

@ Mariololpantunes: Es wird fehlschlagen. Versuchen Sie dies: round(1.005,2);oderround(0.50594724957626620092, 20);
Matthias Braun

Es klappt. Aber uninformativ schweben und verdoppeln sind Annäherungen. Betrachten wir Ihr erstes Beispiel. Wenn Sie die Ausgabe von interessiertenInZeroDPs vor Math.round drucken, wird 100.49999999999999 gedruckt. Sie haben die Präzision als solche verloren. Math.round um sie herum als 100. Aufgrund der Natur oder der Floats und Doubles gibt es Grenzfälle, in denen es nicht richtig funktioniert (weitere Informationen hier en.wikipedia.org/wiki/Floating_point#Accuracy_problems )
mariolpantunes

doppelt ist schnell! Dezimal ist langsam. Computer machen sich nicht die Mühe, ihr Denken in Dezimalschreibweise zu verarbeiten. Sie müssen auf eine gewisse Dezimalgenauigkeit verzichten, um das Gleitkomma doppelt so schnell zu halten.
Hamish

@hamish Die Frage ist nach Präzision, nicht nach Geschwindigkeit.
Marquis von Lorne



7

Versuchen Sie Folgendes: org.apache.commons.math3.util.Precision.round (double x, int scale)

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

Die Homepage der Apache Commons Mathematics Library lautet: http://commons.apache.org/proper/commons-math/index.html

Die interne Implementierung dieser Methode lautet:

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

Da ich zu diesem Thema keine vollständige Antwort gefunden habe, habe ich eine Klasse zusammengestellt, die dies richtig handhaben sollte, mit Unterstützung für:

  • Formatierung : Formatieren Sie einfach eine doppelte Zeichenfolge mit einer bestimmten Anzahl von Dezimalstellen
  • Parsing : Analysiert den formatierten Wert zurück auf double
  • Gebietsschema : Formatieren und analysieren Sie das Standardgebietsschema
  • Exponentialnotation : Beginnen Sie nach einem bestimmten Schwellenwert mit der Verwendung der Exponentialnotation

Die Verwendung ist ziemlich einfach :

(Für dieses Beispiel verwende ich ein benutzerdefiniertes Gebietsschema.)

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

Hier ist die Klasse :

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

Wenn Sie wirklich Dezimalzahlen für die Berechnung (und nicht nur für die Ausgabe) möchten, verwenden Sie kein binärbasiertes Gleitkommaformat wie double.

Use BigDecimal or any other decimal-based format.

Ich verwende BigDecimal für Berechnungen, aber denke daran, dass es von der Größe der Zahlen abhängt, mit denen du es zu tun hast. In den meisten meiner Implementierungen ist das Parsen von Double oder Integer nach Long für Berechnungen mit sehr großen Zahlen ausreichend.

Tatsächlich habe ich kürzlich Parsed-to-Long verwendet, um genaue Darstellungen (im Gegensatz zu Hex-Ergebnissen) in einer GUI für Zahlen zu erhalten, die so groß sind wie ################### ################ Zeichen (als Beispiel).


6

Um dies zu erreichen, können wir diesen Formatierer verwenden:

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

oder:

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

Verwenden Sie diese Methode, um immer zwei Dezimalstellen zu erhalten:

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

Definieren dieser Werte:

91.32
5.22
11.5
1.2
2.6

Mit der Methode können wir diese Ergebnisse erhalten:

91.32
5.22
11.50
1.20
2.60

Demo online.


5

Nur für den Fall, dass noch jemand Hilfe benötigt. Diese Lösung funktioniert perfekt für mich.

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

}

gibt a String mit der gewünschten Ausgabe zurück.


Bitte geben Sie Ihren Grund für die Ablehnung im Kommentar an, andernfalls nennen wir das Einschüchterung.
Asher. M. O

4

Ich stimme der gewählten Antwort zu DecimalFormat--- oder alternativBigDecimal .

Bitte lesen Sie zuerst das Update unten!

Wenn Sie jedoch den doppelten Wert runden und ein doubleWertergebnis erhalten möchten , können Sie ihn org.apache.commons.math3.util.Precision.round(..)wie oben erwähnt verwenden. Die Implementierung verwendet BigDecimal, ist langsam und erzeugt Müll.

Eine ähnliche, aber schnelle und DoubleRoundermüllfreie Methode bietet das Dienstprogramm in der decimal4j-Bibliothek:

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

Wird ausgegeben

 0.667
 0.666
 1000.0
 9.00800700601E10

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

Haftungsausschluss: Ich bin am decimal4j-Projekt beteiligt.

Update: Wie @iaforek hervorhob, gibt DoubleRounder manchmal kontraintuitive Ergebnisse zurück. Der Grund ist, dass es eine mathematisch korrekte Rundung durchführt. Zum BeispielDoubleRounder.round(256.025d, 2) wird auf 256,02 abgerundet, weil der als 256,025d dargestellte Doppelwert etwas kleiner als der rationale Wert 256,025 ist und daher abgerundet wird.

Anmerkungen:

  • Dieses Verhalten ist dem des BigDecimal(double)Konstruktors sehr ähnlich (verwendet jedoch nicht valueOf(double)den String-Konstruktor).
  • Das Problem kann zuerst mit einem doppelten Rundungsschritt mit höherer Präzision umgangen werden, aber es ist kompliziert und ich gehe hier nicht auf die Details ein

Aus diesen Gründen und allem, was oben in diesem Beitrag erwähnt wurde, kann ich die Verwendung von DoubleRounder nicht empfehlen .


Haben Sie Kennzahlen, die zeigen, wie effizient Ihre Lösung im Vergleich zu den anderen ist?
iaforek

Ich habe es nicht mit anderen Lösungen verglichen, aber im Quellcode ist ein jmh-Benchmark verfügbar: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… Ich habe den Benchmark auf einer VM ausgeführt Die Ergebnisse sind als CSV-Datei hier verfügbar: github.com/tools4j/decimal4j/wiki/Performance
marco

1
DoubleRounder schlägt in folgenden Fällen fehl: DoubleRounder.round (256.025d, 2) - erwartet: 256.03, aktuell: 256.02 oder für DoubleRounder.round (260.775d, 2) - erwartet: 260.78, aktuell: 260.77.
iaforek

@iaforek: Dies ist korrekt, da DoubleRounder eine mathematisch korrekte Rundung durchführt. Ich gebe jedoch zu, dass dies etwas nicht intuitiv ist und daher meine Antwort entsprechend aktualisieren wird.
Marco

3

Das folgende Codefragment zeigt, wie n Ziffern angezeigt werden. Der Trick besteht darin, die Variable pp auf 1 zu setzen, gefolgt von n Nullen. Im folgenden Beispiel hat der variable pp-Wert 5 Nullen, sodass 5 Ziffern angezeigt werden.

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

Wenn Sie DecimalFormatzum Konvertieren verwenden double, Stringist dies sehr einfach:

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

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

RoundingModeAbhängig vom gewünschten Verhalten stehen mehrere Aufzählungswerte zur Auswahl.


3

Ich kam hierher und wollte nur eine einfache Antwort, wie man eine Zahl rundet. Dies ist eine ergänzende Antwort, um dies zu gewährleisten.

So runden Sie eine Zahl in Java

Der häufigste Fall ist zu verwenden Math.round().

Math.round(3.7) // 4

Zahlen werden auf die nächste ganze Zahl gerundet. Ein .5Wert wird aufgerundet. Wenn Sie ein anderes Rundungsverhalten benötigen, können Sie eine der anderen mathematischen Funktionen verwenden. Siehe den Vergleich unten.

runden

Wie oben angegeben, rundet dies auf die nächste ganze Zahl. .5Dezimalstellen runden ab. Diese Methode gibt eine zurück 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

Decke

Jeder Dezimalwert wird auf die nächste Ganzzahl aufgerundet. Es geht zur Decke . Diese Methode gibt a zurück 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

Fußboden

Jeder Dezimalwert wird auf die nächste Ganzzahl abgerundet. Diese Methode gibt a zurück 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

Dies ähnelt dem Runden in diesen Dezimalwerten, die auf die nächste Ganzzahl gerundet werden. Doch im Gegensatz zu round, .5runden Werte an die gerade ganze Zahl. Diese Methode gibt a zurück 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
Sie lösen nur den speziellen Fall der Rundung auf 0 Dezimalstellen. Die ursprüngliche Frage ist allgemeiner.
lukas84

3

Wenn Sie eine Technologie mit einem minimalen JDK verwenden. Hier ist ein Weg ohne Java-Bibliotheken:

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

Dies würde in Fällen fehlschlagen, in denen myVal nicht kleiner als 1 ist und Nullen nach Dezimalstellen über dem Skalierungswert liegen. Angenommen, Sie haben myVal = 9.00000000912385; Das obige wird 9.0 zurückgeben. Ich denke, wir sollten eine Lösung anbieten, die in allen Fällen von myVal funktioniert. Nicht speziell für den von Ihnen angegebenen Wert.
Tavalendo

@ user102859 In Ihrem Beispiel ist 9.0 das richtige Ergebnis. Ich verstehe nicht, wie das scheitern würde.
Craigo

2

DecimalFormat ist die beste Art der Ausgabe, aber ich bevorzuge es nicht. Ich mache das immer die ganze Zeit, weil es den doppelten Wert zurückgibt. Ich kann es also mehr als nur ausgeben.

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

ODER

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

Wenn Sie einen großen Dezimalstellenwert benötigen, können Sie stattdessen BigDecimal verwenden. Sowieso .0ist wichtig. Ohne sie ist die Rundung von 0,33333d5 0,33333 und nur 9 Stellen zulässig. Die zweite Funktion ohne .0Probleme mit 0.30000 return 0.30000000000000004.


2

Hier ist meine Antwort:

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

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

1

Nachdem ich die meisten Antworten gelesen hatte, stellte ich fest, dass die meisten nicht präzise sind. Tatsächlich BigDecimalscheint die Verwendung die beste Wahl zu sein. Wenn Sie jedoch nicht verstehen, wie das RoundingModefunktioniert, verlieren Sie unvermeidlich an Präzision. Ich habe das herausgefunden, als ich mit großen Zahlen in einem Projekt gearbeitet habe, und dachte, es könnte anderen helfen, Probleme beim Runden von Zahlen zu haben. Zum Beispiel.

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

Sie würden erwarten, 1363.28als Ausgabe zu erhalten, aber Sie werden am Ende mit 1363.27, was nicht erwartet wird, wenn Sie nicht wissen, was das RoundingModetut. Wenn Sie sich also die Oracle-Dokumente ansehen , finden Sie die folgende Beschreibung für RoundingMode.HALF_UP.

Rundungsmodus zum Runden in Richtung "nächster Nachbar", es sei denn, beide Nachbarn sind gleich weit entfernt. In diesem Fall wird aufgerundet.

Als wir das wussten, stellten wir fest, dass wir keine exakte Rundung erhalten, es sei denn, wir möchten in Richtung des nächsten Nachbarn runden . Um eine angemessene Runde zu erreichen, müssten wir also von der n-1Dezimalstelle zu den gewünschten Dezimalstellen schleifen . Zum Beispiel.

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

Dies wird uns am Ende den erwarteten Output geben, der sein würde 1363.28.


0

Wobei dp = Dezimalstelle ist und der Wert ein Doppel ist.

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

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

1
Produziert 1.0für value = 1.005und dp = 2. Verwenden Sie dies stattdessen.
Matthias Braun

es ist ok Matt, dein Beispiel ist ungültig. weil 1.005 ohnehin nicht im Gleitkommadoppel dargestellt werden kann. Es muss wirklich über oder unter 1,005 gespeichert werden, dh es wird beim Kompilieren doppelt gespeichert: 1.0049998 (es wird in Ihrem kompilierten Code nicht so dezimal gespeichert, wie Sie es von den Lesern glauben lassen würden) Ziel ist korrekt, er speichert Werte als Gleitkomma doppelt, wo Randfälle wie Ihre sowieso unbedeutend sind. Wenn dies der Fall wäre, würden Sie 3dp verwenden, es dann in eine Dezimalzahl konvertieren und dann eine Dezimalrundenfunktion ausführen, genau wie der Link, den Sie gepostet haben.
Hamish

1
@hamish Ich sehe nicht, wo Matthias die Leser glauben lassen würde, dass der Wert als Dezimalzahl kompiliert wird. Stecke keine Worte in den Mund anderer Leute.
Marquis von Lorne

0

Beachten Sie, dass String.format () und DecimalFormat Zeichenfolgen mit dem Standardgebietsschema erstellen. Sie können also eine formatierte Zahl mit Punkt oder Komma als Trennzeichen zwischen Ganzzahl- und Dezimalteilen schreiben. Um sicherzustellen, dass der gerundete String das gewünschte Format hat, verwenden Sie java.text.NumberFormat wie folgt:

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

Wird in englischer Sprache gedruckt (unabhängig von Ihrer Ländereinstellung): 0,99 123,57 123,00

Das Beispiel stammt von Farenda - wie man Double korrekt in String konvertiert .


0

Im Allgemeinen erfolgt die Rundung durch Skalierung: 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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.