Cómo hacer para formatear 1200 a 1.2k en Java

157

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 de la derecha será largo o entero, el número de la izquierda será una 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 lidiar con 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 está. Lo mismo para 7.8m está bien, 19.1m no lo está. Solo un dígito antes del punto decimal puede tener punto decimal. Dos dígitos antes del punto decimal significa no dígitos después del punto decimal.
  • No es necesario redondear. (Los números que se muestran con k y m adjuntos son más de un indicador analógico que indica que la aproximación no es un artículo lógico preciso. Por lo tanto, el redondeo es irrelevante debido principalmente a la naturaleza de la variable que puede aumentar o decrementar varios dígitos incluso mientras mira el resultado en caché).
Mat B.
fuente
1
Si nadie tiene una biblioteca, ¿le importaría publicar su código?
Grammin
1
Esto puede ayudar, aunque esto no es un dup. stackoverflow.com/questions/529432
rfeak
1
@ Mat, tenía curiosidad por saber qué solución estaba usando antes. Si no le importa, ¿lo publicaría también como respuesta?
jzd
1
¿Cuál es la idea detrás de No rounding is necessaryesto? Me parece absurdo. ¿Es solo para complicar las cosas? ¿No sería mejor reformular esto Rounding is not necessary, but welcome?
Wolf
1
En caso de que no haya notado que los números que se muestran con k y m adjuntos son más de un indicador analógico que indica una 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 aumentar o decrementar varios dígitos incluso mientras mira el resultado cobrado.
Mat B.

Respuestas:

155

Aquí hay una solución que funciona para cualquier valor largo y que encuentro bastante legible (la lógica central se realiza en las tres líneas inferiores del formatmétodo).

Aprovecha TreeMappara encontrar el sufijo apropiado. Es sorprendentemente más eficiente que una solución anterior que escribí que usaba matrices y era más difícil de leer.

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

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);
  }
}
asilias
fuente
1
Buena solución Parece que puede agregar más sufijos para esos números realmente grandes (cuatrillones, quintillones, etc.), y la salida continúa escalando.
Cypher
Su código no es del todo correcto con números negativos: -5821debe formatearse como -5k, no como -5.8k.
std.denis
1
@ std.denis El OP no indicó cómo formatear números negativos. Decidí formatearlos como números positivos pero con el prefijo -para mantener el mismo número de dígitos significativos. Hay otras opciones ...
assylias
1
Primero: eliminé los malos comentarios, porque obviamente no es tu culpa. Segundo: no es el problema que las buenas respuestas no reciban suficiente atención siempre que obtengan más que otras, pero como suele ser, hay que buscar respuestas buenas y solo se vota una respuesta incorrecta, mala o genérica (realmente malo para aprender cosas nuevas). Y para las personas que emiten recompensas cuando ya hay muchas respuestas, habría esperado especificar más claramente lo que faltaba y luego elegir cuidadosamente la respuesta que se ajuste mejor a los criterios ...
maraca
1
pero ¿entiende el mundo entero este estándar? ten cuidado si haces una aplicación para todos en el mundo. Para inglés son 10M pero para Rusia son 10 millones y así sucesivamente
usuario924
101

Lo sé, esto se parece más a un programa en C, ¡pero es súper ligero!

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

}

Produce:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Elijah Saounkine
fuente
16
Código ofuscado. No tenemos que codificar así hoy en día. Puede funcionar como se esperaba, pero
recomendaría
30
¿Ofuscado? Perdón, pero probablemente leas un libro y pienses que puedes codificar de alguna manera diferente hoy en día. Cuéntale a Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) sobre eso. ¡Me atrevo con cualquier código que puedas escribir para acercarte a la velocidad de mi método!
Elijah Saounkine
11
Cambiar las variables d, c, n a algo más legible (comprensión más rápida) hace que este código sea decente en mi opinión
Gennadiy Ryabkin
55
¿Por qué esta obsesión con el rendimiento? ¿Por qué alguien querría ejecutar un número suficientemente grande de estas conversiones para garantizar incluso pensar en el rendimiento ...? Legibilidad primero, ajuste de rendimiento solo si es necesario.
Amos M. Carpenter
10
Tendría que estar de acuerdo con @ AmosM.Carpenter. Poco sabía sobre el mantenimiento del código cuando escribí esta respuesta hace 4 años. No es malo optimizar, en general, PERO la legibilidad es lo primero. Por cierto, no es tan malo en cuanto al rendimiento: no es 5 veces más lento que el que escribió Maraca, es casi lo mismo (he presentado algunas de las soluciones para un punto de referencia aquí github.com/esaounkine/number-format- punto de referencia ).
Elijah Saounkine
43

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]+\\.[a-z]")){
        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
