Comment formater 1200 à 1,2k en Java

157

Je voudrais formater les nombres suivants dans les nombres à côté d'eux avec java:

1000 to 1k
5821 to 5.8k
10500 to 10k
101800 to 101k
2000000 to 2m
7800000 to 7.8m
92150000 to 92m
123200000 to 123m

Le nombre à droite sera long ou entier, le nombre à gauche sera une chaîne. Comment devrais-je aborder cela. J'ai déjà fait un petit algorithme pour cela, mais je pensais qu'il y avait peut-être déjà quelque chose d'inventé qui fasse un meilleur travail et ne nécessite pas de tests supplémentaires si je commence à traiter des milliards et des billions :)

Exigences supplémentaires:

  • Le format doit comporter au maximum 4 caractères
  • Ce qui précède signifie que 1,1k est OK 11,2k n'est pas. Même chose pour 7,8 m, c'est OK 19,1 m ne l'est pas. Un seul chiffre avant la virgule décimale peut avoir une virgule décimale. Deux chiffres avant la virgule décimale signifie pas de chiffres après la virgule décimale.
  • Aucun arrondi n'est nécessaire. (Les nombres affichés avec k et m ajoutés sont davantage une jauge analogique indiquant une approximation et non un article de logique précis. Par conséquent, l'arrondi n'est pas pertinent, principalement en raison de la nature de la variable, car il peut augmenter ou décréter plusieurs chiffres même lorsque vous regardez le résultat mis en cache.)
Mat B.
la source
1
Si personne n'a de bibliothèque, cela vous dérangerait-il de publier votre code?
Grammin
1
Cela peut aider, même si ce n'est pas un dup. stackoverflow.com/questions/529432
rfeak
1
@Mat J'étais curieux de savoir quelle solution vous utilisiez auparavant. Si cela ne vous dérange pas, poseriez-vous également une réponse.
jzd
1
Quelle est l'idée derrière No rounding is necessarycela me semble absurde. Est-ce juste pour compliquer les choses? Ne serait-il pas préférable de reformuler cela en Rounding is not necessary, but welcome?
Wolf
1
Au cas où vous ne remarqueriez pas que les nombres affichés avec k et m ajoutés sont plus de jauge analogique indiquant une approximation et non un article de logique précis. Par conséquent, l'arrondi n'est pas pertinent principalement en raison de la nature de la variable qui peut augmenter ou décréter plusieurs chiffres même lorsque vous regardez le résultat encaissé.
Mat B.

Réponses:

155

Voici une solution qui fonctionne pour n'importe quelle valeur longue et que je trouve assez lisible (la logique de base se fait dans les trois dernières lignes duformat méthode).

Il utilise TreeMappour trouver le suffixe approprié. Il est étonnamment plus efficace qu'une solution précédente que j'ai écrite qui utilisait des tableaux et était plus difficile à lire.

private static final NavigableMap<Long, String> suffixes = new TreeMap<> ();
static {
  suffixes.put(1_000L, "k");
  suffixes.put(1_000_000L, "M");
  suffixes.put(1_000_000_000L, "G");
  suffixes.put(1_000_000_000_000L, "T");
  suffixes.put(1_000_000_000_000_000L, "P");
  suffixes.put(1_000_000_000_000_000_000L, "E");
}

public static String format(long value) {
  //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
  if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
  if (value < 0) return "-" + format(-value);
  if (value < 1000) return Long.toString(value); //deal with easy case

  Entry<Long, String> e = suffixes.floorEntry(value);
  Long divideBy = e.getKey();
  String suffix = e.getValue();

  long truncated = value / (divideBy / 10); //the number part of the output times 10
  boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
  return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}

Code de test

public static void main(String args[]) {
  long[] numbers = {0, 5, 999, 1_000, -5_821, 10_500, -101_800, 2_000_000, -7_800_000, 92_150_000, 123_200_000, 9_999_999, 999_999_999_999_999_999L, 1_230_000_000_000_000L, Long.MIN_VALUE, Long.MAX_VALUE};
  String[] expected = {"0", "5", "999", "1k", "-5.8k", "10k", "-101k", "2M", "-7.8M", "92M", "123M", "9.9M", "999P", "1.2P", "-9.2E", "9.2E"};
  for (int i = 0; i < numbers.length; i++) {
    long n = numbers[i];
    String formatted = format(n);
    System.out.println(n + " => " + formatted);
    if (!formatted.equals(expected[i])) throw new AssertionError("Expected: " + expected[i] + " but found: " + formatted);
  }
}
assylies
la source
1
Belle solution. On dirait que vous pouvez simplement ajouter plus de suffixes pour ces très grands nombres (quadrillions, quintillions, etc.), et la sortie continue à évoluer.
Cypher le
Votre code n'est pas tout à fait correct avec des nombres négatifs: -5821doit être formaté comme -5k, pas comme -5.8k.
std.denis
1
@ std.denis Le PO n'a pas indiqué comment formater les nombres négatifs. J'ai décidé de les formater comme des nombres positifs mais avec le préfixe -pour conserver le même nombre de chiffres significatifs. Il y a d'autres options ...
assylias
1
Premièrement: j'ai supprimé les mauvais commentaires, car ce n'est évidemment pas de votre faute. Deuxièmement: ce n'est pas le problème que les bonnes réponses n'obtiennent pas assez d'attention tant qu'elles obtiennent plus que les autres, mais comme c'est le cas, vous devez souvent chercher de bonnes réponses et juste une réponse fausse, mauvaise ou générique est mauvais d'apprendre de nouvelles choses). Et pour les personnes qui émettent des primes alors que déjà tant de réponses sont là, je me serais attendu à préciser plus clairement ce qui manque, puis à choisir soigneusement la réponse qui correspond le mieux aux critères ...
maraca
1
mais le monde entier comprend-il cette norme? soyez prudent si vous créez une application pour tout le monde dans le monde. Pour l'anglais c'est 10M mais pour le russe c'est 10 млн et ainsi de suite
user924
101

Je sais, cela ressemble plus à un programme C, mais c'est super léger!

