Jak zaokrąglić liczbę do n miejsc dziesiętnych w Javie

1259

To, co chciałbym, to metoda konwersji podwójnego na ciąg znaków, który zaokrągla metodę pół-w górę - tj. Jeśli liczba dziesiętna do zaokrąglenia wynosi 5, zawsze zaokrągla w górę do następnej liczby. Jest to standardowa metoda zaokrąglania, której większość ludzi oczekuje w większości sytuacji.

Chciałbym również, aby wyświetlane były tylko cyfry znaczące - tzn. Nie powinny być żadnych zer końcowych.

Wiem, że jedną z metod jest zrobienie tego String.format:

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

zwroty:

0.91239

co jest świetne, jednak zawsze wyświetla liczby z 5 miejscami dziesiętnymi, nawet jeśli nie są znaczące:

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

zwroty:

0.91230

Inną metodą jest użycie DecimalFormatter:

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

zwroty:

0.91238

Jednak, jak widać, wykorzystuje to półokrągłe zaokrąglanie. Oznacza to, że zaokrągli w dół, jeśli poprzednia cyfra jest parzysta. Chciałbym to:

0.912385 -> 0.91239
0.912300 -> 0.9123

Jaki jest najlepszy sposób na osiągnięcie tego w Javie?

Alex Spurling
źródło

Odpowiedzi:

751

Użyj setRoundingMode, ustaw RoundingModejawnie, aby poradzić sobie z problemem z półokrągłą rundą, a następnie użyj wzorca formatu dla wymaganych wyników.

Przykład:

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

daje wynik:

12
123.1235
0.23
0.1
2341234.2125

EDYCJA : Oryginalna odpowiedź nie dotyczy dokładności podwójnych wartości. To dobrze, jeśli nie obchodzi cię, czy zaokrągla w górę czy w dół. Ale jeśli chcesz dokładnego zaokrąglenia, musisz wziąć pod uwagę oczekiwaną dokładność wartości. Wartości zmiennoprzecinkowe mają wewnętrzną reprezentację binarną. Oznacza to, że wartość taka jak 2.7735 tak naprawdę nie ma tej dokładnej wartości wewnętrznie. Może być nieco większy lub nieco mniejszy. Jeśli wartość wewnętrzna jest nieco mniejsza, nie zaokrągli w górę do 2,7740. Aby zaradzić tej sytuacji, musisz być świadomy dokładności wartości, z którymi pracujesz, i dodać lub odjąć tę wartość przed zaokrągleniem. Na przykład, jeśli wiesz, że twoje wartości są dokładne do 6 cyfr, a następnie zaokrąglając wartości w połowie, dodaj tę dokładność do wartości:

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

Aby zaokrąglić w dół, odejmij dokładność.

Curtisk
źródło
9
Jest to prawdopodobnie najlepsze z dotychczas zaprezentowanych rozwiązań. Powodem, dla którego nie zauważyłem tego obiektu, kiedy po raz pierwszy spojrzałem na klasę DecimalFormat, jest to, że został on wprowadzony tylko w Javie 1.6. Niestety jestem ograniczony do używania 1.5, ale warto wiedzieć w przyszłości.
Alex Spurling
1
Próbowałem tego z "#.##":, zaokrąglaniem HALF_UP. 256.335f-> "256.33"... (przykład pochodzi z komentarzy do odpowiedzi @ asterite).
duże kamienie
6
Zachowaj ostrożność, ponieważ format DecimalFormat zależy od bieżącej konfiguracji lokalnej, ponieważ możesz nie otrzymać kropki jako separatora. Osobiście wolę odpowiedź
Asterite
1
Należy również pamiętać, że nie należy oczekiwać, że DecimalFormat będzie bezpieczny dla wątków. Zgodnie z dokumentacją Java : formaty dziesiętne zazwyczaj nie są synchronizowane. Zaleca się utworzenie osobnych instancji formatu dla każdego wątku. Jeśli wiele wątków jednocześnie uzyskuje dostęp do formatu, należy go zsynchronizować zewnętrznie.
CGK,
1
jak to zrobić, aby
471

