Come procedere con la formattazione da 1200 a 1,2k in Java


157

Vorrei formattare i seguenti numeri nei numeri accanto a loro con 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

Il numero a destra sarà lungo o intero, mentre il numero a sinistra sarà stringa. Come dovrei avvicinarmi a questo. Ho già fatto un piccolo algoritmo per questo, ma ho pensato che potrebbe esserci già qualcosa di inventato là fuori che fa un lavoro migliore e non richiede ulteriori test se inizio a gestire miliardi e trilioni :)

Requisiti addizionali:

  • Il formato deve contenere massimo 4 caratteri
  • Quanto sopra significa che 1.1k è OK, 11.2k non lo è. Lo stesso per 7,8 m è OK 19,1 m non lo è. Solo una cifra prima del punto decimale può avere il punto decimale. Due cifre prima del punto decimale significano non cifre dopo il punto decimale.
  • Non è necessario alcun arrotondamento. (I numeri visualizzati con k e m aggiunti sono più di un indicatore analogico che indica approssimazione non un articolo logico preciso. Quindi l'arrotondamento è irrilevante principalmente a causa della natura della variabile che può aumentare o decrementare diverse cifre anche mentre si sta guardando il risultato memorizzato nella cache.)

1
Se nessuno ha una biblioteca ti dispiacerebbe pubblicare il tuo codice?
Grammin,

1
Questo può aiutare, anche se questo non è un dup. stackoverflow.com/questions/529432
rfeak

1
@Mat Ero curioso di sapere quale soluzione stavi usando prima. Se non ti dispiace, lo pubblichi anche come risposta.
jzd

1
Qual è l'idea alla base di No rounding is necessaryciò che mi sembra assurdo. È solo per complicare le cose? Non sarebbe meglio riformulare questo a Rounding is not necessary, but welcome?
Lupo,

1
Nel caso in cui non si sia notato che i numeri visualizzati con k e m aggiunti sono più di un indicatore analogico che indica l'approssimazione non precisa dell'articolo logico. Quindi l'arrotondamento è irrilevante principalmente a causa della natura della variabile che può aumentare o decretare diverse cifre anche mentre si sta guardando il risultato incassato.
Mat B.

Risposte:


155

Ecco una soluzione che funziona per qualsiasi valore lungo e che trovo abbastanza leggibile (la logica di base è fatta nelle tre righe inferiori del formatmetodo).

Fa leva TreeMapper trovare il suffisso appropriato. È sorprendentemente più efficiente di una precedente soluzione che ho scritto che utilizzava array ed era più difficile da leggere.

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

Codice di prova

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

1
Bella soluzione. Sembra che puoi semplicemente aggiungere più suffissi per quei numeri davvero grandi (quadrilioni, quintilioni, ecc.) E l'output continua a ridimensionare.
Cypher,

Il tuo codice non è del tutto corretto con numeri negativi: -5821dovrebbe essere formattato come -5k, non come -5.8k.
std.denis,

1
@ std.denis L'OP non ha indicato come formattare i numeri negativi. Ho deciso di formattarli come numeri positivi ma con il prefisso -per mantenere lo stesso numero di cifre significative. Ci sono altre opzioni ...
Assylias,

1
Primo: ho eliminato i commenti negativi, perché ovviamente non è colpa tua. Secondo: non è il problema che le buone risposte non ottengano abbastanza attenzione fintanto che ottengono di più come le altre, ma dato che spesso devi cercare buone risposte e solo una risposta sbagliata, cattiva o generica viene valutata (davvero male per imparare cose nuove). E per le persone che emettono doni quando già ci sono così tante risposte mi sarei aspettato di specificare più chiaramente cosa manca e quindi scegliere con cura la risposta che si adatta meglio ai criteri ...
Maraca,

1
ma il mondo intero capisce questo standard? fai attenzione se crei un'app per tutti nel mondo. Per l'inglese sono 10 milioni, ma per il russo sono 10 minuti e così via
user924

101

Lo so, sembra più un programma in C, ma è super leggero!

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

}

