Como proceder para formatar 1200 a 1,2k em java

157

Eu gostaria de formatar os seguintes números nos números próximos a eles com 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

O número à direita será longo ou inteiro, o número à esquerda será string. Como devo abordar isso. Eu já fiz pouco algoritmo para isso, mas pensei que talvez já houvesse algo inventado por aí que faça um trabalho melhor e não exija testes adicionais se eu começar a lidar com bilhões e trilhões :)

Requisitos adicionais:

  • O formato deve ter no máximo 4 caracteres
  • O acima significa 1,1k é OK 11,2k não é. Mesmo para 7,8m está OK 19,1m não é. Apenas um dígito antes do ponto decimal pode ter ponto decimal. Dois dígitos antes do ponto decimal não significa dígitos após o ponto decimal.
  • Não é necessário arredondar. (Os números exibidos com k e m anexados são mais medidores analógicos, indicando um artigo lógico não aproximado. Portanto, o arredondamento é irrelevante principalmente devido à natureza da variável que pode aumentar ou decretar vários dígitos, mesmo enquanto você está observando o resultado em cache.)
Mat B.
fonte
1
Se ninguém tiver uma biblioteca, você se importaria de publicar seu código?
Grammin
1
Isso pode ajudar, embora isso não seja uma bobagem. stackoverflow.com/questions/529432
rfeak 20/01
1
@ Mat Eu estava curioso para saber qual solução você estava usando antes. Se você não se importa, você também o postará como resposta.
jzd
1
Qual é a idéia por trás No rounding is necessarydisso parece absurdo para mim. É apenas para complicar as coisas? Não seria melhor reformular isso Rounding is not necessary, but welcome?
Wolf
1
Caso você não tenha notado que os números exibidos com k e m anexados são mais do medidor analógico, indicando um artigo lógico não aproximado da aproximação. Portanto, o arredondamento é irrelevante principalmente devido à natureza da variável que pode aumentar ou decretar vários dígitos, mesmo enquanto você está olhando para o resultado descontado.
Mat B.

Respostas:

155

Aqui está uma solução que funciona com qualquer valor longo e que eu acho bastante legível (a lógica principal é feita nas três linhas inferiores do formatmétodo).

Ele utiliza TreeMappara encontrar o sufixo apropriado. É surpreendentemente mais eficiente do que uma solução anterior que escrevi que usava matrizes e era mais difícil de ler.

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 teste

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);
  }
}
assylias
fonte
1
Ótima solução. Parece que você pode simplesmente adicionar mais sufixos para esses números realmente grandes (quadrilhões, quintilhões, etc.), e a saída continua a aumentar.
Cypher
Seu código não está correto com números negativos: -5821deve ser formatado como -5k, não como -5.8k.
Std.denis
1
@ std.denis O OP não indicou como formatar números negativos. Decidi formatá-los como números positivos, mas prefixo com -para manter o mesmo número de dígitos significativos. Existem outras opções ...
assylias
1
Primeiro: apaguei os comentários ruins, porque obviamente não é sua culpa. Segundo: o problema não é que boas respostas não recebam atenção suficiente, contanto que recebam mais do que as outras, mas como geralmente é necessário procurar boas respostas e apenas algumas respostas erradas, ruins ou genéricas são aprovadas (realmente ruim aprender coisas novas). E para as pessoas que emitem bounties quando já que muitas respostas estão lá, eu teria esperado para especificar mais claramente o que está faltando e, em seguida, escolher cuidadosamente a resposta que se encaixa nos critérios melhor ...
maraca
1
mas o mundo inteiro entende esse padrão? tenha cuidado se você criar aplicativos para todos no mundo. Para o Inglês que 10M mas para russo é 10 млн e assim por diante
user924
101

Eu sei, isso parece mais um programa C, mas é super leve!

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

}