Zakładając, że valueto doublemożna zrobić:

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

To z dokładnością do 5 cyfr. Liczba zer wskazuje liczbę miejsc po przecinku.

asterit
źródło
71
AKTUALIZACJA: Właśnie potwierdziłem, że robienie tego JEST WOLNIE szybciej niż przy użyciu DecimalFormat. Zapętlałem za pomocą DecimalFormat 200 razy i tę metodę. DecimalFormat zajęło 14 ms, aby ukończyć 200 pętli, ta metoda zajęła mniej niż 1 ms. Jak podejrzewałem, jest to szybsze. Jeśli otrzymujesz wynagrodzenie za cykl zegarowy, powinieneś to zrobić. Dziwi mnie, że Chris Cudmore powiedziałby nawet to, co powiedział, mówiąc szczerze. przydzielanie obiektów jest zawsze droższe niż rzutowanie prymitywów i używanie metod statycznych (Math.round () w przeciwieństwie do decimalFormat.format ()).
Andi Jay,
99
Ta technika zawodzi w ponad 90% przypadków. -1.
user207421,
25
Rzeczywiście, to się nie powiedzie: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck
54
Zachowaj ostrożność podczas korzystania z tej metody (lub zaokrąglania punktów zmiennoprzecinkowych). Nie działa w przypadku czegoś tak prostego jak 265.335. Wynik pośredni liczby 265,335 * 100 (dokładność 2 cyfr) to 26533.499999999996. Oznacza to, że zostanie zaokrąglony w dół do 265,33. Po prostu istnieją nieodłączne problemy przy konwersji liczb zmiennoprzecinkowych na rzeczywiste liczby dziesiętne. Zobacz odpowiedź EJP tutaj na stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek
6
@SebastiaanvandenBroek: Wow, nigdy nie wiedziałem, że tak łatwo było uzyskać złą odpowiedź. Jeśli jednak pracujesz z nieprecyzyjnymi liczbami, musisz uznać, że żadna wartość nie jest dokładna . 265.335naprawdę oznacza 265.335 += tolerance, gdzie tolerancja zależy od poprzednich operacji i zakresu wartości wejściowych. Nie znamy prawdziwej, dokładnej wartości. Przy wartościach krańcowych każda odpowiedź jest prawdopodobnie poprawna. Jeśli musimy być dokładni, nie powinniśmy pracować podwójnie. failTutaj nie jest w Nawrócenie podwójnie. W OP uważa, że ​​może polegać na przychodzącym 265.335jako właśnie takim.
ToolmakerSteve
191
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);

dostaniesz BigDecimal. Aby uzyskać ciąg z niego, wystarczy zadzwonić, że BigDecimal„s toStringmetodę lub toPlainStringmetody Java 5+ dla zwykłego formatu ciąg.

Przykładowy program:

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));
    }