Emette:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m

16
Codice offuscato Al giorno d'oggi non dobbiamo codificare in questo modo. Può funzionare come previsto, ma incoraggio l'autore a dare un'occhiata a Roger C. Martin: Clean Code
Andreas Dolk,

30
Offuscato? Chiedo scusa, ma probabilmente hai letto un libro e pensi che al giorno d'oggi sia possibile codificare in qualche modo in modo diverso. Raccontalo a Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ). Oserei qualsiasi codice che possiate scrivere per avvicinarmi alla velocità del mio metodo!
Elia Saounkine,

11
La modifica delle variabili d, c, n in qualcosa di più leggibile (comprensione più rapida) rende questo codice decente a mio avviso
Gennadiy Ryabkin,

5
Perché questa ossessione per le prestazioni? Perché qualcuno dovrebbe voler eseguire un numero sufficiente di queste conversioni da giustificare anche solo pensando alle prestazioni ...? Prima leggibilità, ottimizzazione delle prestazioni solo se necessario.
Amos M. Carpenter,

10
Dovrei essere d'accordo con @ AmosM.Carpenter. Non sapevo della manutenibilità del codice quando ho scritto questa risposta 4 anni fa. Non è male ottimizzare, in generale, ma la leggibilità viene prima di tutto. A proposito, non è poi così male dal punto di vista delle prestazioni: non è 5 volte più lento di quello scritto da Maraca - è più o meno lo stesso (ho messo su alcune soluzioni per un benchmark qui github.com/esaounkine/number-format- punto di riferimento ).
Elijah Saounkine,

43

Ecco una soluzione che utilizza la notazione ingegneristica di 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;
}

Produzione:

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

@Mat Aggiornato per gestire i nuovi requisiti
jzd

C'è un modo semplice per combinare questo con l'istanza di valuta per ottenere funzionalità simili con la valuta?
xdumaine,

@roviuser, non sono sicuro di cosa intendi, ma sembra una domanda separata.
jzd

7
arrotondano da 160000 a 200k e arrotondano anche da 120000 a 100k
k1komans

4
Questo è rotto, ho inserito il numero 10000000000000.0 e dice 103.
Oliver Dixon

23

Hai bisogno di qualche miglioramento, ma: StrictMath in soccorso!
Puoi mettere il suffisso in una stringa o matrice e recuperarlo in base alla potenza, o qualcosa del genere.
La divisione può anche essere gestita in base al potere, penso che quasi tutto riguardi il valore del potere. Spero che sia d'aiuto!

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

uscite:

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


2
Leggibilità migliorata un po ', ho solo bisogno di aggiungere una dichiarazione di ritorno da jzd per risolvere il problema dei 4 caratteri. E ricorda di aggiungere il suffisso se vai su t per evitare l'eccezione AIOOB. ;)
jhurtado,

Questo codice è sensibile alle impostazioni locali, ad esempio nelle impostazioni locali sv_SE 1000 viene convertito in 10x10³, che non corrisponde correttamente a regexp.
Joakim Lundborg,

2
genera un'eccezione per 0, non funziona per i numeri negativi, non arrotonda correttamente 9.999.999 (stampa 10m) ...
assylias

16

Problemi con le risposte attuali

  • Molte delle soluzioni attuali utilizzano questi prefissi k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Tuttavia, secondo varie fonti , i prefissi corretti sono k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Mancanza di supporto per numeri negativi (o almeno mancanza di test che dimostrano che i numeri negativi sono supportati)
  • Mancanza di supporto per l'operazione inversa, ad es. Conversione da 1.1k a 1100 (anche se questo non rientra nell'ambito della domanda originale)

Soluzione Java

Questa soluzione (un'estensione di questa risposta ) risolve i problemi di cui sopra.

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

Groovy Solution

La soluzione è stata originariamente scritta in Groovy come mostrato di seguito.

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

Test (Groovy)

I test sono scritti in Groovy ma possono essere utilizzati per verificare la classe Java o Groovy (poiché entrambi hanno lo stesso nome e 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')
        }
    }
}