Emite:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Elijah Saounkine
fonte
16
Código ofuscado. Atualmente, não precisamos codificar dessa maneira. Pode funcionar como esperado, mas eu incentivaria o autor a dar uma olhada em Roger C. Martin: Código Limpo
Andreas Dolk
30
Ofuscado? Peço desculpas, mas você provavelmente lê um livro e pensa que pode codificar de alguma maneira diferente hoje em dia. Informe Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) sobre isso. Ouso qualquer código que você possa escrever para chegar perto da velocidade do meu método!
Elijah Saounkine
11
Mudando D, C, n variáveis para algo mais legível (mais rápido entendimento) faz este código decente na minha opinião
Gennadiy Ryabkin
5
Por que essa obsessão pelo desempenho? Por que alguém iria querer executar um número grande o suficiente dessas conversões para justificar o pensamento sobre o desempenho ...? Legibilidade primeiro, desempenho aprimorado apenas se necessário.
Amos M. Carpenter
10
Eu teria que concordar com @ AmosM.Carpenter. Mal sabia eu sobre manutenção de código quando escrevi essa resposta há 4 anos. Não é ruim otimizar, de um modo geral, MAS a legibilidade vem primeiro. A propósito, não é tão ruim em termos de desempenho: nem cinco vezes mais lento do que o que maraca escreveu - é quase o mesmo (eu coloquei algumas das soluções para uma referência aqui) github.com/esaounkine/number-format- referência ).
Elijah Saounkine
43

Aqui está uma solução que utiliza a notação de engenharia da 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;
}

Resultado:

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
fonte
@Mat Atualizado para lidar com novos requisitos
jzd 21/01
Existe uma maneira fácil de combinar isso com a Instância da moeda para obter funcionalidades semelhantes com a moeda?
Xdumaine 03/11
@roviuser, não sei o que você quer dizer, mas isso soa como uma pergunta separada.
jzd
7
rounds 160000 para 200k e também rodadas 120000 até 100k
k1komans
4
Isso está quebrado, eu digitei o número 10000000000000.0 e ele diz 103.
Oliver Dixon
23

Precisa de algumas melhorias, mas: StrictMath para o resgate!
Você pode colocar o sufixo em uma String ou matriz e buscá-los com base no poder, ou algo assim.
A divisão também pode ser gerenciada em torno do poder, acho que quase tudo é sobre o valor do poder. Espero que ajude!

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

saídas:

999
1.2K
98k
911k
1.1m
11b
712b
34T

jhurtado
fonte
2
Legibilidade aprimorada um pouco, basta adicionar a declaração de retorno do jzd para resolver o problema de 4 caracteres. E lembre-se de adicionar o sufixo se passar por t para evitar a exceção do AIOOB. ;)
jhurtado
Esse código é sensível ao código do idioma, por exemplo, no sv_SE locale 1000 converte em 10x10³, que não corresponde corretamente ao regexp.
Joakim Lundborg
2
lança uma exceção para 0, não funciona para números negativos, não rodada 9.999.999 adequadamente (impressões 10m) ...
assylias
16

Problemas com as respostas atuais

  • Muitas das soluções atuais estão usando esses prefixos k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . No entanto, de acordo com várias fontes , os prefixos corretos são k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Falta de suporte para números negativos (ou pelo menos uma falta de testes demonstrando que números negativos são suportados)
  • Falta de suporte para a operação inversa, por exemplo, conversão de 1,1k para 1100 (embora isso esteja fora do escopo da pergunta original)

Solução Java

Esta solução (uma extensão desta resposta ) soluciona os problemas acima.

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

Solução Groovy

A solução foi originalmente escrita em Groovy, como mostrado abaixo.

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

Testes (Groovy)

Os testes são gravados no Groovy, mas podem ser usados ​​para verificar a classe Java ou Groovy (porque ambos têm o mesmo nome e API).

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

class RoundedMetricPrefixFormatTests extends GroovyTestCase {

    private Format roundedMetricPrefixFormat = new RoundedMetricPrefixFormat()