MetroidFan2002
źródło
38
To moje preferowane rozwiązanie. Jeszcze krótszy: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) faktycznie wywołuje Double.toString () pod maską;)
Etienne Neveu
4
Miły. Nie tnij zakrętów i używaj, new BigDecimal(doubleVar)ponieważ możesz mieć problemy z zaokrąglaniem zmiennoprzecinkowych
Edd
8
@Edd, co ciekawe, problem zaokrąglania występuje w przypadku, gdy SebastiaanvandenBroek wspomina w komentarzu do odpowiedzi asterytu. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, ale (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
ToolmakerSteve
7
@ToolmakerSteve To dlatego, że użycie nowego BigDecimal z podwójnym pobiera podwójną wartość bezpośrednio i próbuje użyć jej do utworzenia BigDecimal, podczas gdy podczas korzystania z BigDecimal.valueOf lub formularza tostring najpierw analizuje go na ciąg znaków (dokładniejsza reprezentacja) przed konwersją .
MetroidFan2002
116

Możesz także użyć

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

aby upewnić się, że masz końcowe 0.

Milhous
źródło
25
Wierzę, że jednym z celów pytaniem było to, że „nie powinno nie być żadnych końcowe zera”.
Lunchbox
7
W przypadku tego pytania operacja nie chciała zer, ale właśnie tego chciałam. Jeśli masz listę liczb z 3 miejscami po przecinku, chcesz, aby wszystkie miały te same cyfry, nawet jeśli jest to 0.
Tom Kincaid
Zapomniałeś podaćRoundingMode.
IgorGanapolsky
1
@IgorGanapolsky domyślnie Decimal modeużywaRoundingMode.HALF_EVEN.
EndermanAPM
87

Jak zauważyli inni, prawidłowa odpowiedź jest użycie albo DecimalFormatalbo BigDecimal. Liczba zmiennoprzecinkowa nie ma miejsc dziesiętnych, więc nie można zaokrąglić / obciąć określonej liczby w pierwszej kolejności. Musisz pracować z dokładnością dziesiętną i tak właśnie robią te dwie klasy.

Zamieszczam następujący kod jako kontrprzykład na wszystkie odpowiedzi w tym wątku i rzeczywiście w całym StackOverflow (i gdzie indziej), które zalecają mnożenie, a następnie obcięcie, a następnie dzielenie. Zwolennicy tej techniki muszą wyjaśnić, dlaczego poniższy kod generuje nieprawidłowe dane wyjściowe w ponad 92% przypadków.

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

Wynik tego programu:

10001 trials 9251 errors

EDYCJA: Aby odpowiedzieć na niektóre komentarze poniżej, zmodyfikowałem część modułu pętli testowej za pomocą BigDecimali new MathContext(16)dla operacji modułu w następujący sposób:

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

Wynik:

10001 trials 4401 errors
użytkownik207421
źródło
8
Sztuka polega na tym, że we wszystkich twoich błędach 9251 wydrukowany wynik jest nadal poprawny.
Didier L,
7
@DidierL To mnie nie dziwi. Miałem bardzo dużo szczęścia, że ​​mogłem „Metody numeryczne” jako mój pierwszy kurs komputerowy i od samego początku zapoznałem się z tym, co zmiennoprzecinkowe może, a czego nie może. Większość programistów jest dość niejasna.
user207421,
15
Wszystko, co robisz, to obalanie, że liczba zmiennoprzecinkowa nie reprezentuje dokładnie wielu wartości dziesiętnych, co, mam nadzieję, wszyscy rozumiemy. Nie to, że zaokrąglanie powoduje problem. Jak przyznajesz, liczby wciąż są drukowane zgodnie z oczekiwaniami.
Peter Lawrey
8
Twój test jest zepsuty, wyjmij round (), a test nie powiedzie się w 94% przypadków. ideone.com/1y62CY drukuje 100 trials 94 errorsPowinieneś zacząć od pozytywnego wyniku testu i pokazać, że wprowadzenie zaokrąglania przerywa test.
Peter Lawrey
6
Odrzucenie, tutaj odrzucone. Korzystanie z Math.round dla tego zakresu doublejak bez błędów ideone.com/BVCHh3
Peter Lawrey
83

Załóżmy, że masz

double d = 9232.129394d;

możesz użyć BigDecimal

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

lub bez BigDecimal

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

z obu rozwiązań d == 9232.13

użytkownik593581
źródło
2
Myślę, że to najlepsze rozwiązanie dla użytkowników Java 1.5 (i niższych). Jeden komentarz, nie używaj trybu zaokrąglania HALF_EVEN, ponieważ ma on działanie diff dla liczb nieparzystych i parzystych (na przykład 2,5 zaokrągla do 2, a 5,5 zaokrągla do 6), chyba że tego chcesz.
IcedDante
4
Pierwsze rozwiązanie jest prawidłowe: drugie nie działa. Zobacz tutaj dowód.
user207421,
1
@EJP: Nawet pierwsze rozwiązanie RoundingMode.HALF_UPjest złe. Wypróbuj z 1.505. Właściwym sposobem jest użycie BigDecimal.valueOf(d).
Matthias Braun
Matthias Braun, rozwiązanie jest w porządku, stąd 31 wzlotów. 1.505 miejsca dziesiętnego jest przechowywane w zmiennoprzecinkowym podwójnym jako 1.50499998, jeśli chcesz wziąć 1.505 i przekonwertować z podwójnego na dziesiętny, najpierw musisz przekonwertować go na Double.toString (x) następnie umieść go w BigDecimal (), ale jest to bardzo powolne i pokonuje cel używania podwójnej prędkości w pierwszej kolejności.
hamish
1
Przebiegł pętlę 100 tys. Z BigDecimal (zajęło 225 ms) i Math.round (2 ms), a oto czas ... Czas potrzebny: 225 milionów sekund na konwersję przy użyciu: 9232.13 Czas potrzebny: 2 miliony sekund na konwersję do : 9232.13 techiesinfo.com
user1114134,
59

Możesz użyć klasy DecimalFormat.

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));
JibW
źródło
Czy jest jakiś powód, dla Double.valueOf()którego wybrano Double.parseDouble()? valueOf()Sposób powraca do Doubleprzedmiotu, gdy parseDouble()zwróci doublepierwotnej. Sposób, w jaki napisany jest bieżący kod, powoduje również zastosowanie automatycznego rozpakowywania do powrotu, aby rzutować go na twoDoubleoperację podstawową oczekiwaną przez zmienną, dodatkową operację kodu bajtowego. parseDouble()Zamiast tego zmieniłbym odpowiedź .
ecbrodie
Double.parseDouble()potrzebuje Stringwkładu.
Ryde
38

