Comment arrondir un nombre à n décimales en Java


1259

Ce que je voudrais, c'est une méthode pour convertir un double en une chaîne qui arrondit en utilisant la méthode de la moitié - c'est-à-dire que si la décimale à arrondir est 5, elle arrondit toujours au nombre suivant. Il s'agit de la méthode standard d'arrondi que la plupart des gens attendent dans la plupart des situations.

Je voudrais également que seuls les chiffres significatifs soient affichés - c'est-à-dire qu'il ne devrait pas y avoir de zéros à la fin.

Je sais qu'une méthode pour ce faire est d'utiliser la String.formatméthode:

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

Retour:

0.91239

ce qui est génial, mais il affiche toujours des nombres avec 5 décimales même s'ils ne sont pas significatifs:

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

Retour:

0.91230

Une autre méthode consiste à utiliser DecimalFormatter:

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

Retour:

0.91238

Cependant, comme vous pouvez le voir, cela utilise un arrondi demi-pair. Autrement dit, il arrondira vers le bas si le chiffre précédent est pair. Ce que j'aimerais, c'est ceci:

0.912385 -> 0.91239
0.912300 -> 0.9123

Quelle est la meilleure façon d'y parvenir en Java?

Réponses:


751

Utilisez setRoundingMode, définissez RoundingModeexplicitement pour gérer votre problème avec le demi-pair, puis utilisez le modèle de format pour la sortie requise.

Exemple:

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

donne la sortie:

12
123.1235
0.23
0.1
2341234.2125

EDIT : La réponse d'origine ne traite pas de l'exactitude des valeurs doubles. C'est très bien si vous ne vous souciez pas beaucoup si elle arrondit vers le haut ou vers le bas. Mais si vous voulez un arrondi précis, vous devez prendre en compte la précision attendue des valeurs. Les valeurs en virgule flottante ont une représentation binaire en interne. Cela signifie qu'une valeur comme 2.7735 n'a pas réellement cette valeur exacte en interne. Il peut être légèrement plus grand ou légèrement plus petit. Si la valeur interne est légèrement inférieure, elle ne sera pas arrondie à 2,77740. Pour remédier à cette situation, vous devez être conscient de l'exactitude des valeurs avec lesquelles vous travaillez et ajouter ou soustraire cette valeur avant d'arrondir. Par exemple, lorsque vous savez que vos valeurs sont précises jusqu'à 6 chiffres, puis pour arrondir les valeurs à mi-chemin, ajoutez cette précision à la valeur:

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

Pour arrondir vers le bas, soustrayez la précision.


9
C'est probablement la meilleure solution présentée jusqu'à présent. La raison pour laquelle je n'ai pas repéré cette fonctionnalité lorsque j'ai examiné la classe DecimalFormat pour la première fois est qu'elle n'a été introduite qu'en Java 1.6. Malheureusement, je suis limité à l'utilisation de la version 1.5, mais il sera utile de le savoir à l'avenir.
Alex Spurling,

1
J'ai essayé cela avec "#.##":, arrondi HALF_UP. 256.335f-> "256.33"... (l'exemple provient des commentaires de la réponse de @ asterite).
bigstones

6
Veuillez faire attention car DecimalFormat dépend de votre configuration locale actuelle, vous ne pouvez pas obtenir un point comme séparateur.
Personnellement,

1
Sachez également que vous ne devez pas vous attendre à ce que DecimalFormat soit thread-safe. Selon les documents Java : les formats décimaux ne sont généralement pas synchronisés. Il est recommandé de créer des instances de format distinctes pour chaque thread. Si plusieurs threads accèdent à un format simultanément, il doit être synchronisé en externe.
CGK

1
comment puis-je faire en sorte qu'il fasse un arrondi correct afin qu'il ne soit pas arrondi de 0,0004 à 0,001

471

En supposant que valuec'est un double, vous pouvez faire:

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