    void testNumberFormatting() {

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

            assertEquals expectedRoundValue, roundedMetricPrefixFormat.format(rawValue)
        }
    }

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

        ].each { String metricPrefixNumber, Number expectedValue ->

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

    void testStringParsingFail() {

        shouldFail(ParseException) {
            roundedMetricPrefixFormat.parseObject('notNumber')
        }
    }
}
Dónal
fonte
1
Acho que você está pensando nos prefixos do CS, já que ele está falando de bilhões e trilhões, acho que ele quer números de pequena escala.
Jhurtado # 24/14
1
9999999 deve ser impresso como 9,9 milhões, acredito (os números são truncados, não arredondados).
Assilias
Esta solução não tem suporte para os prefixos para valores menores que 1, por exemplo, u (micro) e m (mili).
gbmhunter
13

A biblioteca ICU possui um formatador baseado em regras para números, que pode ser usado para digitar números etc. Acho que usar a ICU daria uma solução legível e sustentável.

[Uso]

A classe certa é RuleBasedNumberFormat. O formato em si pode ser armazenado como arquivo separado (ou como String constante, IIRC).

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

A mesma página mostra números romanos, então acho que seu caso também deve ser possível.

Landei
fonte
A única solução no encadeamento que não desmorona completamente se você precisar de localização.
Grozz 23/02
2
Se você precisar para o desenvolvimento do Android, isso já está incluído na estrutura. Procure CompactDecimalFormat. API Nível 24+
Gokhan Arik
10

Com o Java-12 + , você pode usar NumberFormat.getCompactNumberInstancepara formatar os números. Você pode criar um NumberFormatprimeiro como

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

e use-o para format:

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

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

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

Importante: A conversão de respostas para doublefalhará para números como 99999999999999999Le retornará em 100Pvez de 99Pporque doubleusa o IEEEpadrão :

Se uma sequência decimal com no máximo 15 dígitos significativos for convertida em representação de precisão dupla IEEE 754 e depois convertida novamente em uma sequência com o mesmo número de dígitos significativos, a sequência final deverá corresponder ao original. [ longtem até 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 solução corta dígitos indesejados e funciona para todos os longvalores . Implementação simples, mas com bom desempenho (comparação abaixo). -120k não pode ser expresso com 4 caracteres, até -0,1M é muito longo, por isso, para números negativos, 5 caracteres devem estar bem:

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

O teste no else ifinício é necessário porque o mínimo é -(2^63)e o máximo é (2^63)-1e, portanto, a atribuição number = -numberfalharia se number == Long.MIN_VALUE. Se precisarmos fazer uma verificação, podemos incluir o maior número possível de números, em vez de apenas procurar number == Long.MIN_VALUE.

A comparação desta implementação com a que obteve o maior número de votos (atualmente considerado o mais rápido) mostrou que é mais de 5 vezes mais rápido (depende das configurações do teste, mas com mais números o ganho fica maior e esta implementação tem para fazer mais verificações porque lida com todos os casos; portanto, se o outro for corrigido, a diferença se tornará ainda maior). É tão rápido porque não há operações de ponto flutuante, logaritmo, potência, recursão, regex, formatadores sofisticados e minimização da quantidade de objetos criados.


Aqui está o programa de teste:

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

}

Saída possível: 2309 vs. 11591(aproximadamente o mesmo quando se usa apenas números positivos e muito mais extremo ao reverter a ordem de execução, talvez isso tenha algo a ver com a coleta de lixo)

maraca
fonte
8

Aqui está uma pequena implementação sem recursão e apenas um loop muito pequeno. Não funciona com números negativos, mas suporta todos os longs positivos até 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);
}

Saídas:

1k
5.8k
10k
101k
2m
7,8 milhões
92m
123m
9.2e (isso é Long.MAX_VALUE)

Também fiz alguns testes simples (formatação de 10 milhões de longitudes aleatórias) e é consideravelmente mais rápido que a implementação de Elijah e um pouco mais rápido que a implementação de assylias.

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