jzd
fuente
@Mat Actualizado para manejar nuevos requisitos
jzd
¿Hay una manera fácil de combinar esto con la instancia de moneda para obtener una funcionalidad similar con la moneda?
xdumaine
@roviuser, no estoy seguro de lo que quieres decir, pero esto suena como una pregunta por separado.
jzd
77
redondea 160000 a 200k y también redondea 120000 a 100k
k1komans
44
Esto está roto, ingresé el número 10000000000000.0 y dice 103.
Oliver Dixon
23

Necesita alguna mejora, pero: StrictMath al rescate!
Puede poner el sufijo en una cadena o matriz y buscarlos en función de la potencia, o algo así.
La división también se puede administrar alrededor del poder, creo que casi todo se trata del valor del poder. ¡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;  
}

salidas:

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

jhurtado
fuente
2
Se mejoró un poco la legibilidad, solo necesitaba agregar la declaración de devolución de jzd para resolver el problema de los 4 caracteres. Y recuerde agregar sufijo si va sobre t para evitar la excepción AIOOB. ;)
jhurtado
Este código es sensible a la configuración regional, por ejemplo, en sv_SE, la configuración regional 1000 se convierte a 10x10³, que la expresión regular no coincide correctamente.
Joakim Lundborg
2
lanza una excepción para 0, no funciona para números negativos, no redondea 9,999,999 correctamente (imprime 10m) ...
assylias
16

Problemas con las respuestas actuales

  • Muchas de las soluciones actuales están usando estos prefijos k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Sin embargo, según diversas fuentes , 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, convertir 1.1k a 1100 (aunque esto está fuera del alcance 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 <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);
    }
}

Solución maravillosa

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

Pruebas (Groovy)

Las pruebas se escriben en Groovy, pero se pueden usar para verificar 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')
        }
    }
}
Dónal
fuente
1
Creo que estás pensando en los prefijos CS, dado que está hablando de miles de millones y trillones, supongo que quiere números de escala corta.
jhurtado
1
9999999 debería imprimirse como 9.9m, creo (los números están truncados, no redondeados).
assylias
Esta solución no admite los prefijos para valores que son menores que 1, por ejemplo, u (micro) ym (mili).
gbmhunter
13

La lib de la UCI tiene un formateador basado en reglas para los números, que puede usarse para deletrear números, etc. Creo que usar la UCI le daría una solución fácil de leer y mantener.

[Uso]