C'est pour une précision à 5 chiffres. Le nombre de zéros indique le nombre de décimales.


71
MISE À JOUR: Je viens de confirmer que cela est bien plus rapide que d'utiliser DecimalFormat. J'ai bouclé en utilisant DecimalFormat 200 fois, et cette méthode. DecimalFormat a pris 14 ms pour terminer les 200 boucles, cette méthode a pris moins de 1 ms. Comme je le soupçonnais, c'est plus rapide. Si vous êtes payé par le cycle d'horloge, c'est ce que vous devriez faire. Je suis surpris que Chris Cudmore dise même ce qu'il a dit pour être honnête. l'allocation d'objets est toujours plus coûteuse que la conversion de primitives et l'utilisation de méthodes statiques (Math.round () par opposition à decimalFormat.format ()).
Andi Jay

99
Cette technique échoue dans plus de 90% des cas. -1.
Marquis de Lorne

25
En effet, cela ne fonctionne pas: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck

54
Soyez très prudent lorsque vous utilisez cette méthode (ou tout arrondi de points flottants). Il échoue pour quelque chose d'aussi simple que 265,335. Le résultat intermédiaire de 265,335 * 100 (précision à 2 chiffres) est 26533.499999999996. Cela signifie qu'il est arrondi à 265,33. Il y a simplement des problèmes inhérents lors de la conversion de nombres à virgule flottante en nombres décimaux réels. Voir la réponse d'EJP ici à stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek

6
@SebastiaanvandenBroek: Wow, je n'ai jamais su qu'il était aussi facile d'obtenir une mauvaise réponse. Cependant, si l'on travaille avec des nombres non exacts, il faut reconnaître que toute valeur n'est pas exacte . 265.335signifie vraiment 265.335 += tolerance, où la tolérance dépend des opérations précédentes et de la plage de valeurs d'entrée. Nous ne connaissons pas la vraie valeur exacte. Aux valeurs de bord, l'une ou l'autre réponse est sans doute correcte. Si nous devons être exacts, nous ne devons pas travailler en double. L' failici n'est pas de reconvertir en double. C'est en pensant OP qu'il peut compter sur l'arrivée 265.335comme étant exactement cela.
ToolmakerSteve

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

vous obtiendrez un BigDecimal. Pour obtenir la chaîne hors de lui, il suffit d' appeler que BigDecimalde » toStringla méthode, outoPlainString méthode pour Java 5+ pour une chaîne de format ordinaire.

Exemple de programme:

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
C'est ma solution préférée. Encore plus court: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) appelle réellement Double.toString () sous le capot;)
Etienne Neveu

4
Agréable. Ne coupez pas les coins et n'utilisez pas new BigDecimal(doubleVar)car vous pouvez rencontrer des problèmes avec l'arrondi des points flottants
Edd