Raniz
fonte
1
Na sua última atualização, você adicionou um bug. Agora ele retorna 1k para o número 101800 .
Sufian
2
Obrigado por reparar, ele é fixo
Raniz
8

Para quem quer arredondar. Esta é uma solução ótima e fácil de ler, que tira proveito da 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
fonte
8

O código a seguir mostra como você pode fazer isso com a expansão fácil em mente.

A "mágica" está principalmente na makeDecimalfunção que, pelos valores corretos transmitidos, garante que você nunca terá mais de quatro caracteres na saída.

Primeiro, extrai a parte inteira e a décima parte de um determinado divisor, de modo que, por exemplo, 12,345,678com um divisor de 1,000,000, forneça um wholevalor de 12e um tenthsvalor de 3.

A partir disso, ele pode decidir se produz apenas a parte inteira ou a parte inteira e a décima parte, usando as regras:

  • Se a décima parte for zero, basta imprimir a parte inteira e o sufixo.
  • Se a parte inteira for maior que nove, basta imprimir a parte inteira e o sufixo.
  • Caso contrário, produza parte inteira, décima parte e sufixo.

O código para o seguinte:

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

Em seguida, basta chamar essa função auxiliar com os valores corretos, incluindo algumas constantes para facilitar a vida do desenvolvedor:

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

O fato de a makeDecimalfunção fazer o trabalho pesado significa que expandir além 999,999,999é apenas uma questão de adicionar uma linha extra a Xlat, tão fácil que eu fiz para você.

A final returnem Xlatnão precisa de uma condicional desde o maior valor que você pode segurar em um 64-bit assinado longa é de apenas cerca de 9,2 quintilhões.

Mas se, por algum requisito bizarro, a Oracle decidir adicionar um longertipo de 128 bits ou um de 1024 bits damn_long, você estará pronto para isso :-)


E, finalmente, um pouco de equipamento de teste que você pode usar para validar a funcionalidade.

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

Você pode ver na saída que ela fornece o que você precisa:

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

E, como um aparte, esteja ciente de que passar um número negativo para essa função resultará em uma sequência muito longa para seus requisitos, pois segue o < THOUcaminho). Achei que estava tudo bem, já que você menciona apenas valores não negativos na pergunta.

paxdiablo
fonte
6

Não sei se é a melhor abordagem, mas foi o que fiz.

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 Aviles
fonte
6

Minha função para converter número grande em número pequeno (com 2 dígitos). Você pode alterar o número de dígitos pela mudança #.##naDecimalFormat

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

Teste

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 ajude

Phan Van Linh
fonte
5

Meu Java está enferrujado, mas aqui está como eu o implementaria em 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");
    }

Seria fácil ajustar isso para usar quilos de CS (1.024) em vez de quilos métricos ou para adicionar mais unidades. Ele formata 1.000 como "1,0 k" em vez de "1 k", mas acredito que isso é irrelevante.

Para atender ao requisito mais específico "não mais que quatro caracteres", remova os espaços antes dos sufixos e ajuste o bloco do meio da seguinte maneira:

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

fonte
1
Infelizmente, esse ToStringmétodo não existe em Java - você precisaria de um NumberFormat que possa criar outros problemas (sensíveis ao código do idioma etc.).
Assilias
5

Meu favorito. Você também pode usar "k" e assim por diante como indicador para decimal, como é comum no domínio eletrônico. Isso lhe dará um dígito extra sem espaço adicional

A segunda coluna tenta usar o máximo de dígitos possível

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 é o 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?";
}
}
stefan bachert
fonte
4

Mantendo-me fiel ao meu comentário de que eu valorizaria a legibilidade acima do desempenho, aqui está uma versão em que deve ficar claro o que está acontecendo (supondo que você tenha usado BigDecimals antes) sem comentar excessivamente (acredito em código de auto-documentação), sem se preocupar com o desempenho (como não consigo imaginar um cenário em que você deseje fazer isso tantas milhões de vezes que o desempenho se torna uma consideração).