public static void main(String args[]) {
    long[] numbers = new long[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
    for(long n : numbers) {
        System.out.println(n + " => " + coolFormat(n, 0));
    }
}

private static char[] c = new char[]{'k', 'm', 'b', 't'};

/**
 * Recursive implementation, invokes itself for each factor of a thousand, increasing the class on each invokation.
 * @param n the number to format
 * @param iteration in fact this is the class from the array c
 * @return a String representing the number n formatted in a cool looking way.
 */
private static String coolFormat(double n, int iteration) {
    double d = ((long) n / 100) / 10.0;
    boolean isRound = (d * 10) %10 == 0;//true if the decimal part is equal to 0 (then it's trimmed anyway)
    return (d < 1000? //this determines the class, i.e. 'k', 'm' etc
        ((d > 99.9 || isRound || (!isRound && d > 9.99)? //this decides whether to trim the decimals
         (int) d * 10 / 10 : d + "" // (int) d * 10 / 10 drops the decimal
         ) + "" + c[iteration]) 
        : coolFormat(d, iteration+1));

}

Il sort:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Elijah Saounkine
la source
16
Code obscurci. Nous n'avons plus besoin de coder comme ça de nos jours. Peut fonctionner comme prévu, mais j'encourage l'auteur à jeter un coup d'œil à Roger C. Martin: Clean Code
Andreas Dolk
30
Obscurci? Je vous demande pardon, mais vous avez probablement lu un livre et pensez que vous pouvez coder différemment de nos jours. Dites à Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) à ce sujet. J'ose n'importe quel code que vous pouvez éventuellement écrire pour me rapprocher de la vitesse de ma méthode!
Elijah Saounkine
11
Changer les variables d, c, n en quelque chose de plus lisible (compréhension plus rapide) rend ce code décent à mon avis
Gennadiy Ryabkin
5
Pourquoi cette obsession de la performance? Pourquoi quelqu'un voudrait-il exécuter un nombre suffisamment grand de ces conversions pour justifier même une réflexion sur les performances ...? Lisibilité d'abord, optimisation des performances uniquement si nécessaire.
Amos M. Carpenter
10
Je devrais être d'accord avec @ AmosM.Carpenter. Je ne savais pas grand-chose de la maintenabilité du code lorsque j'ai écrit cette réponse il y a 4 ans. Ce n'est pas mal d'optimiser, d'une manière générale, MAIS la lisibilité passe avant tout. Au fait, ce n'est pas si mauvais en termes de performances: pas 5 fois plus lent que celui écrit par maraca - c'est à peu près la même chose (j'ai mis en place certaines des solutions pour un benchmark ici github.com/esaounkine/number-format- référence ).
Elijah Saounkine
43

Voici une solution qui utilise la notation d'ingénierie de DecimalFormat:

public static void main(String args[]) {
    long[] numbers = new long[]{7, 12, 856, 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
    for(long number : numbers) {
        System.out.println(number + " = " + format(number));
    }
}

private static String[] suffix = new String[]{"","k", "m", "b", "t"};
private static int MAX_LENGTH = 4;

private static String format(double number) {
    String r = new DecimalFormat("##0E0").format(number);
    r = r.replaceAll("E[0-9]", suffix[Character.getNumericValue(r.charAt(r.length() - 1)) / 3]);
    while(r.length() > MAX_LENGTH || r.matches("[0-9]+\\.[a-z]")){
        r = r.substring(0, r.length()-2) + r.substring(r.length() - 1);
    }
    return r;
}

Production:

7 = 7
12 = 12
856 = 856
1000 = 1k
5821 = 5.8k
10500 = 10k
101800 = 102k
2000000 = 2m
7800000 = 7.8m
92150000 = 92m
123200000 = 123m
9999999 = 10m
jzd
la source
@Mat Mise à jour pour gérer les nouvelles exigences
jzd
Existe-t-il un moyen simple de combiner cela avec Currency Instance pour obtenir des fonctionnalités similaires avec la devise?
xdumaine
@roviuser, je ne sais pas ce que vous voulez dire, mais cela ressemble à une question distincte.
jzd
7
arrondit 160000 à 200k et arrondit également 120000 à 100k
k1komans
4
C'est cassé, j'ai entré le numéro 10000000000000.0 et il dit 103.
Oliver Dixon
23

Besoin d'améliorations, mais: StrictMath à la rescousse!
Vous pouvez mettre le suffixe dans une chaîne ou un tableau et les récupérer en fonction de la puissance, ou quelque chose comme ça.
La division peut également être gérée autour de la puissance, je pense que presque tout est lié à la valeur de la puissance. J'espère que ça aide!

public static String formatValue(double value) {
int power; 
    String suffix = " kmbt";
    String formattedNumber = "";

    NumberFormat formatter = new DecimalFormat("#,###.#");
    power = (int)StrictMath.log10(value);
    value = value/(Math.pow(10,(power/3)*3));
    formattedNumber=formatter.format(value);
    formattedNumber = formattedNumber + suffix.charAt(power/3);
    return formattedNumber.length()>4 ?  formattedNumber.replaceAll("\\.[0-9]+", "") : formattedNumber;  
}

les sorties:

999
1.2k
98k
911k
1.1m
11b
712b
34t

jhurtado
la source
2
Amélioration de la lisibilité un peu, juste nécessaire d'ajouter une déclaration de retour de jzd pour résoudre le problème des 4 caractères. Et n'oubliez pas d'ajouter un suffixe si vous dépassez t pour éviter une exception AIOOB. ;)
jhurtado
Ce code est sensible aux paramètres régionaux, par exemple dans sv_SE locale 1000 convertit en 10x10³, ce qui n'est pas correctement mis en correspondance par l'expression rationnelle.
Joakim Lundborg
2
lance une exception pour 0, ne fonctionne pas pour les nombres négatifs,
n'arrondit
16

Problèmes avec les réponses actuelles

  • De nombreuses solutions actuelles utilisent ces préfixes k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Cependant, selon diverses sources , les préfixes corrects sont k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Manque de soutien pour les nombres négatifs (ou au moins un manque de tests démontrant que les nombres négatifs sont pris en charge)
  • Manque de prise en charge de l'opération inverse, par exemple la conversion de 1,1k en 1100 (bien que cela n'entre pas dans le cadre de la question initiale)

Solution Java

Cette solution (une extension de cette réponse ) résout les problèmes ci-dessus.

import org.apache.commons.lang.math.NumberUtils;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.regex.Pattern;


/**
 * Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
 * For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
 */
class RoundedMetricPrefixFormat extends Format {

    private static final String[] METRIC_PREFIXES = new String[]{"", "k", "M", "G", "T"};

    /**
     * The maximum number of characters in the output, excluding the negative sign
     */
    private static final Integer MAX_LENGTH = 4;

    private static final Pattern TRAILING_DECIMAL_POINT = Pattern.compile("[0-9]+\\.[kMGT]");

    private static final Pattern METRIC_PREFIXED_NUMBER = Pattern.compile("\\-?[0-9]+(\\.[0-9])?[kMGT]");

