web-dev-qa-db-fra.com

Comment formater joliment des nombres flottants en chaîne sans décimal 0 inutile?

Un double de 64 bits peut représenter un entier +/- 253 exactement

Compte tenu de ce fait, j'ai choisi d'utiliser un type double en tant que type unique pour tous mes types, car mon plus grand entier est non signé 32 bits.

Mais maintenant, je dois imprimer ces pseudo-entiers, mais le problème est qu’ils sont également mélangés avec des doubles réels.

Alors, comment puis-je imprimer ces doubles bien en Java?

J'ai essayé String.format("%f", value), qui est proche, sauf que j'ai beaucoup de zéros à la fin des petites valeurs.

Voici un exemple de sortie de %f

 232.00000000 
 0.18000000000 
 1237875192.0 
 4.5800000000 
 0.00000000 
 1.23450000 

Ce que je veux c'est:

 232 
 0.18 
 1237875192 
 4.58 
 0 
 1.2345 

Bien sûr, je peux écrire une fonction pour supprimer ces zéros, mais c’est beaucoup de pertes de performances dues à la manipulation de chaînes. Puis-je faire mieux avec un autre code de format?

EDIT

Les réponses de Tom E. et Jeremy S. sont inacceptables car elles arrondissent arbitrairement à deux décimales. S'il vous plaît comprendre le problème avant de répondre.

EDIT 2

Veuillez noter que String.format(format, args...) est dépend des paramètres régionaux (voir les réponses ci-dessous).

446
Pyrolistical

Si l’idée est d’imprimer les entiers stockés sous forme de doublons comme s’ils sont entiers, sinon imprimez les doublons avec la précision minimale requise:

public static String fmt(double d)
{
    if(d == (long) d)
        return String.format("%d",(long)d);
    else
        return String.format("%s",d);
}

Produit:

232
0.18
1237875192
4.58
0
1.2345

Et ne repose pas sur la manipulation de chaîne.

362
JasonD
new DecimalFormat("#.##").format(1.199); //"1.2"

Comme indiqué dans les commentaires, ce n'est pas la bonne réponse à la question initiale.
Cela dit, c’est un moyen très utile de formater des nombres sans zéros inutiles.

393
Tom Esterez
String.format("%.2f", value) ;
224
Jeremy Slade

En bref:

Si vous souhaitez vous débarrasser des zéros et des problèmes de paramètres régionaux, vous devez utiliser:

double myValue = 0.00000021d;

DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
df.setMaximumFractionDigits(340); //340 = DecimalFormat.DOUBLE_FRACTION_DIGITS

System.out.println(df.format(myValue)); //output: 0.00000021

Explication:

Pourquoi d'autres réponses ne me convenaient pas:

  • Double.toString() ou System.out.println ou FloatingDecimal.toJavaFormatString utilise des notations scientifiques si double est inférieur à 10 ^ -3 ou supérieur ou égal à 10 ^ 7

    double myValue = 0.00000021d;
    String.format("%s", myvalue); //output: 2.1E-7
    
  • en utilisant %f, la précision décimale par défaut est 6, sinon vous pouvez le coder en dur, mais des zéros supplémentaires sont ajoutés si vous avez moins de décimales. Exemple :

    double myValue = 0.00000021d;
    String.format("%.12f", myvalue); //output: 0.000000210000
    
  • en utilisant setMaximumFractionDigits(0); ou %.0f, vous supprimez toute précision décimale, ce qui est bien pour les entiers/longs mais pas pour les doubles

    double myValue = 0.00000021d;
    System.out.println(String.format("%.0f", myvalue)); //output: 0
    DecimalFormat df = new DecimalFormat("0");
    System.out.println(df.format(myValue)); //output: 0
    
  • en utilisant DecimalFormat, vous êtes dépendant localement. En français, le séparateur décimal est une virgule, pas un point:

    double myValue = 0.00000021d;
    DecimalFormat df = new DecimalFormat("0");
    df.setMaximumFractionDigits(340);
    System.out.println(df.format(myvalue));//output: 0,00000021
    

    L'utilisation de la langue anglaise vous permet d'obtenir un point pour le séparateur décimal, quel que soit le lieu d'exécution du programme.

Pourquoi utiliser 340 alors pour setMaximumFractionDigits?

Deux raisons :

  • setMaximumFractionDigits accepte un entier, mais son implémentation a un maximum de chiffres permis de DecimalFormat.DOUBLE_FRACTION_DIGITS qui est égal à 340
  • Double.MIN_VALUE = 4.9E-324 donc avec 340 chiffres, vous êtes sûr de ne pas arrondir votre double précision
80
JBE

Pourquoi pas:

if (d % 1.0 != 0)
    return String.format("%s", d);
else
    return String.format("%.0f",d);