Real-Java Java How-to publikuje to rozwiązanie, które jest również kompatybilne z wersjami wcześniejszymi niż Java 1.6.

BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
Ovesh
źródło
33
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;
Chris Cudmore
źródło
2
tak, dokładnie to robi matematyka dla liczb dodatnich, ale czy próbowałeś tego z liczbami ujemnymi? ludzie używają math.round w innych rozwiązaniach, aby objąć również liczbę ujemną.
hamish
Uwaga: Math.floor(x + 0.5)orazMath.round(x)
Peter Lawrey
30

@Milhous: format dziesiętny dla zaokrąglania jest doskonały:

Możesz także użyć

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

aby upewnić się, że masz końcowe 0.

Dodałbym, że ta metoda jest bardzo dobra w zapewnianiu rzeczywistego numerycznego, zaokrąglającego mechanizmu - nie tylko wizualnie, ale także podczas przetwarzania.

Hipotetyczny: musisz zaimplementować mechanizm zaokrąglania w programie GUI. Aby zmienić dokładność / precyzję wyniku, wystarczy zmienić format karetki (tzn. W nawiasach kwadratowych). Po to aby:

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

zwróci jako wynik: 0.912385

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

zwróci jako wynik: 0.91239

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

zwróci jako wynik: 0.9124