    @Override
    public StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {

        Double number = Double.valueOf(obj.toString());

        // if the number is negative, convert it to a positive number and add the minus sign to the output at the end
        boolean isNegative = number < 0;
        number = Math.abs(number);

        String result = new DecimalFormat("##0E0").format(number);

        Integer index = Character.getNumericValue(result.charAt(result.length() - 1)) / 3;
        result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index]);

        while (result.length() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
            int length = result.length();
            result = result.substring(0, length - 2) + result.substring(length - 1);
        }

        return output.append(isNegative ? "-" + result : result);
    }

    /**
     * Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
     * the original number because <tt>format()</tt> is a lossy operation, e.g.
     *
     * <pre>
     * {@code
     * def formatter = new RoundedMetricPrefixFormat()
     * Long number = 5821L
     * String formattedNumber = formatter.format(number)
     * assert formattedNumber == '5.8k'
     *
     * Long parsedNumber = formatter.parseObject(formattedNumber)
     * assert parsedNumber == 5800
     * assert parsedNumber != number
     * }
     * </pre>
     *
     * @param source a number that may have a metric prefix
     * @param pos if parsing succeeds, this should be updated to the index after the last parsed character
     * @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
     */
    @Override
    public Object parseObject(String source, ParsePosition pos) {

        if (NumberUtils.isNumber(source)) {

            // if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
            pos.setIndex(source.length());
            return toNumber(source);

        } else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {

            boolean isNegative = source.charAt(0) == '-';
            int length = source.length();

            String number = isNegative ? source.substring(1, length - 1) : source.substring(0, length - 1);
            String metricPrefix = Character.toString(source.charAt(length - 1));

            Number absoluteNumber = toNumber(number);

            int index = 0;

            for (; index < METRIC_PREFIXES.length; index++) {
                if (METRIC_PREFIXES[index].equals(metricPrefix)) {
                    break;
                }
            }

            Integer exponent = 3 * index;
            Double factor = Math.pow(10, exponent);
            factor *= isNegative ? -1 : 1;

            pos.setIndex(source.length());
            Float result = absoluteNumber.floatValue() * factor.longValue();
            return result.longValue();
        }

        return null;
    }

    private static Number toNumber(String number) {
        return NumberUtils.createNumber(number);
    }
}

Solution Groovy

La solution a été initialement écrite en Groovy comme indiqué ci-dessous.

import org.apache.commons.lang.math.NumberUtils

import java.text.DecimalFormat
import java.text.FieldPosition
import java.text.Format
import java.text.ParsePosition
import java.util.regex.Pattern


/**
 * Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
 * For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
 */
class RoundedMetricPrefixFormat extends Format {

    private static final METRIC_PREFIXES = ["", "k", "M", "G", "T"]

    /**
     * The maximum number of characters in the output, excluding the negative sign
     */
    private static final Integer MAX_LENGTH = 4

    private static final Pattern TRAILING_DECIMAL_POINT = ~/[0-9]+\.[kMGT]/

    private static final Pattern METRIC_PREFIXED_NUMBER = ~/\-?[0-9]+(\.[0-9])?[kMGT]/

    @Override
    StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {

        Double number = obj as Double

        // if the number is negative, convert it to a positive number and add the minus sign to the output at the end
        boolean isNegative = number < 0
        number = Math.abs(number)

        String result = new DecimalFormat("##0E0").format(number)

        Integer index = Character.getNumericValue(result.charAt(result.size() - 1)) / 3
        result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index])

        while (result.size() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
            int length = result.size()
            result = result.substring(0, length - 2) + result.substring(length - 1)
        }

        output << (isNegative ? "-$result" : result)
    }

    /**
     * Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
     * the original number because <tt>format()</tt> is a lossy operation, e.g.
     *
     * <pre>
     * {@code
     * def formatter = new RoundedMetricPrefixFormat()
     * Long number = 5821L
     * String formattedNumber = formatter.format(number)
     * assert formattedNumber == '5.8k'
     *
     * Long parsedNumber = formatter.parseObject(formattedNumber)
     * assert parsedNumber == 5800
     * assert parsedNumber != number
     * }
     * </pre>
     *
     * @param source a number that may have a metric prefix
     * @param pos if parsing succeeds, this should be updated to the index after the last parsed character
     * @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
     */
    @Override
    Object parseObject(String source, ParsePosition pos) {

        if (source.isNumber()) {

            // if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
            pos.index = source.size()
            toNumber(source)

        } else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {

            boolean isNegative = source[0] == '-'

            String number = isNegative ? source[1..-2] : source[0..-2]
            String metricPrefix = source[-1]

            Number absoluteNumber = toNumber(number)

            Integer exponent = 3 * METRIC_PREFIXES.indexOf(metricPrefix)
            Long factor = 10 ** exponent
            factor *= isNegative ? -1 : 1

            pos.index = source.size()
            (absoluteNumber * factor) as Long
        }
    }

    private static Number toNumber(String number) {
        NumberUtils.createNumber(number)
    }
}

Tests (Groovy)

Les tests sont écrits en Groovy mais peuvent être utilisés pour vérifier la classe Java ou Groovy (car ils ont tous les deux le même nom et l'API).

import java.text.Format
import java.text.ParseException

class RoundedMetricPrefixFormatTests extends GroovyTestCase {

    private Format roundedMetricPrefixFormat = new RoundedMetricPrefixFormat()

    void testNumberFormatting() {

        [
                7L         : '7',
                12L        : '12',
                856L       : '856',
                1000L      : '1k',
                (-1000L)   : '-1k',
                5821L      : '5.8k',
                10500L     : '10k',
                101800L    : '102k',
                2000000L   : '2M',
                7800000L   : '7.8M',
                (-7800000L): '-7.8M',
                92150000L  : '92M',
                123200000L : '123M',
                9999999L   : '10M',
                (-9999999L): '-10M'
        ].each { Long rawValue, String expectedRoundValue ->

            assertEquals expectedRoundValue, roundedMetricPrefixFormat.format(rawValue)
        }
    }

    void testStringParsingSuccess() {
        [
                '7'    : 7,
                '8.2'  : 8.2F,
                '856'  : 856,
                '-856' : -856,
                '1k'   : 1000,
                '5.8k' : 5800,
                '-5.8k': -5800,
                '10k'  : 10000,
                '102k' : 102000,
                '2M'   : 2000000,
                '7.8M' : 7800000L,
                '92M'  : 92000000L,
                '-92M' : -92000000L,
                '123M' : 123000000L,
                '10M'  : 10000000L

        ].each { String metricPrefixNumber, Number expectedValue ->

            def parsedNumber = roundedMetricPrefixFormat.parseObject(metricPrefixNumber)
            assertEquals expectedValue, parsedNumber
        }
    }

    void testStringParsingFail() {

        shouldFail(ParseException) {
            roundedMetricPrefixFormat.parseObject('notNumber')
        }
    }
}
Dónal
la source
1
Je pense que vous pensez aux préfixes CS, étant donné qu'il parle de milliards et de billions, je suppose qu'il veut des chiffres à petite échelle.
jhurtado
1
9999999 devrait imprimer 9,9 m je crois (les nombres sont tronqués, pas arrondis).
assylias
Cette solution ne prend pas en charge les préfixes pour les valeurs inférieures à 1, par exemple u (micro) et m (milli).
gbmhunter
13

La bibliothèque ICU a un formateur basé sur des règles pour les nombres, qui peut être utilisé pour l'orthographe des nombres, etc. Je pense que l'utilisation d'ICU vous donnerait une solution lisible et maintenable.

[Usage]

La bonne classe est RuleBasedNumberFormat. Le format lui-même peut être stocké en tant que fichier séparé (ou en tant que constante String, IIRC).

Exemple tiré de http://userguide.icu-project.org/formatparse/numbers

double num = 2718.28;
NumberFormat formatter = 
    new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
String result = formatter.format(num);
System.out.println(result);

La même page montre des chiffres romains, donc je suppose que votre cas devrait également être possible.

Landei
la source
La seule solution du thread qui ne s'effondre pas complètement si vous avez besoin d'une localisation.
Grozz
2
Si vous en avez besoin pour le développement Android, cela est déjà inclus dans le framework. Cherchez CompactDecimalFormat. API Niveau 24+
Gokhan Arik
10

Avec Java-12 + , vous pouvez utiliser NumberFormat.getCompactNumberInstancepour formater les nombres. Vous pouvez créer une NumberFormatpremière comme

NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);