1
Penso che tu stia pensando ai prefissi CS, dato che sta parlando di miliardi e trilioni immagino che voglia numeri a scala breve.
jhurtado,

1
9999999 dovrebbe essere stampato come 9,9 m credo (i numeri sono troncati, non arrotondati).
Assylias,

Questa soluzione non supporta i prefissi per valori inferiori a 1, ad esempio u (micro) e m (milli).
gbmhunter,

13

La libreria ICU ha un formattatore basato su regole per i numeri, che può essere usato per lo spelling di numeri, ecc. Penso che l'uso dell'ICU ti darebbe una soluzione leggibile e mantenibile.

[Uso]

La classe giusta è RuleBasedNumberFormat. Il formato stesso può essere memorizzato come file separato (o come costante String, IIRC).

Esempio da 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 stessa pagina mostra numeri romani, quindi immagino che anche il tuo caso dovrebbe essere possibile.


L'unica soluzione nel thread che non cade completamente a pezzi se hai bisogno di localizzazione.
Grozz,

2
Se ne hai bisogno per lo sviluppo Android, questo è già incluso nel framework. Cercare CompactDecimalFormat. Livello API 24+
Gokhan Arik,

10

Con Java-12 + , è possibile utilizzare NumberFormat.getCompactNumberInstanceper formattare i numeri. È possibile creare un NumberFormatprimo come

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

e poi usalo per format:

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

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

fmt.format(1000000000)
$11 ==> "1B"

8

Importante: Le risposte di fusione per doublenon riuscirà per i numeri come 99999999999999999Le tornare 100Pal posto del 99Pperché doubleusi la IEEEserie :

Se una stringa decimale con al massimo 15 cifre significative viene convertita in rappresentazione a doppia precisione IEEE 754 e quindi riconvertita in una stringa con lo stesso numero di cifre significative, la stringa finale deve corrispondere all'originale. [ longha fino a 19 cifre significative .]

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

Questa soluzione elimina le cifre indesiderate e funziona per tutti i longvalori . Implementazione semplice ma performante (confronto sotto). -120k non può essere espresso con 4 caratteri, anche -0.1M è troppo lungo, ecco perché per i numeri negativi 5 caratteri devono essere a posto:

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

Il test else ifall'inizio è necessario perché il minimo è -(2^63)e il massimo è (2^63)-1e quindi l'assegnazione number = -numberfallirebbe se number == Long.MIN_VALUE. Se dobbiamo fare un controllo, allora possiamo anche includere quanti più numeri possibili invece di controllare number == Long.MIN_VALUE.

Il confronto di questa implementazione con quello che ha ottenuto il maggior numero di voti (attualmente si dice che sia il più veloce) ha mostrato che è più di 5 volte più veloce (dipende dalle impostazioni del test, ma con più numeri il guadagno aumenta e questa implementazione ha fare più controlli perché gestisce tutti i casi, quindi se l'altro fosse riparato la differenza diventerebbe ancora più grande). È così veloce perché non ci sono operazioni in virgola mobile, nessun logaritmo, nessuna potenza, nessuna ricorsione, nessuna regex, nessun formattatore sofisticato e minimizzazione della quantità di oggetti creati.


Ecco il programma di 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;
    }

}

Possibile output: 2309 vs. 11591(più o meno lo stesso quando si usano solo numeri positivi e molto più estremo quando si inverte l'ordine di esecuzione, forse ha qualcosa a che fare con la garbage collection)


8

Ecco una breve implementazione senza ricorsione e solo un ciclo molto piccolo. Non funziona con numeri negativi ma supporta tutti i messaggi positivi longfino a 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);
}

Uscite:

1k
5,8k
10k
101k
2m
7,8m
92m
123m
9,2e (questo è Long.MAX_VALUE)

Ho anche fatto un benchmarking molto semplice (formattando 10 milioni di long casuali) ed è considerevolmente più veloce dell'implementazione di Elia e leggermente più veloce dell'implementazione di assylias.