Cela devrait fonctionner avec les valeurs extrêmes supportées par Double. Rendements:

0.12
12
12.144252
0
24
Valeriu Paloş

Mes 2 centimes:

if(n % 1 == 0) {
    return String.format(Locale.US, "%.0f", n));
} else {
    return String.format(Locale.US, "%.1f", n));
}
23
Fernando Gallego

Sur ma machine, la fonction suivante est environ 7 fois plus rapide que la fonction fournie par réponse de JasonD , car elle évite String.format:

public static String prettyPrint(double d) {
  int i = (int) d;
  return d == i ? String.valueOf(i) : String.valueOf(d);
}
22
Rok Strniša

Non, ça ne fait rien.

La perte de performance due à la manipulation de chaîne est égale à zéro.

Et voici le code pour couper la fin après %f

private static String trimTrailingZeros(String number) {
    if(!number.contains(".")) {
        return number;
    }

    return number.replaceAll("\\.?0*$", "");
}
11
Pyrolistical
if (d == Math.floor(d)) {
    return String.format("%.0f", d);
} else {
    return Double.toString(d);
}
6
fop6316

J'ai créé DoubleFormatter pour convertir efficacement un grand nombre de valeurs doubles en chaîne Nice/presentable:

double horribleNumber = 3598945.141658554548844; 
DoubleFormatter df = new DoubleFormatter(4,6); //4 = MaxInteger, 6 = MaxDecimal
String beautyDisplay = df.format(horribleNumber);
  • Si la partie entière de V a plus que MaxInteger => affichez V au format scientifique (1.2345e + 30), sinon affichez au format normal 124.45678.
  • le MaxDecimal décider des nombres de chiffres décimaux (couper avec l'arrondi du banquier)

Voici le code:

import Java.math.RoundingMode;
import Java.text.DecimalFormat;
import Java.text.DecimalFormatSymbols;
import Java.text.NumberFormat;
import Java.util.Locale;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;

/**
 * Convert a double to a beautiful String (US-local):
 * 
 * double horribleNumber = 3598945.141658554548844; 
 * DoubleFormatter df = new DoubleFormatter(4,6);
 * String beautyDisplay = df.format(horribleNumber);
 * String beautyLabel = df.formatHtml(horribleNumber);
 * 
 * Manipulate 3 instances of NumberFormat to efficiently format a great number of double values.
 * (avoid to create an object NumberFormat each call of format()).
 * 
 * 3 instances of NumberFormat will be reused to format a value v:
 * 
 * if v < EXP_DOWN, uses nfBelow
 * if EXP_DOWN <= v <= EXP_UP, uses nfNormal
 * if EXP_UP < v, uses nfAbove
 * 
 * nfBelow, nfNormal and nfAbove will be generated base on the precision_ parameter.
 * 
 * @author: DUONG Phu-Hiep
 */
public class DoubleFormatter
{
    private static final double EXP_DOWN = 1.e-3;
    private double EXP_UP; // always = 10^maxInteger
    private int maxInteger_;
    private int maxFraction_;
    private NumberFormat nfBelow_; 
    private NumberFormat nfNormal_;
    private NumberFormat nfAbove_;

    private enum NumberFormatKind {Below, Normal, Above}

    public DoubleFormatter(int maxInteger, int maxFraction){
        setPrecision(maxInteger, maxFraction);
    }

    public void setPrecision(int maxInteger, int maxFraction){
        Preconditions.checkArgument(maxFraction>=0);
        Preconditions.checkArgument(maxInteger>0 && maxInteger<17);

        if (maxFraction == maxFraction_ && maxInteger_ == maxInteger) {
            return;
        }

        maxFraction_ = maxFraction;
        maxInteger_ = maxInteger;
        EXP_UP =  Math.pow(10, maxInteger);
        nfBelow_ = createNumberFormat(NumberFormatKind.Below);
        nfNormal_ = createNumberFormat(NumberFormatKind.Normal);
        nfAbove_ = createNumberFormat(NumberFormatKind.Above);
    }

    private NumberFormat createNumberFormat(NumberFormatKind kind) {
        final String sharpByPrecision = Strings.repeat("#", maxFraction_); //if you do not use Guava library, replace with createSharp(precision);
        NumberFormat f = NumberFormat.getInstance(Locale.US);

        //Apply banker's rounding:  this is the rounding mode that statistically minimizes cumulative error when applied repeatedly over a sequence of calculations
        f.setRoundingMode(RoundingMode.HALF_EVEN);

        if (f instanceof DecimalFormat) {
            DecimalFormat df = (DecimalFormat) f;
            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();

            //set group separator to space instead of comma

            //dfs.setGroupingSeparator(' ');

            //set Exponent symbol to minus 'e' instead of 'E'
            if (kind == NumberFormatKind.Above) {
                dfs.setExponentSeparator("e+"); //force to display the positive sign in the exponent part
            } else {
                dfs.setExponentSeparator("e");
            }

            df.setDecimalFormatSymbols(dfs);

            //use exponent format if v is out side of [EXP_DOWN,EXP_UP]

            if (kind == NumberFormatKind.Normal) {
                if (maxFraction_ == 0) {
                    df.applyPattern("#,##0");
                } else {
                    df.applyPattern("#,##0."+sharpByPrecision);
                }
            } else {
                if (maxFraction_ == 0) {
                    df.applyPattern("0E0");
                } else {
                    df.applyPattern("0."+sharpByPrecision+"E0");
                }
            }
        }
        return f;
    } 

    public String format(double v) {
        if (Double.isNaN(v)) {
            return "-";
        }
        if (v==0) {
            return "0"; 
        }
        final double absv = Math.abs(v);

        if (absv<EXP_DOWN) {
            return nfBelow_.format(v);
        }

        if (absv>EXP_UP) {
            return nfAbove_.format(v);
        }

        return nfNormal_.format(v);
    }

    /**
     * format and higlight the important part (integer part & exponent part) 
     */
    public String formatHtml(double v) {
        if (Double.isNaN(v)) {
            return "-";
        }
        return htmlize(format(v));
    }

    /**
     * This is the base alogrithm: create a instance of NumberFormat for the value, then format it. It should
     * not be used to format a great numbers of value 
     * 
     * We will never use this methode, it is here only to understanding the Algo principal:
     * 
     * format v to string. precision_ is numbers of digits after decimal. 
     * if EXP_DOWN <= abs(v) <= EXP_UP, display the normal format: 124.45678
     * otherwise display scientist format with: 1.2345e+30 
     * 
     * pre-condition: precision >= 1
     */
    @Deprecated
    public String formatInefficient(double v) {

        final String sharpByPrecision = Strings.repeat("#", maxFraction_); //if you do not use Guava library, replace with createSharp(precision);

        final double absv = Math.abs(v);

        NumberFormat f = NumberFormat.getInstance(Locale.US);

        //Apply banker's rounding:  this is the rounding mode that statistically minimizes cumulative error when applied repeatedly over a sequence of calculations
        f.setRoundingMode(RoundingMode.HALF_EVEN);

        if (f instanceof DecimalFormat) {
            DecimalFormat df = (DecimalFormat) f;
            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();

            //set group separator to space instead of comma

            dfs.setGroupingSeparator(' ');

            //set Exponent symbol to minus 'e' instead of 'E'

            if (absv>EXP_UP) {
                dfs.setExponentSeparator("e+"); //force to display the positive sign in the exponent part
            } else {
                dfs.setExponentSeparator("e");
            }
            df.setDecimalFormatSymbols(dfs);

            //use exponent format if v is out side of [EXP_DOWN,EXP_UP]

            if (absv<EXP_DOWN || absv>EXP_UP) {
                df.applyPattern("0."+sharpByPrecision+"E0");
            } else {
                df.applyPattern("#,##0."+sharpByPrecision);
            }
        }
        return f.format(v);
    }

    /**
     * Convert "3.1416e+12" to "<b>3</b>.1416e<b>+12</b>"
     * It is a html format of a number which highlight the integer and exponent part
     */
    private static String htmlize(String s) {
        StringBuilder resu = new StringBuilder("<b>");
        int p1 = s.indexOf('.');

        if (p1>0) {
            resu.append(s.substring(0, p1));
            resu.append("</b>");
        } else {
            p1 = 0;
        }

        int p2 = s.lastIndexOf('e');
        if (p2>0) {
            resu.append(s.substring(p1, p2));
            resu.append("<b>");
            resu.append(s.substring(p2, s.length()));
            resu.append("</b>");
        } else {
            resu.append(s.substring(p1, s.length()));
            if (p1==0){
                resu.append("</b>");
            }
        }
        return resu.toString();
    }
}

Note: J'ai utilisé 2 fonctions de la bibliothèque GUAVA. Si vous n'utilisez pas GUAVA, codez-le vous-même:

/**
 * Equivalent to Strings.repeat("#", n) of the Guava library: 
 */
private static String createSharp(int n) {
    StringBuilder sb = new StringBuilder(); 
    for (int i=0;i<n;i++) {
        sb.append('#');
    }
    return sb.toString();
}
5
Hiep

Veuillez noter que String.format(format, args...) est dépend de l'environnement local car il formate à l'aide de l'environnement local par défaut de l'utilisateur. , , c'est-à-dire probablement avec des virgules et même des espaces, comme 123 456,789 ou 123,456.789 , qui ne correspond peut-être pas exactement à ce que vous attendez.

Vous préférerez peut-être utiliser String.format((Locale)null, format, args...).

Par exemple,

    double f = 123456.789d;
    System.out.println(String.format(Locale.FRANCE,"%f",f));
    System.out.println(String.format(Locale.GERMANY,"%f",f));
    System.out.println(String.format(Locale.US,"%f",f));

empreintes

123456,789000
123456,789000
123456.789000

et c’est ce que String.format(format, args...) fera dans différents pays.

EDIT Ok, puisqu'il y a eu une discussion sur les formalités:

    res += stripFpZeroes(String.format((Locale) null, (nDigits!=0 ? "%."+nDigits+"f" : "%f"), value));
    ...

protected static String stripFpZeroes(String fpnumber) {
    int n = fpnumber.indexOf('.');
    if (n == -1) {
        return fpnumber;
    }
    if (n < 2) {
        n = 2;
    }
    String s = fpnumber;
    while (s.length() > n && s.endsWith("0")) {
        s = s.substring(0, s.length()-1);
    }
    return s;
}
5
new DecimalFormat("00.#").format(20.236)
//out =20.2

new DecimalFormat("00.#").format(2.236)
//out =02.2
  1. 0 pour le nombre minimum de chiffres
  2. Render # digits
4
Hossein Hajizadeh

Celui-ci fera le travail bien, je sais que le sujet est vieux, mais je me débattais avec le même problème jusqu'à ce que j'y arrive. J'espère que quelqu'un le trouvera utile.

    public static String removeZero(double number) {
        DecimalFormat format = new DecimalFormat("#.###########");
        return format.format(number);
    }
4
Bialy

Utilisez un DecimalFormat et setMinimumFractionDigits(0)

3
vlazzle
String s = String.valueof("your int variable");
while (g.endsWith("0") && g.contains(".")) {
    g = g.substring(0, g.length() - 1);
    if (g.endsWith("."))
    {
        g = g.substring(0, g.length() - 1);
    }
}
3
kamal

Réponse tardive mais ...

Vous avez dit que vous choisissez de stocker vos numéros avec le double type. Je pense que cela pourrait être la racine du problème car il vous oblige à stocker nombres entiers en doublons (et donc à perdre les informations initiales sur la nature de la valeur). Que diriez-vous de stocker vos nombres dans des instances de la classe Number (la superclasse de Double et Integer) et de vous fier au polymorphisme pour déterminer le format correct de chaque nombre?

Je sais qu'il est peut-être inacceptable de refactoriser toute une partie de votre code pour cette raison, mais cela pourrait produire le résultat souhaité sans code/transtypage/analyse supplémentaire.

Exemple:

import Java.util.ArrayList;
import Java.util.List;

public class UseMixedNumbers {

    public static void main(String[] args) {
        List<Number> listNumbers = new ArrayList<Number>();

        listNumbers.add(232);
        listNumbers.add(0.18);
        listNumbers.add(1237875192);
        listNumbers.add(4.58);
        listNumbers.add(0);
        listNumbers.add(1.2345);

        for (Number number : listNumbers) {
            System.out.println(number);
        }
    }

}

Produira la sortie suivante:

232
0.18
1237875192
4.58
0
1.2345
2
Spotted
public static String fmt(double d) {
    String val = Double.toString(d);
    String[] valArray = val.split("\\.");
    long valLong = 0;
    if(valArray.length == 2){
        valLong = Long.parseLong(valArray[1]);
    }
    if (valLong == 0)
        return String.format("%d", (long) d);
    else
        return String.format("%s", d);
}

Je devais utiliser cette cause d == (long)d m'indiquait une violation dans le rapport sonar

1
ShAkKiR

Voici ce que je suis venu avec:

  private static String format(final double dbl) {
    return dbl % 1 != 0 ? String.valueOf(dbl) : String.valueOf((int) dbl);
  }

Simple doublure, ne jette que sur int si vraiment besoin de

1
keisar

Voici deux façons de le réaliser. Premièrement, le moyen le plus court (et probablement le meilleur):

public static String formatFloatToString(final float f)
  {
  final int i=(int)f;
  if(f==i)
    return Integer.toString(i);
  return Float.toString(f);
  }

Et voici la manière la plus longue et probablement la pire:

public static String formatFloatToString(final float f)
  {
  final String s=Float.toString(f);
  int dotPos=-1;
  for(int i=0;i<s.length();++i)
    if(s.charAt(i)=='.')
      {
      dotPos=i;
      break;
      }
  if(dotPos==-1)
    return s;
  int end=dotPos;
  for(int i=dotPos+1;i<s.length();++i)
    {
    final char c=s.charAt(i);
    if(c!='0')
      end=i+1;
    }
  final String result=s.substring(0,end);
  return result;
  }
0