puis utilisez-le pour format:

fmt.format(1000)
$5 ==> "1K"

fmt.format(10000000)
$9 ==> "10M"

fmt.format(1000000000)
$11 ==> "1B"
Naman
la source
8

Important: les réponses envoyées à doubleéchoueront pour les nombres comme 99999999999999999Let retourneront 100Pau lieu de99P parce que doubleutilise la IEEEnorme :

Si une chaîne décimale comportant au plus 15 chiffres significatifs est convertie en représentation double précision IEEE 754, puis reconvertie en chaîne avec le même nombre de chiffres significatifs, la chaîne finale doit correspondre à l'original. [ longcomporte jusqu'à 19 chiffres significatifs .]

System.out.println((long)(double)99999999999999992L); // 100000000000000000
System.out.println((long)(double)99999999999999991L); //  99999999999999984
// it is even worse for the logarithm:
System.out.println(Math.log10(99999999999999600L)); // 17.0
System.out.println(Math.log10(99999999999999500L)); // 16.999999999999996

Cette solution supprime les chiffres indésirables et fonctionne pour tous long valeurs . Implémentation simple mais performante (comparaison ci-dessous). -120k ne peut pas être exprimé avec 4 caractères, même -0,1M est trop long, c'est pourquoi pour les nombres négatifs, 5 caractères doivent être corrects:

private static final char[] magnitudes = {'k', 'M', 'G', 'T', 'P', 'E'}; // enough for long

public static final String convert(long number) {
    String ret;
    if (number >= 0) {
        ret = "";
    } else if (number <= -9200000000000000000L) {
        return "-9.2E";
    } else {
        ret = "-";
        number = -number;
    }
    if (number < 1000)
        return ret + number;
    for (int i = 0; ; i++) {
        if (number < 10000 && number % 1000 >= 100)
            return ret + (number / 1000) + '.' + ((number % 1000) / 100) + magnitudes[i];
        number /= 1000;
        if (number < 1000)
            return ret + number + magnitudes[i];
    }
}

Le test else ifau début est nécessaire car le min est-(2^63) et le max est (2^63)-1et donc l'affectation number = -numberéchouerait si number == Long.MIN_VALUE. Si nous devons faire une vérification, nous pouvons également inclure autant de numéros que possible au lieu de simplement vérifiernumber == Long.MIN_VALUE .

La comparaison de cette implémentation avec celle qui a obtenu le plus de votes positifs (considérée comme la plus rapide actuellement) a montré qu'elle est plus de 5 fois plus rapide (cela dépend des paramètres de test, mais avec plus de nombres, le gain devient plus important et cette implémentation a faire plus de vérifications car il gère tous les cas, donc si l'autre était corrigé, la différence deviendrait encore plus grande). C'est aussi rapide car il n'y a pas d'opérations en virgule flottante, pas de logarithme, pas de puissance, pas de récursivité, pas de regex, pas de formateurs sophistiqués et une minimisation de la quantité d'objets créés.


Voici le programme de test:

public class Test {

    public static void main(String[] args) {
        long[] numbers = new long[20000000];
        for (int i = 0; i < numbers.length; i++)
            numbers[i] = Math.random() < 0.5 ? (long) (Math.random() * Long.MAX_VALUE) : (long) (Math.random() * Long.MIN_VALUE);
        System.out.println(convert1(numbers) + " vs. " + convert2(numbers));
    }

    private static long convert1(long[] numbers) {
        long l = System.currentTimeMillis();
        for (int i = 0; i < numbers.length; i++)
            Converter1.convert(numbers[i]);
        return System.currentTimeMillis() - l;
    }

    private static long convert2(long[] numbers) {
        long l = System.currentTimeMillis();
        for (int i = 0; i < numbers.length; i++)
            Converter2.coolFormat(numbers[i], 0);
        return System.currentTimeMillis() - l;
    }

}