Esta versão:

  • usa BigDecimals para precisão e para evitar problemas de arredondamento
  • trabalha para arredondar para baixo, conforme solicitado pelo OP
  • funciona para outros modos de arredondamento, por exemplo, HALF_UPcomo nos testes
  • permite ajustar a precisão (alterar REQUIRED_PRECISION)
  • usa um enumpara definir os limites, ou seja, pode ser facilmente ajustado para usar KB / MB / GB / TB em vez de k / m / b / t, etc. e pode, obviamente, ser estendido além, TRILLIONse necessário
  • vem com testes de unidade completos, pois os casos de teste na pergunta não estavam testando as fronteiras
  • deve funcionar para números zero e 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;
  }
}

(Remova o comentário das printlninstruções ou altere para usar seu criador de logs favorito para ver o que está fazendo.)

E, finalmente, os testes no NumberShortenerTest (simples 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));
    }
  }
}

Sinta-se à vontade para apontar nos comentários se perdi um caso de teste significativo ou se os valores esperados devem ser ajustados.

Amos M. Carpenter
fonte
A única desvantagem óbvia em sua solução parece as barras de rolagem V + H para seu código, isso reduz a legibilidade. Você acha que uma reformatação seria possível sem perder a clareza?
Lobo
@ Wolf: Estava esperando fugir com copiar / colar do meu IDE, mas você está certo, é hipócrita da minha parte reivindicar legibilidade e exigir rolagem horizontal, então obrigado por apontar isso. ;-) Atualizei os dois primeiros bits do código, pois você verá o que está acontecendo para ver o que está acontecendo, mas deixei o código de teste, pois analisá-lo por si só não é tão útil - você ' Provavelmente, você deseja colar isso em seu próprio IDE para executar os testes de unidade, se quiser ver se os testes funcionam. Espero que esteja tudo bem.
Amos M. Carpenter
Ah bom. Mas, na última caixa, nos casos de teste, os resultados esperados poderiam estar - opticamente - melhor relacionados às entradas (refiro-me aos literais nas 6 primeiras matrizes).
Lobo
@Wolf: Eu não sou fã de tentar alinhar itens em uma linha com espaços ou guias - que não podem ser facilmente configurados de forma consistente para todos os casos no meu formatador favorito (eclipse), e fazê-lo manualmente ... dessa maneira está loucura , devido a todos os ajustes que você deve fazer sempre que adicionar ou remover um item. Se eu realmente quisesse vê-los alinhados, bastava colar os números / valores em uma planilha como CSV.
Amos M. Carpenter
1
Tudo depende do que você procura, @assylias. Se você está logo após resolver um caso de uso único, sua solução deve funcionar bem; Eu gosto da TreeMapabordagem. "Legibilidade" é subjetivo, é claro. ;-) Agora, e se alguém quiser arredondar de forma diferente do que truncar na sua versão? (Por exemplo, ao usar isso para indicar o tamanho do arquivo, quem deseja truncar?) Se você deseja potências de 2 em vez de 10? Você teria que reescrever um pouco, não é? Como eu disse, eu não estava deliberadamente tentando jogar meu código, muito do que poderia ter sido reduzido (eu nunca manteria um if-then em uma linha, por exemplo).
Amos M. Carpenter
4

esse é o meu código limpo e simples.

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
fonte
2

Adicionando minha própria resposta, código Java, código auto-explicativo ..

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
fonte
1

Este trecho de código é simplesmente mortal, simples e limpo, e 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
fonte
1

tente isto:

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

Resultado:

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
fonte
0
//code longer but work sure...

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

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

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

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

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

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

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

    NumberFormat formatterHasDigi = new DecimalFormat("###,###,###");
    return formatterHasDigi.format(number);
}
user2089762
fonte
2
isso não funciona para todos os seus casos extremos. Tente 999, por exemplo.
Jzd #