La clase correcta es RuleBasedNumberFormat. El formato en sí puede almacenarse como un archivo separado (o como String constant, 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 números romanos, así que supongo que su caso también debería ser posible.

Landei
fuente
La única solución en el hilo que no se desmorona por completo si necesita localización.
Grozz
2
Si lo necesita para el desarrollo de Android, esto ya está incluido en el marco. Buscar CompactDecimalFormat. Nivel API 24+
Gokhan Arik
10

Con Java-12 + , puede usar NumberFormat.getCompactNumberInstancepara formatear los números. Puedes crear un NumberFormatprimero como

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

y luego úsalo para format:

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

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

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

Importante: Las respuestas a la conversión doublefallarán para números como 99999999999999999Ly regresar en 100Plugar de 99Pporque doubleusa el IEEEestándar :

Si una cadena decimal con un máximo de 15 dígitos significativos se convierte en una representación de doble precisión IEEE 754 y luego se vuelve a convertir en una cadena con el mismo número de dígitos significativos, entonces la cadena final debe coincidir con el original. [ longtiene 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 los longvalores . Implementación simple pero eficiente (comparación a continuación). -120k no se puede expresar con 4 caracteres, incluso -0.1M es demasiado largo, es por eso que para números negativos 5 caracteres deben 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 else ifal principio es necesaria porque el mínimo es -(2^63)y el máximo es (2^63)-1y, por lo tanto, la asignación number = -numberfallaría si number == Long.MIN_VALUE. Si tenemos que hacer una verificación, entonces también podemos incluir tantos números como sea posible en lugar de simplemente verificar number == Long.MIN_VALUE.

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


Aquí está el programa 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(casi lo mismo cuando solo se usan números positivos y mucho más extremo al invertir el orden de ejecución, tal vez tenga algo que ver con la recolección de basura)

maraca
fuente
8

Aquí hay una implementación corta sin recursión y solo un bucle muy pequeño. No funciona con números negativos, pero admite todos los longmensajes positivos 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);
}

Salidas:

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

También hice algunos puntos de referencia realmente simples (formateando 10 millones de largos aleatorios) 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 las asilias.

Mina: 1137.028 ms
Elijah: 2664.396 ms
assylias ': 1373.473 ms

Raniz
fuente
1
En su última actualización, agregó un error. Ahora devuelve 1k para el número 101800 .
Sufian
2
Gracias por notarlo, está arreglado
Raniz
8

Para cualquiera que quiera redondear. Esta es una excelente 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);
        }
    }
Chris
fuente
8

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

La "magia" radica principalmente en la makeDecimalfunción que, para los valores correctos pasados, 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 ejemplo, 12,345,678con un divisor de 1,000,000dará un wholevalor de 12y un tenthsvalor de 3.

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

  • Si la décima parte es cero, simplemente envíe la parte completa y el sufijo.
  • Si la parte completa es mayor que nueve, simplemente envíe la parte completa y el sufijo.
  • De lo contrario, salida 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 makeDecimalfunción haga el trabajo pesado significa que expandirse más allá 999,999,999es solo cuestión de agregar una línea adicional Xlat, tan fácil que lo he hecho por usted.

La final returnen Xlatno necesita un condicional ya que el valor más grande se puede mantener en un signo de 64 bits de largo es de sólo 9,2 trillones.

Pero si, por algún requisito extraño, Oracle decide agregar un tipo de 128 bits longero un tipo de 1024 bits damn_long, 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 brinda 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 < THOUruta). Supuse que estaba bien ya que solo mencionas valores no negativos en la pregunta.

paxdiablo
fuente
6

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
}
Eduardo Avilés
fuente
6

Mi función para convertir números grandes a números pequeños (con 2 dígitos). Puede cambiar el número de dígitos cambiando #.##enDecimalFormat

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

Pruebas

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

Espero que ayude

Phan Van Linh
fuente
5

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 agregar más unidades. Formatea 1,000 como "1.0 k" en lugar de "1 k", pero confío en que sea irrelevante.

Para cumplir con el requisito más específico "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];
  }

fuente
1
Desafortunadamente, este ToStringmétodo no existe en Java; necesitaría un NumberFormat que pueda crear otros problemas (confidenciales, etc.).
Assylias
5

Mi favorito. También podría usar "k", etc., como indicador de decimal, como es común en el dominio electrónico. Esto le dará un dígito adicional 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 codigo

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

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

Manteniéndome fiel a mi comentario de que valoraría la legibilidad por encima del rendimiento, aquí hay una versión en la que debe quedar claro lo que está sucediendo (suponiendo que haya utilizado BigDecimal s antes) sin comentarios excesivos (creo en el código de autodocumentación), sin preocuparse por el rendimiento (ya que no puedo imaginar un escenario en el que quieras hacer esto tantas millones de veces que el rendimiento incluso se vuelve una consideración).