[EDYCJA: również jeśli format karetki jest podobny („# 0. ############”) i wprowadzasz liczbę dziesiętną, np. 3.1415926, ze względu na argument, DecimalFormat nie generuje żadnych śmieci ( np. końcowe zera) i zwróci: 3.1415926jeśli jesteś w ten sposób skłonny. To prawda, że ​​jest trochę gadatliwy, jeśli chodzi o upodobania niektórych twórców - ale hej, ma on mało pamięci podczas przetwarzania i jest bardzo łatwy do wdrożenia.]

Zasadniczo piękno DecimalFormat polega na tym, że jednocześnie obsługuje on wygląd sznurka - a także poziom dokładności zaokrąglania. Ergo: zyskujesz dwie korzyści w cenie jednej implementacji kodu. ;)

Ivan
źródło
2
Jeśli naprawdę chcesz liczb dziesiętnych do obliczeń (i nie tylko do danych wyjściowych), nie używaj binarnego formatu zmiennoprzecinkowego, takiego jak double. Użyj BigDecimal lub dowolnego innego formatu dziesiętnego.
Paŭlo Ebermann
20

Oto podsumowanie tego, czego możesz użyć, jeśli chcesz uzyskać wynik w postaci ciągu:

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

Oto propozycja bibliotek, których możesz użyć, jeśli chcesz double. Nie polecałbym go jednak do konwersji ciągów, ponieważ double może nie być w stanie dokładnie przedstawić tego, co chcesz (patrz np. Tutaj ):

  1. Precyzja z Apache Commons Math

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
  2. Funkcje od Colta

    double rounded = Functions.round(0.00001).apply(0.912385)
  3. Używa od Weka

    double rounded = Utils.roundDouble(0.912385, 5)
Mifeet
źródło
18

Możesz użyć następującej metody narzędzia

public static double round(double valueToRound, int numberOfDecimalPlaces)
{
    double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
    double interestedInZeroDPs = valueToRound * multipicationFactor;
    return Math.round(interestedInZeroDPs) / multipicationFactor;
}
Amit
źródło
@mariolpantunes: To się nie powiedzie. Spróbuj tego: round(1.005,2);lubround(0.50594724957626620092, 20);
Matthias Braun
To działa. Ale nieinformacyjnie liczba zmiennoprzecinkowa i liczba podwójna są przybliżeniami. Rozważmy twój pierwszy przykład. Jeśli wydrukujesz wyjście zainteresowanychInZeroDPs przed Math.round, wydrukuje 100.49999999999999. Straciłeś precyzję jako taką Math.round zaokrągloną do 100. Ze względu na naturę lub liczba zmiennoprzecinkowa występują przypadki graniczne, gdy nie działa ona poprawnie (więcej informacji tutaj en.wikipedia.org/wiki/Floating_point#Accuracy_problems )
mariolpantunes
podwójne jest szybkie! dziesiętny jest wolny. komputery nie zawracają sobie głowy przetwarzaniem ich myślenia w zapisie dziesiętnym. musisz zrezygnować z pewnej precyzji dziesiętnej, aby utrzymać zmiennoprzecinkowy podwójnie szybki.
hamish
@hamish Pytanie dotyczy precyzji, a nie prędkości.
user207421,
13

Zwięzłe rozwiązanie:

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

Zobacz także https://stackoverflow.com/a/22186845/212950 Dziękujemy jpdymond za zaoferowanie tego.

MAbraham1
źródło
7

Spróbuj tego: org.apache.commons.math3.util.Precision.round (podwójne x, skala int)

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

Strona główna Biblioteki Matematycznej Apache Commons: http://commons.apache.org/proper/commons-math/index.html

Wewnętrzna implementacja tej metody to:

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;
        }
    }
}
Li Ying
źródło
7

Ponieważ nie znalazłem pełnej odpowiedzi na ten temat, stworzyłem klasę, która powinna sobie z tym poradzić poprawnie, ze wsparciem dla:

  • Formatowanie : łatwo sformatuj ciąg od podwójnego do łańcucha z określoną liczbą miejsc dziesiętnych
  • Analiza : parsuj sformatowaną wartość z powrotem na podwójną wartość
  • Ustawienia regionalne : sformatuj i przeanalizuj przy użyciu domyślnych ustawień regionalnych
  • Notacja wykładnicza : zacznij używać notacji wykładniczej po pewnym progu

Użycie jest dość proste :

(Na potrzeby tego przykładu używam niestandardowych ustawień regionalnych)

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

