Cómo formatear de 1200 a 1.2k en Java

Me gustaría formatear los siguientes números en los números junto a ellos 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 

El número a la derecha será largo o entero, el número a la izquierda será cadena. ¿Cómo debería abordar esto? Ya hice un pequeño algoritmo para esto, pero pensé que podría haber algo inventado por ahí que haga un mejor trabajo y no requiera pruebas adicionales si empiezo a manejar miles de millones y billones 🙂

Requerimientos adicionales:

  • El formato debe tener un máximo de 4 caracteres
  • Lo anterior significa que 1.1k está bien, 11.2k no. Lo mismo para 7.8m está bien 19.1m no. Solo un dígito antes del punto decimal puede tener punto decimal. Dos dígitos antes del punto decimal significan no dígitos después del punto decimal.
  • No se necesita redondeo (Los números que se muestran con k y m adjuntos son más de un indicador analógico que indica aproximación, no un artículo lógico preciso. Por lo tanto, el redondeo es irrelevante debido principalmente a la naturaleza de la variable que puede boost o decrementar varios dígitos incluso mientras mira el resultado almacenado).

Aquí hay una solución que funciona para cualquier valor largo y que encuentro bastante legible (la lógica del núcleo se hace en las tres líneas inferiores del método de format ).

Aprovecha TreeMap para encontrar el sufijo apropiado. Sorprendentemente es más eficiente que una solución anterior que escribí que usaba matrices y que era más difícil de leer.

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

Código de prueba

 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 sé, esto se parece más a un progtwig C, ¡pero es súper liviano!

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

Emite:

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

Aquí una solución que hace uso de la notación de ingeniería de DecimalFormat:

 public static void main(String args[]) { long[] numbers = new long[]{7, 12, 856, 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999}; for(long number : numbers) { System.out.println(number + " = " + format(number)); } } private static String[] suffix = new String[]{"","k", "m", "b", "t"}; private static int MAX_LENGTH = 4; private static String format(double number) { String r = new DecimalFormat("##0E0").format(number); r = r.replaceAll("E[0-9]", suffix[Character.getNumericValue(r.charAt(r.length() - 1)) / 3]); while(r.length() > MAX_LENGTH || r.matches("[0-9]+\\.[az]")){ r = r.substring(0, r.length()-2) + r.substring(r.length() - 1); } return r; } 

Salida:

 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 

Necesita una mejora, pero: ¡StrictMath al rescate!
Puede poner el sufijo en un String o matriz y fetch’em en función de la potencia, o algo así.
La división también se puede gestionar en torno a la potencia, creo que casi todo se trata del valor de la potencia. ¡Espero eso ayude!

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

productos:

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

Problemas con las respuestas actuales

  • Muchas de las soluciones actuales usan estos prefijos k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Sin embargo, según varias fonts , los prefijos correctos son k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Falta de soporte para números negativos (o al menos falta de pruebas que demuestren que los números negativos son compatibles)
  • Falta de soporte para la operación inversa, por ejemplo, la conversión de 1.1k a 1100 (aunque esto está fuera del scope de la pregunta original)

Solución Java

Esta solución (una extensión de esta respuesta ) aborda los problemas anteriores.

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

Solución Groovy

La solución se escribió originalmente en Groovy como se muestra a continuación.

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

Pruebas (Groovy)

Las pruebas están escritas en Groovy pero se pueden usar para verificar ya sea la clase Java o Groovy (porque ambas tienen el mismo nombre y 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 ICU lib tiene un formateador basado en reglas para números, que se puede usar para deletrear números, etc. Creo que usar ICU le daría una solución legible y fácil de mantener.

[Uso]

La clase correcta es RuleBasedNumberFormat. El formato en sí mismo puede almacenarse como un archivo separado (o como una constante de cadena, IIRC).

Ejemplo de http://userguide.icu-project.org/formatparse/numbers

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

La misma página muestra los números romanos, así que supongo que su caso también debería ser posible.

Importante: Las respuestas de conversión al double fallarán para números como 99999999999999999L y devolverán 100P lugar de 99P porque el double usa el estándar IEEE :

Si una cadena decimal con un máximo de 15 dígitos significativos se convierte a representación de doble precisión IEEE 754 y luego se convierte de nuevo a una cadena con el mismo número de dígitos significativos, entonces la cadena final debe coincidir con el original. [ long tiene hasta 19 dígitos significativos .]

 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 

Esta solución corta los dígitos no deseados y funciona para todos long valores long . Implementación simple pero efectiva (comparación más abajo). -120k no puede expressse con 4 caracteres, incluso -0.1M es demasiado largo, es por eso que para números negativos, 5 caracteres tienen que estar bien:

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

La prueba en el else if al principio es necesaria porque el mínimo es -(2^63) y el máximo es (2^63)-1 y por lo tanto el number = -number asignación number = -number fallaría si el number == Long.MIN_VALUE . Si tenemos que hacer un chequeo, entonces podemos incluir tantos números como sea posible en lugar de simplemente verificar el number == Long.MIN_VALUE .

La comparación de esta implementación con el que obtuvo la mayor cantidad de votos positivos (se dice que es el más rápido actualmente) mostró que es más de 5 veces más rápido (depende de la configuración de la prueba, pero con más números la ganancia aumenta y esta implementación hacer más comprobaciones porque maneja todos los casos, por lo que si se solucionara la diferencia, la diferencia sería aún mayor). Es así de rápido porque no hay operaciones de punto flotante, ni logaritmo, ni potencia, ni recurrencia, ni expresiones regulares, ni formateadores sofisticados, ni minimización de la cantidad de objetos creados.


Aquí está el progtwig de prueba:

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

Salida posible: 2309 vs. 11591 (más o menos lo mismo cuando solo se usan números positivos y mucho más extremos al revertir el orden de ejecución, tal vez tiene algo que ver con la recolección de basura)

Aquí hay una implementación corta sin recurrencia y solo un ciclo muy pequeño. No funciona con números negativos pero admite todas las long positivas hasta 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); } 