8
@Edd, fait intéressant, le problème d'arrondi se produit dans le cas où SebastiaanvandenBroek mentionne en commentaire la réponse d'astérite. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, mais (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
ToolmakerSteve

7
@ToolmakerSteve C'est parce que l'utilisation de nouveau BigDecimal avec le double prend directement la valeur double et tente de l'utiliser pour créer le BigDecimal, alors que lors de l'utilisation de BigDecimal.valueOf ou du formulaire tostring, il l'analyse d'abord en chaîne (une représentation plus exacte) avant la conversion .
MetroidFan2002

116

Vous pouvez également utiliser le

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

pour vous assurer que vous avez les 0 de fin.


25
Je crois que l'un des objectifs de la question était "qu'il ne devrait pas y avoir de zéros à la fin".
Lunchbox du

7
Pour cette question, l'op ne voulait pas de zéros, mais c'est exactement ce que je voulais. Si vous avez une liste de nombres avec 3 décimales, vous voulez qu'ils aient tous les mêmes chiffres même si c'est 0.
Tom Kincaid

Vous avez oublié de préciserRoundingMode.
IgorGanapolsky

1
@IgorGanapolsky par défaut Decimal modeutiliseRoundingMode.HALF_EVEN.
EndermanAPM

87

Comme certains l'ont noté, la bonne réponse consiste à utiliser soit DecimalFormatou BigDecimal. Les virgules flottantes n'ont pas de décimales, vous ne pouvez donc pas arrondir / tronquer à un nombre spécifique d'entre elles en premier lieu. Vous devez travailler dans une base décimale, et c'est ce que font ces deux classes.

Je poste le code suivant comme contre-exemple à toutes les réponses dans ce fil et en effet partout dans StackOverflow (et ailleurs) qui recommandent la multiplication suivie de la troncature suivie de la division. Il incombe aux partisans de cette technique d'expliquer pourquoi le code suivant produit la mauvaise sortie dans plus de 92% des cas.

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

Résultat de ce programme:

10001 trials 9251 errors

EDIT: Pour répondre à certains commentaires ci-dessous, j'ai refait la partie module de la boucle de test en utilisant BigDecimalet new MathContext(16)pour l'opération de module comme suit:

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

Résultat:

10001 trials 4401 errors

8
L'astuce est que dans toutes vos erreurs 9251, le résultat imprimé est toujours correct.
Didier L

7
@DidierL Cela ne me surprend pas. J'ai eu la très bonne chance de faire des «méthodes numériques» comme mon tout premier cours d'informatique et d'avoir été initié dès le début à ce que la virgule flottante peut et ne peut pas faire. La plupart des programmeurs sont assez vagues à ce sujet.
Marquis de Lorne

15
Tout ce que vous faites, c'est réfuter que le fait de flotter ne représente pas exactement de nombreuses valeurs décimales, ce que j'espère que nous comprenons tous. Ce n'est pas que l'arrondi cause un problème. Comme vous l'admettez, les chiffres s'impriment toujours comme prévu.
Peter Lawrey

8
Votre test est cassé, prenez round () et le test échoue 94% du temps. ideone.com/1y62CY imprime 100 trials 94 errorsVous devriez commencer par un test qui réussit et montrer que l'introduction de l'arrondi rompt le test.
Peter Lawrey

6
Réfutation, réfutée ici. Utilisation de Math.round pour cette plage de doublepas d'erreurs ideone.com/BVCHh3
Peter Lawrey

83

Supposons que vous ayez

double d = 9232.129394d;

vous pouvez utiliser BigDecimal

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

ou sans BigDecimal

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

avec les deux solutions d == 9232.13


2
Je pense que c'est la meilleure solution pour les utilisateurs de Java 1.5 (et inférieurs). Un commentaire cependant, n'utilisez pas le mode d'arrondi HALF_EVEN car il a un comportement différent pour les nombres pairs et impairs (2,5 tours à 2 tandis que 5,5 tours à 6, par exemple), sauf si c'est ce que vous voulez.
IcedDante

4
La première solution est correcte: la seconde ne fonctionne pas. Voir ici pour la preuve.
Marquis de Lorne

1
@EJP: Même la première solution avec RoundingMode.HALF_UPest fausse. Essayez-le avec 1.505. La bonne façon est d'utiliser BigDecimal.valueOf(d).
Matthias Braun

Matthias Braun, la solution est très bien, d'où 31 ups. 1.505 décimal est stocké en virgule flottante double comme 1.50499998 si vous voulez prendre 1.505 et convertir du double en décimal, alors vous devez d'abord le convertir en Double.toString (x) puis placez-le dans un BigDecimal (), mais cela est extrêmement lent et va à l'encontre du but d'utiliser double pour la vitesse en premier lieu.
hamish

1
A exécuté une boucle de 100k avec BigDecimal (a pris 225 ms) et Math.round (2 ms) et voici le timing ... Temps pris: 225 milli secondes pour convertir en utilisant: 9232.13 Temps pris: 2 milli secondes pour convertir en : 9232.13 techiesinfo.com
user1114134

59

Vous pouvez utiliser la classe DecimalFormat.

double d = 3.76628729;

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

Une raison pour laquelle a Double.valueOf()été choisi Double.parseDouble()? La valueOf()méthode renvoie un Doubleobjet, tandis que parseDouble()renvoie une doubleprimitive. Avec la façon dont le code actuel est écrit, vous appliquez également la décompression automatique au retour pour le convertir en la primitive attendue par votre twoDoublevariable, une opération de bytecode supplémentaire. Je changerais la réponse à utiliser à la parseDouble()place.
ecbrodie

Double.parseDouble()a besoin de Stringcommentaires.
Ryde

38

Java's Real-How publie cette solution, qui est également compatible avec les versions antérieures à Java 1.6.

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

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

2
oui c'est exactement ce que fait math.round pour les nombres positifs, mais avez-vous essayé cela avec des nombres négatifs? les gens utilisent math.round dans les autres solutions pour couvrir également le cas des nombres négatifs.
hamish

Remarque: Math.floor(x + 0.5)etMath.round(x)
Peter Lawrey

30

@Milhous: le format décimal pour l'arrondi est excellent:

Vous pouvez également utiliser le

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

pour vous assurer que vous avez les 0 de fin.

J'ajouterais que cette méthode est très efficace pour fournir un véritable mécanisme d'arrondi numérique - non seulement visuellement, mais aussi lors du traitement.

Hypothèse: vous devez implémenter un mécanisme d'arrondi dans un programme GUI. Pour modifier l'exactitude / la précision d'une sortie de résultat, il suffit de changer le format du curseur (c'est-à-dire entre crochets). Pour que:

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