Oto klasa :

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

}
Andrei Lupsa
źródło
7

Jeśli naprawdę chcesz liczb dziesiętnych do obliczeń (i nie tylko do danych wyjściowych), nie używaj binarnego formatu zmiennoprzecinkowego, takiego jak double.

Use BigDecimal or any other decimal-based format.

Używam BigDecimal do obliczeń, ale pamiętaj, że zależy to od wielkości liczb, z którymi masz do czynienia. W większości moich implementacji analiza składni z podwójnej lub całkowitej na Long jest wystarczająca do obliczeń bardzo dużej liczby.

W rzeczywistości niedawno użyłem analizy składniowej do uzyskania długiej reprezentacji (w przeciwieństwie do wyników szesnastkowych) w interfejsie GUI dla liczb tak dużych, jak ################## ############### znaków (jako przykład).

Ivan
źródło
6

Aby to osiągnąć, możemy użyć tego formatyzatora:

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

lub:

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

Użyj tej metody, aby uzyskać zawsze dwa miejsca po przecinku:

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

Definiowanie tych wartości:

91.32
5.22
11.5
1.2
2.6

Za pomocą metody możemy uzyskać następujące wyniki:

91.32
5.22
11.50
1.20
2.60

demo online.

Jorgesys
źródło
5

Na wypadek, gdyby ktoś nadal potrzebował pomocy w tym zakresie. To rozwiązanie działa dla mnie idealnie.

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

}

zwraca a String z żądanym wyjściem.

Asher M. O.
źródło
Podaj powód oddania głosu w komentarzu, w przeciwnym razie nazywamy to zastraszaniem.
Asher. M.O
4

Zgadzam się z wybraną odpowiedzią na użycie DecimalFormat--- lub alternatywnieBigDecimal .

Najpierw przeczytaj aktualizację poniżej!

Jeśli jednak nie chcesz zaokrąglić podwójnej wartości i uzyskać doublewynik wartości, można użyć org.apache.commons.math3.util.Precision.round(..)jak wspomniano powyżej. Implementacja używa BigDecimal, jest powolna i powoduje śmieci.

Podobna, ale szybka i pozbawiona śmieci metoda jest udostępniana przez DoubleRoundernarzędzie w bibliotece 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);

Wyjdzie

 0.667
 0.666
 1000.0
 9.00800700601E10

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

Uwaga: Jestem zaangażowany w projekt decimal4j.

Aktualizacja: Jak zauważył @iaforek, DoubleRounder czasami zwraca wyniki sprzeczne z intuicją. Powodem jest to, że dokonuje matematycznie poprawnego zaokrąglania. Na przykładDoubleRounder.round(256.025d, 2) zostanie zaokrąglony w dół do 256.02, ponieważ podwójna wartość reprezentowana jako 256.025d jest nieco mniejsza niż wartość wymierna 256.025, a zatem zostanie zaokrąglona w dół.

Uwagi:

  • To zachowanie jest bardzo podobne do zachowania BigDecimal(double)konstruktora (ale nie do valueOf(double)którego używa konstruktora łańcucha).
  • Problem można obejść, wykonując najpierw podwójny krok zaokrąglania do wyższej precyzji, ale jest to skomplikowane i nie wchodzę w szczegóły

Z tych powodów i wszystkiego wymienionego powyżej w tym poście nie mogę polecić korzystania z DoubleRounder .