Sortie possible: 2309 vs. 11591(à peu près la même lorsque vous utilisez uniquement des nombres positifs et beaucoup plus extrême lors de l'inversion de l'ordre d'exécution, peut-être que cela a quelque chose à voir avec le ramasse-miettes)

maraca
la source
8

Voici une courte implémentation sans récursivité et juste une très petite boucle. Ne fonctionne pas avec les nombres négatifs mais prend en charge tous les positifs longjusqu'à Long.MAX_VALUE:

private static final char[] SUFFIXES = {'k', 'm', 'g', 't', 'p', 'e' };

public static String format(long number) {
    if(number < 1000) {
        // No need to format this
        return String.valueOf(number);
    }
    // Convert to a string
    final String string = String.valueOf(number);
    // The suffix we're using, 1-based
    final int magnitude = (string.length() - 1) / 3;
    // The number of digits we must show before the prefix
    final int digits = (string.length() - 1) % 3 + 1;

    // Build the string
    char[] value = new char[4];
    for(int i = 0; i < digits; i++) {
        value[i] = string.charAt(i);
    }
    int valueLength = digits;
    // Can and should we add a decimal point and an additional number?
    if(digits == 1 && string.charAt(1) != '0') {
        value[valueLength++] = '.';
        value[valueLength++] = string.charAt(1);
    }
    value[valueLength++] = SUFFIXES[magnitude - 1];
    return new String(value, 0, valueLength);
}

Les sorties:

1k
5,8k
10k
101k
2m
7,8m
92m
123m
9.2e (c'est Long.MAX_VALUE)

J'ai également fait un benchmarking très simple (formatage de 10 millions de longs aléatoires) et c'est considérablement plus rapide que l'implémentation d'Elijah et légèrement plus rapide que l'implémentation d'assylias.

Mine: 1137.028 ms
Elijah's: 2664.396 ms
assylies ': 1373.473 ms

Raniz
la source
1
Dans votre dernière mise à jour, vous avez ajouté un bogue. Il renvoie maintenant 1k pour le numéro 101800 .
Sufian
2
Merci d'avoir remarqué, c'est réglé
Raniz
8

Pour tous ceux qui veulent arrondir. Il s'agit d'une excellente solution facile à lire, qui tire parti de la bibliothèque Java.Lang.Math

 public static String formatNumberExample(Number number) {
        char[] suffix = {' ', 'k', 'M', 'B', 'T', 'P', 'E'};
        long numValue = number.longValue();
        int value = (int) Math.floor(Math.log10(numValue));
        int base = value / 3;
        if (value >= 3 && base < suffix.length) {
            return new DecimalFormat("~#0.0").format(numValue / Math.pow(10, base * 3)) + suffix[base];
        } else {
            return new DecimalFormat("#,##0").format(numValue);
        }
    }
Chris
la source
8

Le code suivant montre comment vous pouvez le faire avec une expansion facile à l'esprit.

La "magie" réside principalement dans le makeDecimal fonction qui, pour les valeurs correctes transmises, garantit que vous n'aurez jamais plus de quatre caractères dans la sortie.

Il extrait d'abord le tout et les dixièmes parties pour un diviseur donné ainsi, par exemple, 12,345,678avec un diviseur de 1,000,000donnera une wholevaleur de 12et une tenthsvaleur de 3.

À partir de là, il peut décider s'il sort uniquement la partie entière ou à la fois la partie entière et la dixième, en utilisant les règles:

  • Si la partie des dixièmes vaut zéro, affichez simplement la partie entière et le suffixe.
  • Si la partie entière est supérieure à neuf, affichez simplement la partie entière et le suffixe.
  • Sinon, affichez la partie entière, la partie dixième et le suffixe.

Le code pour cela suit:

static private String makeDecimal(long val, long div, String sfx) {
    val = val / (div / 10);
    long whole = val / 10;
    long tenths = val % 10;
    if ((tenths == 0) || (whole >= 10))
        return String.format("%d%s", whole, sfx);
    return String.format("%d.%d%s", whole, tenths, sfx);
}

Ensuite, il suffit d'appeler cette fonction d'assistance avec les valeurs correctes, y compris certaines constantes pour faciliter la vie du développeur:

static final long THOU =                1000L;
static final long MILL =             1000000L;
static final long BILL =          1000000000L;
static final long TRIL =       1000000000000L;
static final long QUAD =    1000000000000000L;
static final long QUIN = 1000000000000000000L;

static private String Xlat(long val) {
    if (val < THOU) return Long.toString(val);
    if (val < MILL) return makeDecimal(val, THOU, "k");
    if (val < BILL) return makeDecimal(val, MILL, "m");
    if (val < TRIL) return makeDecimal(val, BILL, "b");
    if (val < QUAD) return makeDecimal(val, TRIL, "t");
    if (val < QUIN) return makeDecimal(val, QUAD, "q");
    return makeDecimal(val, QUIN, "u");
}

Le fait que la makeDecimalfonction fasse le gros travail signifie que s'étendre au 999,999,999- delà est juste une question d'ajouter une ligne supplémentaire Xlat, si facile que je l'ai fait pour vous.

La finale returnen Xlatn'a pas besoin conditionnel puisque la plus grande valeur que vous pouvez tenir dans un 64 bits signé longue est seulement d' environ 9,2 trillion.

Mais si, par une exigence bizarre, Oracle décide d'ajouter un type 128 bits longerou un type 1024 bits damn_long, vous serez prêt pour cela :-)


Et, enfin, un petit faisceau de test que vous pouvez utiliser pour valider la fonctionnalité.

public static void main(String[] args) {
    long vals[] = {
        999L, 1000L, 5821L, 10500L, 101800L, 2000000L,
        7800000L, 92150000L, 123200000L, 999999999L,
        1000000000L, 1100000000L, 999999999999L,
        1000000000000L, 999999999999999L,
        1000000000000000L, 9223372036854775807L
    };
    for (long val: vals)
        System.out.println ("" + val + " -> " + Xlat(val));
    }
}

Vous pouvez voir à partir de la sortie qu'il vous donne ce dont vous avez besoin:

999 -> 999
1000 -> 1k
5821 -> 5.8k
10500 -> 10k
101800 -> 101k
2000000 -> 2m
7800000 -> 7.8m
92150000 -> 92m
123200000 -> 123m
999999999 -> 999m
1000000000 -> 1b
1100000000 -> 1.1b
999999999999 -> 999b
1000000000000 -> 1t
999999999999999 -> 999t
1000000000000000 -> 1q
9223372036854775807 -> 9.2u

Et, en passant, sachez que passer un nombre négatif à cette fonction entraînera une chaîne trop longue pour vos besoins, car elle suit le < THOUchemin). J'ai pensé que c'était correct puisque vous ne mentionnez que des valeurs non négatives dans la question.

paxdiablo
la source
6

Je ne sais pas si c'est la meilleure approche, mais c'est ce que j'ai fait.

7=>7
12=>12
856=>856
1000=>1.0k
5821=>5.82k
10500=>10.5k
101800=>101.8k
2000000=>2.0m
7800000=>7.8m
92150000=>92.15m
123200000=>123.2m
9999999=>10.0m

--- Code ---

public String Format(Integer number){
    String[] suffix = new String[]{"k","m","b","t"};
    int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
    if (size >= 3){
        while (size % 3 != 0) {
            size = size - 1;
        }
    }
    double notation = Math.pow(10, size);
    String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
    return result
}
Eduardo Aviles
la source
6

Ma fonction pour convertir un grand nombre en petit nombre (avec 2 chiffres). Vous pouvez modifier le nombre de chiffres par le changement #.##dansDecimalFormat

