1394 votes

Comment arrondir un nombre à n décimales en Java ?

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é supérieure - c'est-à-dire que si la décimale à arrondir est 5, elle arrondit toujours au nombre supérieur. Il s'agit de la méthode d'arrondi standard à laquelle la plupart des gens s'attendent dans la plupart des situations.

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

Je sais qu'une méthode pour y parvenir est d'utiliser la fonction String.format méthode :

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

retours :

0.91239

ce qui est très bien, mais il affiche toujours les nombres avec 5 décimales même s'ils ne sont pas significatifs :

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

retours :

0.91230

Une autre méthode consiste à utiliser le DecimalFormatter :

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

retours :

0.91238

Cependant, comme vous pouvez le constater, cette méthode utilise l'arrondi à la moitié de la valeur nominale. C'est-à-dire qu'il arrondit à la baisse 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 ?

843voto

curtisk Points 8623

Utilisez setRoundingMode fixer le RoundingMode explicitement pour traiter votre problème avec le rond demi-pair, puis utilisez le modèle de format pour votre 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

EDITAR : La réponse originale ne traite pas de la précision des valeurs doubles. C'est très bien si vous ne vous souciez pas beaucoup de savoir si les valeurs sont arrondies vers le haut ou vers le bas. Mais si vous voulez un arrondi précis, alors 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. Elle peut être légèrement supérieure ou légèrement inférieure. Si la valeur interne est légèrement inférieure, elle ne sera pas arrondie à 2,7740. Pour remédier à cette situation, vous devez être conscient de la précision des valeurs avec lesquelles vous travaillez, et ajouter ou soustraire cette valeur avant d'arrondir. Par exemple, si vous savez que vos valeurs sont précises jusqu'à 6 chiffres, pour arrondir des valeurs à mi-chemin, ajoutez cette précision à la valeur :

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

Pour arrondir au chiffre inférieur, soustrayez la précision.

10 votes

C'est probablement la meilleure solution présentée jusqu'à présent. La raison pour laquelle je n'ai pas repéré cette possibilité lorsque j'ai examiné pour la première fois la classe DecimalFormat est qu'elle n'a été introduite qu'en Java 1.6. Malheureusement, je ne peux utiliser que la version 1.5, mais il me sera utile de la connaître pour l'avenir.

0 votes

Ne fonctionne pas avec les formats décimaux en exposant, par exemple format("0.0E00"). Dans cet exemple, il va arrondir 0,0155 à 0,0E00 au lieu de 1,6E-02.

1 votes

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

506voto

asterite Points 3218

En supposant que value est un double que vous pouvez faire :

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

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

1 votes

Math.round utilise une conversion interne en long, que vous retranscrivez ensuite en double.

1 votes

Je pense cependant que c'est mieux que d'utiliser DecimalFormat. DecimalFormat allouera divers objets implicitement, ainsi qu'un String à chaque appel à decimalFormat.Format(). Je pense qu'il est préférable de faire un casting vers quelques primitives plutôt que d'allouer des objets.

77 votes

MISE À JOUR : je viens de confirmer que cette méthode est BEAUCOUP plus rapide que l'utilisation de DecimalFormat. J'ai bouclé en utilisant DecimalFormat 200 fois, et cette méthode. DecimalFormat a mis 14 ms pour effectuer les 200 boucles, cette méthode a mis moins de 1 ms. Comme je le soupçonnais, cette méthode est plus rapide. Si vous êtes payé au cycle d'horloge, c'est ce que vous devriez faire. Pour être honnête, je suis surpris que Chris Cudmore ait pu dire ce qu'il a dit. L'allocation d'objets est toujours plus coûteuse que le moulage de primitives et l'utilisation de méthodes statiques (Math.round() par opposition à decimalFormat.format()).

203voto

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

vous obtiendra un BigDecimal . Pour obtenir la chaîne de caractères, il suffit d'appeler cette fonction BigDecimal 's toString ou la méthode toPlainString pour Java 5+ pour une chaîne de format simple.

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

42 votes

C'est la solution que je préfère. Encore plus court : BigDecimal.valueOf(doubleVar).setScale(yourScaleHere, BigDecimal.ROUND_HALF_UP) ; BigDecimal.valueOf(double val) appelle en fait Double.toString() sous le capot ;)

4 votes

Bien. Ne prenez pas de raccourcis et utilisez new BigDecimal(doubleVar) car vous pouvez rencontrer des problèmes avec l'arrondi des points flottants.

8 votes

@Edd, il est intéressant de noter que le problème d'arrondi se produit dans le cas que SebastiaanvandenBroek mentionne en commentaire de la réponse d'asterite. 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 .

125voto

Milhous Points 6362

Vous pouvez également utiliser le

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

pour être sûr que vous avez les 0 de fin.

25 votes

Je crois que l'un des objectifs de la question était qu'" il devrait y avoir no être des zéros de queue".

9 votes

Pour cette question, l'opérateur 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.

0 votes

Vous avez oublié de préciser RoundingMode.

92voto

EJP Points 113412

Comme d'autres l'ont fait remarquer, la réponse correcte est d'utiliser soit DecimalFormat o BigDecimal . La virgule flottante ne ont décimales, il n'est donc pas possible d'arrondir/de tronquer à un nombre spécifique de décimales en premier lieu. Vous devez travailler dans un radix décimal, et c'est ce que font ces deux classes.

Je publie le code suivant comme contre-exemple à toutes les réponses de ce fil de discussion et de tout StackOverflow (et d'ailleurs) qui recommandent une multiplication suivie d'une troncature suivie d'une division. Il incombe aux défenseurs de cette technique d'expliquer pourquoi le code suivant produit un résultat erroné 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");
    }
}

Sortie de ce programme :

10001 trials 9251 errors

EDITAR: Pour répondre à certains commentaires ci-dessous, j'ai refait la partie module de la boucle de test en utilisant BigDecimal y new MathContext(16) pour l'opération de modulation 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

9 votes

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

1 votes

Cela prouve que le "résultat imprimé" et le "résultat" ne sont pas la même chose. L'exactitude du "résultat imprimé" est affectée par ce qui se passe pendant l'impression. La multiplication et la division ne suffisent pas à résoudre le problème.

7 votes

@DidierL Cela ne me surprend pas. J'ai eu la chance de suivre le cours "Méthodes numériques" comme premier cours d'informatique et d'être 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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X