marco
źródło
Czy masz wskaźniki wskazujące, jak skuteczne jest twoje rozwiązanie w porównaniu do innych?
iaforek
Nie porównałem go z innymi rozwiązaniami, ale w kodzie źródłowym dostępny jest test porównawczy jmh : github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… Uruchomiłem test porównawczy na maszynie wirtualnej , wyniki są dostępne jako plik csv tutaj: github.com/tools4j/decimal4j/wiki/Performance
marco
1
DoubleRounder kończy się niepowodzeniem w następujących przypadkach: DoubleRounder.round (256.025d, 2) - oczekiwany: 256.03, aktualny: 256.02 lub dla DoubleRounder.round (260.775d, 2) - oczekiwany: 260.78, aktualny: 260.77.
iaforek
@iaforek: jest to poprawne, ponieważ DoubleRounder wykonuje matematycznie poprawne zaokrąglanie. Przyznaję jednak, że jest to nieco sprzeczne z intuicją i dlatego odpowiednio zaktualizuję moją odpowiedź.
marco
3

Poniższy fragment kodu pokazuje, jak wyświetlać n cyfr. Sztuczka polega na ustawieniu zmiennej pp na 1, po której następuje n zer. W poniższym przykładzie zmienna pp ma 5 zer, więc wyświetli się 5 cyfr.

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();
Jasdeep Singh
źródło
3

Jeśli używasz DecimalFormatkonwertować doubledo String, to jest bardzo proste:

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

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

Do wyboru jest kilka RoundingModewartości wyliczania, w zależności od wymaganego zachowania.

Drew Noakes
źródło
3

Przybyłem tu tylko z prostą odpowiedzią, jak zaokrąglić liczbę. To jest dodatkowa odpowiedź na to pytanie.

Jak zaokrąglić liczbę w Javie

Najczęstszym przypadkiem jest użycie Math.round().

Math.round(3.7) // 4

Liczby są zaokrąglane do najbliższej liczby całkowitej. .5Wartość jest zaokrąglone. Jeśli potrzebujesz innego zachowania zaokrąglania niż to, możesz użyć jednej z innych funkcji matematycznych . Zobacz porównanie poniżej.

okrągły

Jak wspomniano powyżej, zaokrągla to do najbliższej liczby całkowitej. .5miejsca po przecinku w górę. Ta metoda zwraca 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

stropować

Każda wartość dziesiętna jest zaokrąglana w górę do następnej liczby całkowitej. Idzie do sufitu . Ta metoda zwraca 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

piętro

Każda wartość dziesiętna jest zaokrąglana w dół do następnej liczby całkowitej. Ta metoda zwraca 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

Jest to podobne do zaokrąglania w tych wartościach dziesiętnych zaokrąglanych do najbliższej liczby całkowitej. Jednak w odróżnieniu od round, .5wartości zaokrąglić do parzystej liczby całkowitej. Ta metoda zwraca 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 ***
Suragch
źródło
1
rozwiązujesz tylko konkretny przypadek zaokrąglenia do 0 miejsc po przecinku. Pierwotne pytanie jest bardziej ogólne.
lukas84
3

Jeśli używasz technologii, która ma minimalny pakiet JDK. Oto sposób bez bibliotek Java:

double scale = 100000;    
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;
Craigo
źródło
Nie udałoby się to w przypadkach, gdy myVal jest nie mniejsze niż 1 i z zerami po przecinku poza wartością skali. Powiedzmy, że masz myVal = 9.00000000912385; Powyższe zwróci 9.0. Myślę, że powinniśmy zapewnić rozwiązanie, które działa we wszystkich przypadkach myVal. Nie specjalnie dla podanej wartości.
tavalendo
@ user102859 W twoim przykładzie 9,0 jest poprawnym wynikiem. Nie rozumiem, jak to się nie powiedzie.
Craigo
2

DecimalFormat jest najlepszym sposobem na wyjście, ale nie wolę tego. Zawsze robię to cały czas, ponieważ zwraca podwójną wartość. Więc mogę użyć tego więcej niż tylko danych wyjściowych.

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

LUB

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

Jeśli potrzebujesz dużej liczby miejsc dziesiętnych, możesz zamiast tego użyć BigDecimal. W każdym razie .0jest to ważne. Bez tego zaokrąglenie 0.33333d5 zwraca 0.33333 i dozwolone jest tylko 9 cyfr. Druga funkcja bez .0ma problemy ze zwrotem 0.30000 0.30000000000000004.