public String formatValue(float value) {
    String arr[] = {"", "K", "M", "B", "T", "P", "E"};
    int index = 0;
    while ((value / 1000) >= 1) {
        value = value / 1000;
        index++;
    }
    DecimalFormat decimalFormat = new DecimalFormat("#.##");
    return String.format("%s %s", decimalFormat.format(value), arr[index]);
}

Essai

System.out.println(formatValue(100));     //  100
System.out.println(formatValue(1000));    // 1 K
System.out.println(formatValue(10345));   // 10.35 K
System.out.println(formatValue(10012));   // 10.01 K
System.out.println(formatValue(123456));  // 123.46 K
System.out.println(formatValue(4384324)); // 4.38 M
System.out.println(formatValue(10000000)); // 10 M
System.out.println(formatValue(Long.MAX_VALUE)); // 9.22 E

J'espère que ça aide

Phan Van Linh
la source
5

Mon Java est rouillé, mais voici comment je l'implémenterais en C #:

private string  FormatNumber(double value)
    {
    string[]  suffixes = new string[] {" k", " m", " b", " t", " q"};
    for (int j = suffixes.Length;  j > 0;  j--)
        {
        double  unit = Math.Pow(1000, j);
        if (value >= unit)
            return (value / unit).ToString("#,##0.0") + suffixes[--j];
        }
    return value.ToString("#,##0");
    }

Il serait facile d'ajuster cela pour utiliser des kilos CS (1 024) plutôt que des kilos métriques, ou pour ajouter plus d'unités. Il formate 1 000 en tant que «1,0 k» plutôt que «1 k», mais j'espère que c'est sans importance.

Pour répondre à l'exigence plus spécifique "pas plus de quatre caractères", supprimez les espaces avant les suffixes et ajustez le bloc du milieu comme ceci:

if (value >= unit)
  {
  value /= unit;
  return (value).ToString(value >= unit * 9.95 ? "#,##0" : "#,##0.0") + suffixes[--j];
  }

la source
1
Malheureusement, cette ToStringméthode n'existe pas en Java - vous auriez besoin d'un NumberFormat qui peut créer d'autres problèmes (sensibles aux paramètres régionaux, etc.).
assylias
5

Mon préféré. Vous pouvez également utiliser "k" et ainsi de suite comme indicateur de décimal, comme cela est courant dans le domaine électronique. Cela vous donnera un chiffre supplémentaire sans espace supplémentaire

La deuxième colonne essaie d'utiliser autant de chiffres que possible

1000 => 1.0k | 1000
5821 => 5.8k | 5821
10500 => 10k | 10k5
101800 => 101k | 101k
2000000 => 2.0m | 2m
7800000 => 7.8m | 7m8
92150000 => 92m | 92m1
123200000 => 123m | 123m
9999999 => 9.9m | 9m99

C'est le code

public class HTTest {
private static String[] unit = {"u", "k", "m", "g", "t"};
/**
 * @param args
 */
public static void main(String[] args) {
    int[] numbers = new int[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
    for(int n : numbers) {
        System.out.println(n + " => " + myFormat(n) + " | " + myFormat2(n));
    }
}

private static String myFormat(int pN) {
    String str = Integer.toString(pN);
    int len = str.length ()-1;
    if (len <= 3) return str;
    int level = len / 3;
    int mode = len % 3;
    switch (mode) {
    case 0: return str.substring(0, 1) + "." + str.substring(1, 2) + unit[level];
    case 1: return str.substring(0, 2) + unit[level];
    case 2: return str.substring(0, 3) + unit[level];
    }
    return "how that?";
}
private static String trim1 (String pVal) {
    if (pVal.equals("0")) return "";
    return pVal;
}
private static String trim2 (String pVal) {
    if (pVal.equals("00")) return "";
    return pVal.substring(0, 1) + trim1(pVal.substring(1,2));
}
private static String myFormat2(int pN) {
    String str = Integer.toString(pN);
    int len = str.length () - 1;
    if (len <= 3) return str;
    int level = len / 3;
    int mode = len % 3;
    switch (mode) {
    case 0: return str.substring(0, 1) + unit[level] + trim2(str.substring(1, 3));
    case 2: return str.substring(0, 3) + unit[level];
    case 1: return str.substring(0, 2) + unit[level] + trim1(str.substring(2, 3));
    }
    return "how that?";
}
}
Stefan Bachert
la source
4

En restant fidèle à mon commentaire selon lequel j'apprécierais la lisibilité au-dessus des performances, voici une version où il devrait être clair ce qui se passe (en supposant que vous ayez utilisé BigDecimal s) sans commentaires excessifs (je crois au code auto-documenté), sans vous soucier des performances (puisque je ne peux pas imaginer un scénario où vous voudriez faire cela tant de millions de fois que la performance devient même une considération).

Cette version:

  • utilise BigDecimals pour la précision et pour éviter les problèmes d'arrondi
  • fonctionne pour arrondir comme demandé par le PO
  • fonctionne pour d'autres modes d'arrondi, par exemple HALF_UPcomme dans les tests
  • vous permet d'ajuster la précision (changer REQUIRED_PRECISION)
  • utilise un enumpour définir les seuils, c'est-à-dire pourrait être facilement ajusté pour utiliser KB / MB / GB / TB au lieu de k / m / b / t, etc., et pourrait bien sûr être étendu au TRILLION- delà si nécessaire
  • est livré avec des tests unitaires approfondis, car les cas de test de la question ne testaient pas les frontières
  • devrait fonctionner pour les nombres nuls et négatifs

Threshold.java :

import java.math.BigDecimal;

public enum Threshold {
  TRILLION("1000000000000", 12, 't', null),
  BILLION("1000000000", 9, 'b', TRILLION),
  MILLION("1000000", 6, 'm', BILLION),
  THOUSAND("1000", 3, 'k', MILLION),
  ZERO("0", 0, null, THOUSAND);

  private BigDecimal value;
  private int zeroes;
  protected Character suffix;
  private Threshold higherThreshold;

  private Threshold(String aValueString, int aNumberOfZeroes, Character aSuffix,
      Threshold aThreshold) {
    value = new BigDecimal(aValueString);
    zeroes = aNumberOfZeroes;
    suffix = aSuffix;
    higherThreshold = aThreshold;
  }

  public static Threshold thresholdFor(long aValue) {
    return thresholdFor(new BigDecimal(aValue));
  }

  public static Threshold thresholdFor(BigDecimal aValue) {
    for (Threshold eachThreshold : Threshold.values()) {
      if (eachThreshold.value.compareTo(aValue) <= 0) {
        return eachThreshold;
      }
    }
    return TRILLION; // shouldn't be needed, but you might have to extend the enum
  }