Esta versión:

  • usa BigDecimals para precisión y para evitar problemas de redondeo
  • funciona para redondear según lo solicitado por el OP
  • funciona para otros modos de redondeo, por ejemplo, HALF_UPcomo en las pruebas
  • le permite ajustar la precisión (cambio REQUIRED_PRECISION)
  • usa un enumpara definir los umbrales, es decir, podría ajustarse fácilmente para usar KB / MB / GB / TB en lugar de k / m / b / t, etc., y por supuesto podría extenderse más alláTRILLION si es necesario
  • viene con pruebas unitarias exhaustivas, ya que los casos de prueba en la pregunta no estaban probando las fronteras
  • debería funcionar para cero y números negativos

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

(Descomente las printlndeclaraciones o cambie para usar su registrador favorito para ver qué está haciendo).

Y finalmente, las pruebas en NumberShortenerTest (JUnit 4 simple):

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

Siéntase libre de señalar en los comentarios si omití un caso de prueba significativo o si los valores esperados deberían ajustarse.

Amos M. Carpenter
fuente
El único inconveniente obvio en su solución parecen las barras de desplazamiento V + H para su código, esto reduce la legibilidad. ¿Crees que un reformateo sería posible sin perder claridad?
Wolf
@Wolf: esperaba poder copiar / pegar desde mi IDE, pero tienes razón, es hipócrita de mi parte reclamar legibilidad y requerir desplazamiento horizontal, así que gracias por señalarlo. ;-) He actualizado los primeros dos bits de código, ya que esos son los que estarías viendo para ver qué está sucediendo, pero dejé el código de prueba, ya que mirarlo por sí solo no es tan útil. probablemente quiera pegar eso en su propio IDE para ejecutar las pruebas unitarias si desea ver que las pruebas funcionan. Espero que esté bien.
Amos M. Carpenter
Ah bueno. Pero en el último cuadro, los casos de prueba, los resultados esperados podrían estar, ópticamente, mejor relacionados con las entradas (me refiero a los literales en los primeros 6 arreglos).
Wolf
@Wolf: No soy fanático de tratar de alinear elementos en una línea con espacios o pestañas, eso no se puede configurar fácilmente de manera consistente para todos los casos en mi formateador favorito (eclipse), y hacerlo manualmente ... de esa manera yace la locura , debido a todos los ajustes que tiene que hacer cada vez que agrega o elimina un elemento. Si realmente quisiera verlos alineados, simplemente pegaría los números / valores en una hoja de cálculo como CSV.
Amos M. Carpenter
1
Todo depende de lo que buscas, @assylias. Si justo después de resolver un caso de uso único, su solución debería funcionar bien; Me gusta el TreeMapacercamiento. La "legibilidad" es subjetiva, por supuesto. ;-) Ahora, ¿qué pasa si alguien quiere redondear de manera diferente a truncar en su versión? (Por ejemplo, cuando se usa esto para indicar el tamaño del archivo, ¿quién querría truncar?) Si desea potencias de 2 en lugar de 10? Tendrías que reescribir un poco, ¿no? Como dije, deliberadamente no estaba tratando de jugar golf mi código, gran parte del cual podría haberse acortado (por ejemplo, nunca mantendría un if-then en una línea).
Amos M. Carpenter
4

Este es mi código. Limpio y sencillo.

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

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

}
Ebrahim sadeghi
fuente
2

Agregando mi propia respuesta, código Java, código autoexplicativo ...

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

}
LearningDeveloper
fuente
1

Este fragmento de código es simplemente mortal, simple y limpio, y funciona 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);
    }
}
KimKha
fuente
1

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

Salida:

1000 => 1.0 K

5821 => 5.8 K

10500 => 10.5 K

101800 => 1.0 L

2000000 => 20.0 L

7800000 => 78.0 L

92150000 => 9.2 Cr

123200000 => 12.3 Cr

9999999 => 100.0 L

999 => 999
Ankit Singh
fuente
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);
}
usuario2089762
fuente
2
Esto no funciona para todos sus casos límite. Prueba 999 por ejemplo.
jzd