Miniera: 1137.028 ms
Elia: 2664.396 ms
assylias ': 1373.473 ms


1
Nel tuo ultimo aggiornamento, hai aggiunto un bug. Ora restituisce 1k per il numero 101800 .
Sufian,

2
Grazie per averlo notato, è stato risolto
Raniz,

8

Per chiunque voglia arrotondare. Questa è un'ottima soluzione di facile lettura che sfrutta la libreria 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);
        }
    }

8

Il codice seguente mostra come è possibile farlo pensando alla facile espansione.

La "magia" sta principalmente nella makeDecimalfunzione che, per i valori corretti passati, garantisce che non avrai mai più di quattro caratteri nell'output.

Per prima cosa estrae le porzioni intere e decime per un dato divisore, quindi, ad esempio, 12,345,678con un divisore di 1,000,000fornirà un wholevalore 12e un tenthsvalore di 3.

Da ciò, può decidere se produrre solo l'intera parte o sia la parte intera che la decima, usando le regole:

  • Se la decima parte è zero, è sufficiente produrre l'intera parte e il suffisso.
  • Se la parte intera è maggiore di nove, basta generare la parte intera e il suffisso.
  • Altrimenti, output intera parte, decima parte e suffisso.

Il codice per questo segue:

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

Quindi, si tratta semplicemente di chiamare quella funzione di supporto con i valori corretti, incluse alcune costanti per semplificare la vita allo sviluppatore:

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

Il fatto che la makeDecimalfunzione faccia il lavoro grugnito significa che espandersi oltre 999,999,999è solo una questione di aggiungere una linea in più Xlat, così facile che l'ho fatto per te.

Il finale returnin Xlatnon ha bisogno di un condizionale poiché il valore più grande che puoi contenere in un long con segno a 64 bit è solo circa 9,2 quintilioni.

Ma se, per qualche bizzarro requisito, Oracle decide di aggiungere un tipo a 128 bit longero un tipo a 1024 bit damn_long, sarai pronto per questo :-)


E, infine, un piccolo cablaggio di prova che puoi utilizzare per convalidare la funzionalità.

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

Dall'output puoi vedere che ti dà ciò di cui hai bisogno:

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

Inoltre, tieni presente che passare un numero negativo a questa funzione comporterà una stringa troppo lunga per le tue esigenze, poiché segue il < THOUpercorso). Ho pensato che andasse bene dato che hai menzionato solo valori non negativi nella domanda.


6

Non so se sia l'approccio migliore, ma è quello che ho fatto.

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

--- Codice---

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
}

6

La mia funzione per convertire un numero grande in un numero piccolo (con 2 cifre). È possibile modificare il numero di cifre modificando #.##inDecimalFormat

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

analisi

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

Spero che sia d'aiuto


5

Il mio Java è arrugginito, ma ecco come lo implementerei in 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");
    }

Sarebbe facile regolarlo per usare chili CS (1.024) piuttosto che chili metrici, o per aggiungere più unità. Formatta 1.000 come "1.0 k" anziché "1 k", ma credo sia irrilevante.

Per soddisfare il requisito più specifico "non più di quattro caratteri", rimuovere gli spazi prima dei suffissi e regolare il blocco centrale in questo modo:

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

1
Sfortunatamente questo ToStringmetodo non esiste in Java - avresti bisogno di un NumberFormat che potrebbe creare altri problemi (sensibili alle impostazioni locali ecc.).
Assylias,

5

Il mio preferito. È possibile utilizzare "k" e così via anche come indicatore per i decimali, come comune nel dominio elettronico. Questo ti darà una cifra in più senza spazio aggiuntivo

La seconda colonna tenta di utilizzare quante più cifre possibile

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

Questo è il codice

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

4