  public int getNumberOfZeroes() {
    return zeroes;
  }

  public String getSuffix() {
    return suffix == null ? "" : "" + suffix;
  }

  public Threshold getHigherThreshold() {
    return higherThreshold;
  }
}

NumberShortener.java :

import java.math.BigDecimal;
import java.math.RoundingMode;

public class NumberShortener {

  public static final int REQUIRED_PRECISION = 2;

  public static BigDecimal toPrecisionWithoutLoss(BigDecimal aBigDecimal,
      int aPrecision, RoundingMode aMode) {
    int previousScale = aBigDecimal.scale();
    int previousPrecision = aBigDecimal.precision();
    int newPrecision = Math.max(previousPrecision - previousScale, aPrecision);
    return aBigDecimal.setScale(previousScale + newPrecision - previousPrecision,
        aMode);
  }

  private static BigDecimal scaledNumber(BigDecimal aNumber, RoundingMode aMode) {
    Threshold threshold = Threshold.thresholdFor(aNumber);
    BigDecimal adjustedNumber = aNumber.movePointLeft(threshold.getNumberOfZeroes());
    BigDecimal scaledNumber = toPrecisionWithoutLoss(adjustedNumber, REQUIRED_PRECISION,
        aMode).stripTrailingZeros();
    // System.out.println("Number: <" + aNumber + ">, adjusted: <" + adjustedNumber
    // + ">, rounded: <" + scaledNumber + ">");
    return scaledNumber;
  }

  public static String shortenedNumber(long aNumber, RoundingMode aMode) {
    boolean isNegative = aNumber < 0;
    BigDecimal numberAsBigDecimal = new BigDecimal(isNegative ? -aNumber : aNumber);
    Threshold threshold = Threshold.thresholdFor(numberAsBigDecimal);
    BigDecimal scaledNumber = aNumber == 0 ? numberAsBigDecimal : scaledNumber(
        numberAsBigDecimal, aMode);
    if (scaledNumber.compareTo(new BigDecimal("1000")) >= 0) {
      scaledNumber = scaledNumber(scaledNumber, aMode);
      threshold = threshold.getHigherThreshold();
    }
    String sign = isNegative ? "-" : "";
    String printNumber = sign + scaledNumber.stripTrailingZeros().toPlainString()
        + threshold.getSuffix();
    // System.out.println("Number: <" + sign + numberAsBigDecimal + ">, rounded: <"
    // + sign + scaledNumber + ">, print: <" + printNumber + ">");
    return printNumber;
  }
}

(Décommentez les printlndéclarations ou changez pour utiliser votre enregistreur préféré pour voir ce qu'il fait.)

Et enfin, les tests dans NumberShortenerTest (plain JUnit 4):

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.junit.Test;

public class NumberShortenerTest {

  private static final long[] NUMBERS_FROM_OP = new long[] { 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000 };
  private static final String[] EXPECTED_FROM_OP = new String[] { "1k", "5.8k", "10k", "101k", "2m", "7.8m", "92m", "123m" };
  private static final String[] EXPECTED_FROM_OP_HALF_UP = new String[] { "1k", "5.8k", "11k", "102k", "2m", "7.8m", "92m", "123m" };
  private static final long[] NUMBERS_TO_TEST = new long[] { 1, 500, 999, 1000, 1001, 1009, 1049, 1050, 1099, 1100, 12345, 123456, 999999, 1000000,
      1000099, 1000999, 1009999, 1099999, 1100000, 1234567, 999999999, 1000000000, 9123456789L, 123456789123L };
  private static final String[] EXPECTED_FROM_TEST = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1k", "1k", "1.1k", "12k", "123k",
      "999k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.2m", "999m", "1b", "9.1b", "123b" };
  private static final String[] EXPECTED_FROM_TEST_HALF_UP = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1.1k", "1.1k", "1.1k", "12k",
      "123k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.1m", "1.2m", "1b", "1b", "9.1b", "123b" };

  @Test
  public void testThresholdFor() {
    assertEquals(Threshold.ZERO, Threshold.thresholdFor(1));
    assertEquals(Threshold.ZERO, Threshold.thresholdFor(999));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1000));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1234));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(9999));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(999999));
    assertEquals(Threshold.MILLION, Threshold.thresholdFor(1000000));
  }

  @Test
  public void testToPrecision() {
    RoundingMode mode = RoundingMode.DOWN;
    assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
    assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
    assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
    assertEquals(new BigDecimal("1.234"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode).stripTrailingZeros()
        .toPlainString());

    mode = RoundingMode.HALF_UP;
    assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
    assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
    assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
    assertEquals(new BigDecimal("1.235"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("1000").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode)
        .stripTrailingZeros().toPlainString());
  }

  @Test
  public void testNumbersFromOP() {
    for (int i = 0; i < NUMBERS_FROM_OP.length; i++) {
      assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP[i],
          NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP_HALF_UP[i],
          NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.HALF_UP));
    }
  }

  @Test
  public void testBorders() {
    assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.DOWN));
    assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.HALF_UP));
    for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
      assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST[i],
          NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST_HALF_UP[i],
          NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
    }
  }

  @Test
  public void testNegativeBorders() {
    for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
      assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST[i],
          NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST_HALF_UP[i],
          NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
    }
  }
}

N'hésitez pas à préciser dans les commentaires si j'ai manqué un cas de test significatif ou si les valeurs attendues doivent être ajustées.