retournerait en sortie: 0.912385

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

retournerait en sortie: 0.91239

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

retournerait en sortie: 0.9124

[EDIT: aussi si le format caret est comme ça ("# 0. ############") et que vous entrez une décimale, par exemple 3.1415926, pour les besoins de l'argument, DecimalFormat ne produit aucune ordure ( par exemple des zéros à la fin) et retournera: 3.1415926.. si vous êtes incliné de cette façon. Certes, c'est un peu bavard au goût de certains développeurs - mais bon, il a une faible empreinte mémoire pendant le traitement et est très facile à implémenter.]

Donc, essentiellement, la beauté de DecimalFormat est qu'il gère simultanément l'apparence de la chaîne - ainsi que le niveau de précision d'arrondi défini. Ergo: vous bénéficiez de deux avantages pour le prix d'une seule implémentation de code. ;)


2
Si vous voulez vraiment des nombres décimaux pour le calcul (et pas seulement pour la sortie), n'utilisez pas un format à virgule flottante binaire comme double. Utilisez BigDecimal ou tout autre format décimal.
Paŭlo Ebermann

20

Voici un résumé de ce que vous pouvez utiliser si vous voulez que le résultat soit une chaîne:

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

Voici une suggestion des bibliothèques que vous pouvez utiliser si vous le souhaitez double. Cependant, je ne le recommanderais pas pour la conversion de chaînes, car double peut ne pas être en mesure de représenter exactement ce que vous voulez (voir par exemple ici ):

  1. Précision d'Apache Commons Math

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

    double rounded = Functions.round(0.00001).apply(0.912385)
  3. Ustensiles de Weka

    double rounded = Utils.roundDouble(0.912385, 5)

18

Vous pouvez utiliser la méthode utilitaire suivante:

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

@mariolpantunes: Cela échouera. Essayez ceci: round(1.005,2);ouround(0.50594724957626620092, 20);
Matthias Braun