Se Song
źródło
2

oto moja odpowiedź:

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

System.out.println(num); // 4.89
Pan Jamal Uddin
źródło
1

Więc po przeczytaniu większości odpowiedzi zdałem sobie sprawę, że większość z nich nie będzie precyzyjna, w rzeczywistości użycie BigDecimalwydaje się najlepszym wyborem, ale jeśli nie zrozumiesz, jak RoundingModedziała, nieuchronnie stracisz precyzję. Zrozumiałem to podczas pracy z dużymi liczbami w projekcie i pomyślałem, że może to pomóc innym w problemach z zaokrąglaniem liczb. Na przykład.

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

Spodziewałbyś się uzyskać 1363.28jako wynik, ale skończysz z 1363.27, co nie jest oczekiwane, jeśli nie wiesz, co RoundingModerobi. Patrząc na Dokumenty Oracle , znajdziesz następujący opis RoundingMode.HALF_UP.

Tryb zaokrąglania do zaokrąglania w kierunku „najbliższego sąsiada”, chyba że obaj sąsiedzi są w równej odległości, w takim przypadku zaokrąglaj w górę.

Wiedząc o tym, zdaliśmy sobie sprawę, że nie uzyskamy dokładnego zaokrąglenia, chyba że będziemy chcieli zaokrąglić w kierunku najbliższego sąsiada . Tak więc, aby uzyskać odpowiednią rundę, musielibyśmy zapętlać n-1cyfry dziesiętne w kierunku pożądanych cyfr dziesiętnych. Na przykład.

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

W rezultacie otrzymamy oczekiwaną wydajność, która byłaby 1363.28.

Alain Cruz
źródło
0

Gdzie dp = pożądane miejsce po przecinku, a wartość jest podwójna.

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

    double result = Math.round(value * p)/p;
cel
źródło
1
Produkuje 1.0dla value = 1.005i dp = 2. Zamiast tego użyj tego .
Matthias Braun
jest ok Matt, twój przykład jest nieprawidłowy. ponieważ 1,005 i tak nie może być reprezentowane w podwójnym zmiennoprzecinkowym. musi być przechowywany naprawdę powyżej lub poniżej 1.005, tzn. podczas kompilacji jest przechowywany podwójnie: 1.0049998 (nie jest zapisywany w postaci dziesiętnej w skompilowanym kodzie, jak chcielibyście, zdaniem czytelników) cel jest poprawny, zapisuje wartości jako zmiennoprzecinkowe podwójnie, gdzie przypadki z grzywką, takie jak twoja, i tak są nieznaczne. gdyby tak było, to użyłbyś 3dp, a następnie przekształciłby go na dziesiętny, a następnie wykonałby funkcję dziesiętną zaokrągloną, podobnie jak opublikowany link.
hamish
1
@hamish Nie wiem, gdzie Matthias „chciałby, aby czytelnicy wierzyli” w coś takiego, że wartość jest obliczana jako dziesiętna. Nie wkładaj słów w usta innych ludzi.
user207421
0

Należy pamiętać, że String.format () i DecimalFormat wytwarzają ciąg znaków przy użyciu domyślnych ustawień regionalnych. Mogą więc pisać sformatowaną liczbę z kropką lub przecinkiem jako separator między liczbami całkowitymi i dziesiętnymi. Aby upewnić się, że zaokrąglony ciąg znaków ma format, którego chcesz użyć, java.text.NumberFormat jako:

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

Zostanie wydrukowany w języku angielskim (bez względu na lokalizację): 0,99 123,57 123,00

Przykład pochodzi z Farenda - jak poprawnie przekonwertować double na String .

pwojnowski
źródło
0

Ogólnie zaokrąglanie odbywa się poprzez skalowanie: 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
Amr Ali
źródło