Amos M. Carpenter
la source
Le seul inconvénient évident de votre solution semble les barres de défilement V + H pour votre code, cela réduit la lisibilité. Pensez-vous qu'un reformatage serait possible sans perdre en clarté?
Wolf
@Wolf: J'espérais m'en tirer avec le copier / coller de mon IDE, mais vous avez raison, c'est hypocrite de ma part de revendiquer la lisibilité et d'exiger un défilement horizontal, alors merci de l'avoir signalé. ;-) J'ai mis à jour les deux premiers bits de code, car ce sont ceux que vous regarderiez pour voir ce qui se passe, mais j'ai laissé le code de test, car le regarder en lui-même n'est pas si utile - vous ' d voudriez probablement le coller dans votre propre IDE pour exécuter les tests unitaires si vous vouliez voir que les tests fonctionnent. J'espère que ça va.
Amos M. Carpenter
Ah bien. Mais dans la dernière case, les cas de test, les résultats attendus pourraient être - optiquement - mieux liés aux entrées (je veux dire les littéraux dans les 6 premiers tableaux).
Wolf
@Wolf: Je ne suis pas fan d'essayer d'aligner les éléments sur une ligne avec des espaces ou des tabulations - cela ne peut pas être facilement configuré de manière cohérente pour tous les cas dans mon formateur préféré (éclipse), et le faire manuellement ... de cette façon, c'est la folie , en raison de tous les ajustements que vous devez effectuer chaque fois que vous ajoutez ou supprimez un élément. Si je voulais vraiment les voir alignés, je collerais simplement les nombres / valeurs dans une feuille de calcul au format CSV.
Amos M. Carpenter
1
Tout dépend de ce que vous recherchez, @assylias. Si vous venez de résoudre un cas d'utilisation ponctuel, votre solution devrait fonctionner correctement; J'aime l' TreeMapapproche. La «lisibilité» est bien entendu subjective. ;-) Et si quelqu'un veut arrondir différemment de la troncature dans votre version? (Par exemple, lorsque vous l'utilisez pour indiquer la taille du fichier, qui voudrait tronquer?) Si vous voulez des puissances de 2 plutôt que 10? Vous auriez à réécrire un peu, n'est-ce pas? Comme je l'ai dit, je n'essayais délibérément pas de jouer au golf sur mon code, dont une grande partie aurait pu être raccourcie (je ne garderais jamais un si-alors sur une seule ligne, par exemple).
Amos M. Carpenter
4

c'est mon code. propre et simple.

public static String getRoughNumber(long value) {
    if (value <= 999) {
        return String.valueOf(value);
    }

    final String[] units = new String[]{"", "K", "M", "B", "P"};
    int digitGroups = (int) (Math.log10(value) / Math.log10(1000));
    return new DecimalFormat("#,##0.#").format(value / Math.pow(1000, digitGroups)) + "" + units[digitGroups];

}
Ebrahim Sadeghi
la source
2

Ajout de ma propre réponse, du code Java, du code explicite.

import java.math.BigDecimal;

/**
 * Method to convert number to formatted number.
 * 
 * @author Gautham PJ
 */
public class ShortFormatNumbers
{

    /**
     * Main method. Execution starts here.
     */
    public static void main(String[] args)
    {

        // The numbers that are being converted.
        int[] numbers = {999, 1400, 2500, 45673463, 983456, 234234567};


        // Call the "formatNumber" method on individual numbers to format 
        // the number.
        for(int number : numbers)
        {
            System.out.println(number + ": " + formatNumber(number));
        }

    }


    /**
     * Format the number to display it in short format.
     * 
     * The number is divided by 1000 to find which denomination to be added 
     * to the number. Dividing the number will give the smallest possible 
     * value with the denomination.
     * 
     * @param the number that needs to be converted to short hand notation.
     * @return the converted short hand notation for the number.
     */
    private static String formatNumber(double number)
    {
        String[] denominations = {"", "k", "m", "b", "t"};
        int denominationIndex = 0;

        // If number is greater than 1000, divide the number by 1000 and 
        // increment the index for the denomination.
        while(number > 1000.0)
        {
            denominationIndex++;
            number = number / 1000.0;
        }

        // To round it to 2 digits.
        BigDecimal bigDecimal = new BigDecimal(number);
        bigDecimal = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_EVEN);


        // Add the number with the denomination to get the final value.
        String formattedNumber = bigDecimal + denominations[denominationIndex];
        return formattedNumber;
    }

}
Développeur d'apprentissage
la source
1

Cet extrait de code est juste un code mortellement simple et propre, et fonctionne totalement:

private static char[] c = new char[]{'K', 'M', 'B', 'T'};
private String formatK(double n, int iteration) {
    if (n < 1000) {
        // print 999 or 999K
        if (iteration <= 0) {
            return String.valueOf((long) n);
        } else {
            return String.format("%d%s", Math.round(n), c[iteration-1]);
        }
    } else if (n < 10000) {
        // Print 9.9K
        return String.format("%.1f%s", n/1000, c[iteration]);
    } else {
        // Increase 1 iteration
        return formatK(Math.round(n/1000), iteration+1);
    }
}
KimKha
la source
1

essaye ça :

public String Format(Integer number){
    String[] suffix = new String[]{"k","m","b","t"};
    int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
    if (size >= 3){
        while (size % 3 != 0) {
            size = size - 1;
        }
    }
    double notation = Math.pow(10, size);
    String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
    return result
}
AzizAhmad
la source
1
public class NumberToReadableWordFormat {

    public static void main(String[] args) {
        Integer[] numbers = new Integer[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999,999};
        for(int n : numbers) {
            System.out.println(n + " => " + coolFormat(n));
        }
    }

    private static String[] c = new String[]{"K", "L", "Cr"};
    private static String coolFormat(int n) {
        int size = String.valueOf(n).length();
        if (size>=4 && size<6) {
                int value = (int) Math.pow(10, 1);
                double d = (double) Math.round(n/1000.0 * value) / value;
                return (double) Math.round(n/1000.0 * value) / value+" "+c[0];
        } else if(size>5 && size<8) {
                int value = (int) Math.pow(10, 1);
                return (double) Math.round(n/100000.0 * value) / value+" "+c[1];
        } else if(size>=8) {
                int value = (int) Math.pow(10, 1);
                return (double) Math.round(n/10000000.0 * value) / value+" "+c[2];
        } else {
            return n+"";
        }
    }
}

Production:

1000 => 1.0 K

5821 => 5.8 K

10500 => 10.5 K

101800 => 1.0 L

2000000 => 20.0 L

7800000 => 78.0 L

92150000 => 9.2 Cr

123200000 => 12.3 Cr

9999999 => 100.0 L

999 => 999
Ankit Singh
la source
0
//code longer but work sure...

public static String formatK(int number) {
    if (number < 999) {
        return String.valueOf(number);
    }

    if (number < 9999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 1);
        String str2 = strNumber.substring(1, 2);
        if (str2.equals("0")) {
            return str1 + "k";
        } else {
            return str1 + "." + str2 + "k";
        }
    }

    if (number < 99999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 2);
        return str1 + "k";
    }

    if (number < 999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 3);
        return str1 + "k";
    }

    if (number < 9999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 1);
        String str2 = strNumber.substring(1, 2);
        if (str2.equals("0")) {
            return str1 + "m";
        } else {
            return str1 + "." + str2 + "m";
        }
    }

    if (number < 99999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 2);
        return str1 + "m";
    }

    if (number < 999999999) {
        String strNumber = String.valueOf(number);
        String str1 = strNumber.substring(0, 3);
        return str1 + "m";
    }

    NumberFormat formatterHasDigi = new DecimalFormat("###,###,###");
    return formatterHasDigi.format(number);
}
user2089762
la source
2
cela ne fonctionne pas pour tous vos cas extrêmes. Essayez 999 par exemple.
jzd