Come fare per la formattazione da 1200 a 1.2k in Java

Mi piacerebbe 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, 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 più lavoro e non richiede test aggiuntivi se comincio a gestire miliardi e miliardi di dollari 🙂

Requisiti addizionali:

  • Il formato dovrebbe avere un massimo di 4 caratteri
  • Quanto sopra significa 1.1k è OK 11.2k non lo è. Lo stesso per 7.8m è OK 19.1m no. Solo una cifra prima del punto decimale può avere il punto decimale. Due cifre prima del punto decimale non indicano cifre dopo il punto decimale.
  • Non è necessario alcun arrotondamento (I numeri visualizzati con k e m sono più di un indicatore analogico che indica l’approssimazione dell’articolo di logica non preciso. Quindi l’arrotondamento è irrilevante principalmente a causa della natura della variabile che può aumentare o decodificare diverse cifre anche mentre si guarda il risultato memorizzato nella cache.)

Ecco una soluzione che funziona per qualsiasi valore lungo e che trovo abbastanza leggibile (la logica di base viene eseguita nelle tre righe inferiori del metodo di format ).

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

 private static final NavigableMap 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 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); } } 

Lo so, sembra più un programma 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, ie '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 

Ecco una soluzione che utilizza la notazione tecnica 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]+\\.[az]")){ 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 

Hai bisogno di qualche miglioramento, ma: StrictMath in soccorso!
Puoi mettere il suffisso in una stringa o array e fetch’em in base al potere, o qualcosa del genere.
La divisione può anche essere gestita attorno al potere, penso che quasi tutto riguardi il valore della potenza. 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

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 i numeri negativi (o almeno per la mancanza di test che dimostrano che i numeri negativi sono supportati)
  • Mancanza di supporto per l’operazione inversa, ad esempio 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 ) affronta i problemi precedenti.

 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 metric prefix 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 format() back to a number. This will generally not restore * the original number because format() is a lossy operation, eg * * 
 * {@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 * } * 

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

Soluzione Groovy

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 metric prefix 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 format() back to a number. This will generally not restore * the original number because format() is a lossy operation, eg * * 
 * {@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 * } * 

* * @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 usati per verificare la class Java o Groovy (perché 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') } } } 

La libreria ICU ha un programma di formattazione basato su regole per i numeri, che può essere usato per il numero spellout ecc. Penso che l’uso di ICU ti darebbe una soluzione leggibile e controllabile.

[Uso]

La class corretta è 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 sia ansible.

Importante: le risposte al casting per il double falliscono per numeri come 99999999999999999L e restituiscono 100P anziché 99P perché il double utilizza lo standard IEEE :

Se una stringa decimale con un massimo di 15 cifre significative viene convertita in una 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. [ long ha 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 taglia le cifre indesiderate e funziona per tutti long valori long . Implementazione semplice ma performante (confronto sotto). -120k non può essere express 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 nel else if all'inizio è necessario perché il min è -(2^63) e il massimo è (2^63)-1 e quindi il number = -number assegnazione number = -number fallirebbe se number == Long.MIN_VALUE . Se dobbiamo fare un controllo, allora possiamo anche includere quanti più numeri possibili invece di controllare il number == Long.MIN_VALUE .

Il confronto di questa implementazione con quello che ha ottenuto il maggior numero di uptotes (si dice che sia il più veloce al momento) ha dimostrato 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 verrebbe corretto la differenza diventerebbe ancora più grande). È così veloce perché non ci sono operazioni in virgola mobile, nessun logaritmo, nessuna alimentazione, 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; } } 

Output ansible: 2309 vs. 11591 (all'incirca lo stesso quando si utilizzano solo numeri positivi e molto più estremo quando si inverte l'ordine di esecuzione, forse ha qualcosa a che fare con la garbage collection)

Ecco una breve implementazione senza ricorsione e solo un ciclo molto piccolo. Non funziona con numeri negativi ma supporta tutti i positivi long fino 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 davvero semplice (formattando 10 milioni di random) ed è considerevolmente più veloce rispetto all'implementazione di Elijah e leggermente più veloce dell'attuazione di assylias.

Il mio: 1137.028 ms
Elijah: 2664,396 ms
assylias: 1373.473 ms

Per chiunque voglia girare. Questa è una soluzione eccezionale e 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); } } 

Non so se è l’approccio migliore ma, questo è 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 } 

Il codice seguente mostra come puoi farlo con una facile espansione in mente.

La “magia” risiede principalmente nella funzione makeDecimal 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 decimali per un dato divisore, quindi, per esempio, 12,345,678 con un divisore di 1,000,000 darà un valore whole di 12 e un valore tenths di 3 .

Da questo, può decidere se emette solo l’intera parte o la parte intera e la decima, usando le regole:

  • Se la parte decima è zero, è sufficiente emettere la parte intera e il suffisso.
  • Se la parte intera è maggiore di nove, è sufficiente produrre l’intera parte e il suffisso.
  • Altrimenti, invia la parte intera, la parte dei decimi e il suffisso.

Il codice per quello 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 la funzione helper 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 funzione makeDecimal faccia il grunt funziona significa che espandendo oltre 999,999,999 è solo questione di aggiungere una linea extra a Xlat , così facile che l'ho fatto per te.

Il return finale in Xlat non ha bisogno di un condizionale poiché il valore più grande che puoi contenere in un documento firmato a 64 bit è solo di circa 9,2 quintilioni.

Ma se, per qualche strana richiesta, Oracle decide di aggiungere un tipo longer 128 bit o un tipo damn_long 1024 bit, sarai pronto per questo 🙂


E, infine, un piccolo imbrago di prova che puoi usare 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)); } } 

Puoi vedere dall'output 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 

E, a parte, sappiate che passare un numero negativo a questa funzione risulterà in una stringa troppo lunga per le vostre esigenze, poiché segue il percorso < THOU ). Ho capito che andava bene visto che citavi solo valori non negativi nella domanda.

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 CS kilos (1.024) piuttosto che metric kilos, o per aggiungere più unità. Formatta 1.000 come “1.0 k” anziché “1 k”, ma credo che sia irrilevante.

Per soddisfare il requisito più specifico “non più di quattro caratteri”, rimuovi gli spazi prima dei suffissi e regola il blocco centrale in questo modo:

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

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

La seconda colonna cerca di utilizzare quante più cifre possibili

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

Staying true to my comment that I’d value readability above performance, here’s a version where it should be clear what’s happening (assuming you’ve used BigDecimal s before) without excessive commenting (I believe in self-documenting code), without worrying about performance (since I can’t picture a scenario where you’d want to do this so many millions of times that performance even becomes a consideration).

Questa versione:

  • uses BigDecimal s for precision and to avoid rounding issues
  • works for rounding down as requested by the OP
  • works for other rounding modes, eg HALF_UP as in the tests
  • allows you to adjust the precision (change REQUIRED_PRECISION )
  • uses an enum to define the thresholds, ie could easily be adjusted to use KB/MB/GB/TB instead of k/m/b/t, etc., and could of course be extended beyond TRILLION if required
  • comes with thorough unit tests, since the test cases in the question weren’t testing the borders
  • should work for zero and negative numbers

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

(Uncomment the println statements or change to use your favourite logger to see what it's doing.)

And finally, the tests in 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)); } } } 

Feel free to point out in the comments if I missed a significant test case or if expected values should be adjusted.

Adding my own answer, Java code, self explanatory code..

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

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

This code snippet just deadly simple, and clean code, and totally works:

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