Productos:

1k
5.8k
10k
101k
2m
7.8m
92m
123m
9.2e (esto es Long.MAX_VALUE )

También realicé algunos benchmarking realmente simples (formateando 10 millones de longitudes aleatorias) y es considerablemente más rápido que la implementación de Elijah y un poco más rápido que la implementación de assylias.

El mío: 1137.028 ms
Elijah's: 2664,396 ms
assylias ': 1373.473 ms

Para cualquiera que quiera redondear. Esta es una gran solución fácil de leer que aprovecha la biblioteca 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); } } 

No sé si es el mejor enfoque, pero esto es lo que hice.

 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 

— Código —

 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 } 

El siguiente código muestra cómo puede hacer esto teniendo en cuenta una fácil expansión.

La “magia” radica principalmente en la función makeDecimal que, para los valores correctos makeDecimal , garantiza que nunca tendrá más de cuatro caracteres en la salida.

Primero extrae las porciones enteras y décimas para un divisor dado, por lo que, por ejemplo, 12,345,678 con un divisor de 1,000,000 dará un valor whole de 12 y un valor de tenths de 3 .

A partir de eso, puede decidir si genera solo la parte completa o ambas, la parte entera y las décimas, usando las reglas:

  • Si la décima parte es cero, solo muestra la parte completa y el sufijo.
  • Si la parte entera es mayor que nueve, solo muestra la parte completa y el sufijo.
  • De lo contrario, muestra la parte entera, décimas parte y sufijo.

El código para eso sigue:

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

Entonces, es una simple cuestión de llamar a esa función auxiliar con los valores correctos, incluidas algunas constantes para facilitar la vida del desarrollador:

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

El hecho de que la función makeDecimal hace que el gruñido funcione significa que expandir más allá de 999,999,999 es simplemente una cuestión de agregar una línea adicional a Xlat , tan fácil que lo he hecho por ti.

La return final en Xlat no necesita un condicional, ya que el valor más grande que puede mantener en una longitud de 64 bits es solo alrededor de 9.2 quintillones.

Pero si, por algún extraño requisito, Oracle decide agregar un tipo longer 128 bits o un tipo damn_long 1024 bits, estará listo para ello 🙂


Y, finalmente, un pequeño arnés de prueba que puede usar para validar la funcionalidad.

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

Puede ver en el resultado que le proporciona lo que necesita:

 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 

Y, aparte, tenga en cuenta que pasar un número negativo a esta función dará como resultado una cadena demasiado larga para sus requisitos, ya que sigue la ruta < THOU ). Pensé que estaba bien, ya que solo mencionas valores no negativos en la pregunta.

Mi Java está oxidado, pero así es como lo implementaría en C #:

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

Sería fácil ajustar esto para usar kilos CS (1,024) en lugar de kilos métricos, o para agregar más unidades. Formatea 1,000 como “1.0 k” en lugar de “1 k”, pero confío en que sea inmaterial.

Para cumplir con el requisito más específico de “no más de cuatro caracteres”, elimine los espacios antes de los sufijos y ajuste el bloque del medio de esta manera:

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

Mi favorito. Puede usar “k” y demás como indicador para decimal también, como es común en el dominio electrónico. Esto te dará un dígito extra sin espacio adicional

La segunda columna intenta usar tantos dígitos como sea posible

 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 

Este es el código

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

Esta versión:

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

prueba esto :

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