Rimanendo fedele al mio commento sul fatto che apprezzerei la leggibilità al di sopra delle prestazioni, ecco una versione in cui dovrebbe essere chiaro cosa sta succedendo (supponendo che tu l'abbia usato BigDecimalprima) senza commenti eccessivi (credo nel codice autocompensante), senza preoccuparti delle prestazioni (dal momento che non riesco a immaginare uno scenario in cui vorresti farlo così tante milioni di volte che la performance diventa anche una considerazione).

Questa versione:

  • usa BigDecimals per precisione ed evitare problemi di arrotondamento
  • lavora per arrotondare per difetto come richiesto dal PO
  • funziona per altre modalità di arrotondamento, ad esempio HALF_UPcome nei test
  • ti permette di regolare la precisione (cambia REQUIRED_PRECISION)
  • usa un enumper definire le soglie, cioè potrebbe essere facilmente regolato per usare KB / MB / GB / TB invece di k / m / b / t, ecc., e ovviamente potrebbe essere esteso oltre TRILLIONse necessario
  • viene fornito con test unitari approfonditi, poiché i casi di test nella domanda non stavano testando i confini
  • dovrebbe funzionare per zero e numeri negativi

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

(Rimuovi il commento dalle printlndichiarazioni o modifica per utilizzare il tuo logger preferito per vedere cosa sta facendo.)

E infine, i test in NumberShortenerTest (semplice 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));
    }
  }
}

Sentiti libero di sottolineare nei commenti se ho perso un caso di test significativo o se i valori previsti dovrebbero essere adeguati.


L'unico aspetto negativo evidente nella tua soluzione sembrano le barre di scorrimento V + H per il tuo codice, questo riduce la leggibilità. Pensi che una riformattazione sarebbe possibile senza perdere la chiarezza?
Lupo,

@Lupo: speravo di cavarmela con la copia / incolla dal mio IDE, ma hai ragione, è ipocrita da parte mia rivendicare la leggibilità e richiedere lo scorrimento orizzontale, quindi grazie per averlo sottolineato. ;-) Ho aggiornato i primi due bit di codice, poiché quelli sono quelli che guarderesti per vedere cosa sta succedendo, ma ho lasciato il codice di test, dato che guardarlo da solo non è molto utile - tu ' probabilmente vorresti incollarlo nel tuo IDE per eseguire i test unitari se volessi vedere che i test funzionano. Spero sia ok
Amos M. Carpenter,

Ah bene. Ma nell'ultima casella, i casi di test, i risultati attesi potrebbero essere - otticamente - meglio correlati agli input (intendo i letterali nei primi 6 array).
Lupo,

@Lupo: non sono un fan del tentativo di allineare gli elementi in una linea con spazi o tabulazioni - che non possono essere facilmente configurati in modo coerente per tutti i casi nel mio formatter preferito (eclissi), e farlo manualmente ... in questo modo sta la follia , a causa di tutte le modifiche che devi apportare ogni volta che aggiungi o rimuovi un elemento. Se volessi davvero vederli allineati, incollerei semplicemente i numeri / i valori in un foglio di calcolo come CSV.
Amos M. Carpenter,

1
Tutto dipende da cosa stai cercando, @assylias. Se stai risolvendo un caso d'uso una tantum, la tua soluzione dovrebbe funzionare bene; Mi piace l' TreeMapapproccio. La "leggibilità" è ovviamente soggettiva. ;-) E se qualcuno volesse arrotondare diversamente che troncare nella tua versione? (Ad esempio, quando si utilizza questo per indicare la dimensione del file, chi vorrebbe troncare?) Se si desidera un potere di 2 anziché 10? Dovresti riscrivere un po ', vero? Come ho detto, non stavo deliberatamente cercando di golf il mio codice, molti dei quali avrebbero potuto essere abbreviati (per esempio, non avrei mai tenuto un if-then su una riga).
Amos M. Carpenter,

4

questo è il mio codice pulito e semplice.

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

}

2

Aggiunta di una mia risposta, codice Java, codice autoesplicativo.

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

}

1

Questo frammento di codice è un codice mortalmente semplice e pulito, e funziona totalmente:

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

1

prova questo :

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
}

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

Produzione:

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

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

2
questo non funziona per tutti i casi limite. Prova ad esempio 999.
jzd
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.