Ça marche. Mais flottement et double non informatifs sont des approximations. Considérons votre premier exemple. Si vous imprimez la sortie deInInzeroDPs avant Math.round, il imprimera 100.49999999999999. Vous avez perdu la précision en tant que Math.round autour de 100. En raison de la nature ou des flottants et des doubles, il y a des cas limites quand cela ne fonctionne pas correctement (plus d'informations ici en.wikipedia.org/wiki/Floating_point#Accuracy_problems )
mariolpantunes

double est rapide! décimal est lent. les ordinateurs ne prennent pas la peine de traiter leur réflexion en notation décimale. vous devez renoncer à une précision décimale pour garder le virgule flottante double rapide.
hamish

@hamish La question porte sur la précision, pas sur la vitesse.
Marquis de Lorne du



7

Essayez ceci: org.apache.commons.math3.util.Precision.round (double x, échelle int)

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

La page d'accueil de la bibliothèque de mathématiques Apache Commons est la suivante : http://commons.apache.org/proper/commons-math/index.html

L'implémentation interne de cette méthode est:

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

Depuis que je n'ai trouvé aucune réponse complète sur ce thème, j'ai mis en place une classe qui devrait gérer cela correctement, avec le support de:

  • Mise en page : formatez facilement un double en chaîne avec un certain nombre de décimales
  • Analyse : réanalyser la valeur formatée pour doubler
  • Paramètres régionaux : formater et analyser à l'aide des paramètres régionaux par défaut
  • Notation exponentielle : commencez à utiliser la notation exponentielle après un certain seuil

L'utilisation est assez simple :

(Pour cet exemple, j'utilise un environnement local personnalisé)

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

Voici la classe :

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

public class NumberFormatter {

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

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

    private char groupSeparator;

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

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

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

        groupSeparator = separators.getGroupingSeparator();

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

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

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

        exponential.append("E0");

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

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

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

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

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

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

}

7

Si vous voulez vraiment des nombres décimaux pour le calcul (et pas seulement pour la sortie), n'utilisez pas un format à virgule flottante binaire comme double.

Use BigDecimal or any other decimal-based format.

J'utilise BigDecimal pour les calculs, mais gardez à l'esprit que cela dépend de la taille des nombres avec lesquels vous avez affaire. Dans la plupart de mes implémentations, je trouve que l'analyse du double ou de l'entier à Long est suffisante pour les calculs de très grand nombre.

En fait, j'ai récemment utilisé l'analyse syntaxique pour obtenir des représentations précises (par opposition aux résultats hexadécimaux) dans une interface graphique pour des nombres aussi grands que ################### ############### caractères (à titre d'exemple).


6

Pour ce faire, nous pouvons utiliser ce formateur:

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

ou:

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

Utilisez cette méthode pour obtenir toujours deux décimales:

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

Définition de ces valeurs:

91.32
5.22
11.5
1.2
2.6

En utilisant la méthode, nous pouvons obtenir ces résultats:

91.32
5.22
11.50
1.20
2.60

démo en ligne.


5

Juste au cas où quelqu'un aurait encore besoin d'aide. Cette solution fonctionne parfaitement pour moi.

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

}

renvoie un String avec la sortie souhaitée.


Veuillez indiquer votre motif de vote négatif dans le commentaire, sinon c'est ce que nous appelons l'intimidation.
Asher. M. O

4

Je suis d'accord avec la réponse choisie à utiliser DecimalFormat--- ou alternativement BigDecimal.

Veuillez d'abord lire la mise à jour ci-dessous!

Toutefois , si vous ne voulez arrondir la valeur double et d' obtenir un doublerésultat de valeur, vous pouvez utiliser org.apache.commons.math3.util.Precision.round(..)comme mentionné ci - dessus. L'implémentation utilise BigDecimal, est lente et crée des ordures.

Une méthode similaire mais rapide et sans déchets est fournie par l' DoubleRounderutilitaire dans la bibliothèque 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);

Sortira

 0.667
 0.666
 1000.0
 9.00800700601E10

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

Avertissement: je suis impliqué dans le projet decimal4j.

Mise à jour: Comme l'a souligné @iaforek, DoubleRounder renvoie parfois des résultats contre-intuitifs. La raison en est qu'il effectue un arrondi mathématiquement correct. Par exemple, DoubleRounder.round(256.025d, 2)il sera arrondi à 256,02 car la valeur double représentée par 256,025d est quelque peu inférieure à la valeur rationnelle 256,025 et sera donc arrondie à la baisse.

Remarques:

  • Ce comportement est très similaire à celui du BigDecimal(double)constructeur (mais pas à celui valueOf(double)qui utilise le constructeur de chaîne).
  • Le problème peut être contourné avec une double étape d'arrondi à une précision plus élevée d'abord, mais c'est compliqué et je n'entre pas dans les détails ici

Pour ces raisons et tout ce qui est mentionné ci-dessus dans ce post, je ne peux pas recommander d'utiliser DoubleRounder .


Avez-vous des mesures montrant l'efficacité de votre solution par rapport aux autres?
iaforek

Je ne l'ai pas comparé à d'autres solutions mais il y a un benchmark jmh disponible dans le code source: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… J'ai exécuté le benchmark sur une VM , les résultats sont disponibles sous forme de fichier csv ici: github.com/tools4j/decimal4j/wiki/Performance
marco

1
DoubleRounder échoue dans les cas suivants: DoubleRounder.round (256.025d, 2) - attendu: 256.03, réel: 256.02 ou pour DoubleRounder.round (260.775d, 2) - attendu: 260.78, réel: 260.77.
iaforek

@iaforek: c'est correct, car DoubleRounder effectue un arrondi mathématiquement correct. Cependant, j'avoue que cela est quelque peu contre-intuitif et mettra donc à jour ma réponse en conséquence.
marco

3

L'extrait de code ci-dessous montre comment afficher n chiffres. L'astuce consiste à définir la variable pp à 1 suivi de n zéros. Dans l'exemple ci-dessous, la valeur pp variable a 5 zéros, donc 5 chiffres seront affichés.

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

Si vous utilisez DecimalFormatpour convertir doubleen String, c'est très simple:

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

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

Il existe plusieurs RoundingModevaleurs d'énumération à sélectionner, selon le comportement dont vous avez besoin.


3

Je suis venu ici juste pour avoir une réponse simple sur la façon d'arrondir un nombre. Ceci est une réponse supplémentaire pour fournir cela.

Comment arrondir un nombre en Java

Le cas le plus courant est d'utiliser Math.round().

Math.round(3.7) // 4

Les nombres sont arrondis au nombre entier le plus proche. Une .5valeur est arrondie. Si vous avez besoin d'un comportement d'arrondi différent de celui-là, vous pouvez utiliser l'une des autres fonctions mathématiques . Voir la comparaison ci-dessous.

rond

Comme indiqué ci-dessus, cela arrondit au nombre entier le plus proche. .5décimales arrondies. Cette méthode renvoie 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

plafond

Toute valeur décimale est arrondie au nombre entier suivant. Cela va jusqu'au plafond . Cette méthode renvoie 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

sol

Toute valeur décimale est arrondie à l'entier suivant. Cette méthode renvoie 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

Ceci est similaire à arrondir dans la mesure où les valeurs décimales sont arrondies à l'entier le plus proche. Cependant, à la différence round, .5autour de valeurs à l'entier même. Cette méthode renvoie 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 ***

1
vous ne résolvez que le cas particulier de l'arrondissement à 0 décimales. La question d'origine est plus générique.
lukas84

3

Si vous utilisez une technologie qui a un JDK minimal. Voici un moyen sans aucune bibliothèque Java:

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

Cela échouerait dans les cas où myVal n'est pas inférieur à 1 et avec des zéros après la décimale au-delà de la valeur d'échelle. Disons que vous avez myVal = 9.00000000912385; Ce qui précède renverra 9.0. Je pense que nous devrions fournir une solution qui fonctionne dans tous les cas de myVal. Pas spécifiquement pour la valeur que vous avez indiquée.
tavalendo

@ user102859 Dans votre exemple, 9.0 est le résultat correct. Je ne comprends pas comment cela échouerait.
Craigo

2

DecimalFormat est le meilleur moyen de sortie, mais je ne le préfère pas. Je le fais toujours tout le temps, car il renvoie la double valeur. Je peux donc l'utiliser plus qu'une simple sortie.

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

OU

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

Si vous avez besoin d'une valeur décimale élevée, vous pouvez utiliser BigDecimal à la place. Quoi qu'il en soit, .0c'est important. Sans elle, l'arrondi de 0,33333d5 renvoie 0,33333 et seuls 9 chiffres sont autorisés. La deuxième fonction sans .0a des problèmes avec 0.30000 return 0.30000000000000004.


2

voici ma réponse:

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

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

1

Donc, après avoir lu la plupart des réponses, j'ai réalisé que la plupart d'entre elles ne seraient pas précises, en fait, l'utilisation BigDecimalsemble être le meilleur choix, mais si vous ne comprenez pas comment cela RoundingModefonctionne, vous perdrez inévitablement la précision. J'ai compris cela lorsque je travaillais avec de grands nombres dans un projet et pensais que cela pourrait aider d'autres personnes à avoir du mal à arrondir les nombres. Par exemple.

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

Vous vous attendriez à obtenir 1363.28une sortie, mais vous vous retrouverez avec 1363.27, ce qui n'est pas prévu, si vous ne savez pas ce que RoundingModefait. En consultant les documents Oracle , vous trouverez la description suivante pour RoundingMode.HALF_UP.

Mode d'arrondi pour arrondir vers le "voisin le plus proche" à moins que les deux voisins ne soient équidistants, auquel cas arrondir.

Donc, sachant cela, nous avons réalisé que nous n'obtiendrons pas un arrondi exact, sauf si nous voulons arrondir vers le plus proche voisin . Ainsi, pour accomplir un tour adéquat, nous aurions besoin de boucler de la n-1décimale vers les chiffres décimaux souhaités. Par exemple.

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

Cela finira par nous donner la sortie attendue, ce qui serait le cas 1363.28.


0

dp = décimale que vous voulez, et la valeur est un double.

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

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

1
Produit 1.0pour value = 1.005et dp = 2. Utilisez -le à la place.
Matthias Braun

c'est ok Matt, ton exemple n'est pas valide. car 1.005 ne peut de toute façon pas être représenté en virgule flottante double. il doit être stocké vraiment au-dessus ou en dessous de 1.005, c'est-à-dire qu'il est stocké en double lorsque vous compilez: 1.0049998 (il n'est pas stocké en décimal dans votre code compilé comme vous le feriez croire aux lecteurs), le but est correct, il stocke les valeurs en virgule flottante double, où les cas marginaux comme le vôtre sont de toute façon insignifiants. si c'était le cas, alors vous utiliseriez 3dp, puis le convertiriez en décimal, puis vous feriez une fonction ronde décimale, tout comme le lien que vous avez publié.
hamish

1
@hamish Je ne vois pas où Matthias 'voudrait faire croire aux lecteurs' que la valeur est compilée en décimal. Ne mettez pas de mots dans la bouche des autres.
Marquis de Lorne

0

Gardez à l'esprit que String.format () et DecimalFormat produisent une chaîne en utilisant les paramètres régionaux par défaut. Ils peuvent donc écrire un nombre formaté avec un point ou une virgule comme séparateur entre les parties entières et décimales. Pour vous assurer que la chaîne arrondie est au format souhaité, utilisez java.text.NumberFormat comme suit:

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

S'imprimera dans les paramètres régionaux anglais (quel que soit votre paramètre régional): 0.99 123.57 123.00

L'exemple est tiré de Farenda - comment convertir correctement le double en String .


0

En général, l'arrondi se fait par mise à l'échelle: 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.