Как перейти к форматированию 1200 в 1.2k в Java - PullRequest
141 голосов
/ 21 января 2011

Я бы хотел отформатировать следующие числа в числа рядом с ними с помощью 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

Число справа будет длинным или целым, число слева будет строкой. Как я должен подойти к этому. Я уже разработал небольшой алгоритм для этого, но я подумал, что, может быть, уже что-то придумано, что делает его лучше и не требует дополнительного тестирования, если я начинаю работать с миллиардами и триллионами:)

Дополнительные требования:

  • Формат должен содержать не более 4 символов
  • Вышеуказанное означает, что 1.1k в порядке, 11.2k - нет. То же самое для 7,8 м в порядке, 19,1 м нет. Только одна цифра перед десятичной точкой может иметь десятичную точку. Две цифры перед десятичной точкой означают не цифры после десятичной точки.
  • Округление не требуется. (Числа, отображаемые с добавленными символами k и m, являются скорее аналоговыми индикаторами, указывающими на приближение, а не на точную статью логики. Следовательно, округление не имеет значения, главным образом из-за природы переменной, которое может увеличивать или уменьшать несколько цифр, даже если вы смотрите на результат в кэше.)

Ответы [ 22 ]

131 голосов
/ 05 июня 2015

Вот решение, которое работает для любого длинного значения и которое я нахожу вполне читабельным (основная логика сделана в трех нижних строках метода format).

Используется TreeMap для поиска подходящего суффикса.Это на удивление более эффективно, чем предыдущее решение, написанное мной, в котором использовались массивы, и его было труднее читать.

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

Тестовый код

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);
  }
}
92 голосов
/ 21 января 2011

Я знаю, это больше похоже на программу на C, но она очень легкая!

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

}

Это выводит:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
42 голосов
/ 21 января 2011

Вот решение, которое использует инженерную нотацию 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;
}

Выход:

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
19 голосов
/ 21 января 2011

Нужны некоторые улучшения, но: StrictMath на помощь!
Вы можете поместить суффикс в String или массив и вызывать их на основе мощности или чего-то подобного.
Делением также можно управлять вокругсила, я думаю, что почти все о силовой ценности.Надеюсь, это поможет!

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

выходы:

999
1,2k
98k
911k
1,1 м
11b
712b
34t

15 голосов
/ 23 июля 2014

Проблемы с текущими ответами

  • Многие из существующих решений используют эти префиксы k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 .Однако, согласно различным источникам , правильные префиксы: k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Отсутствие поддержки отрицательных чисел (или, по крайней мере, отсутствие тестов, демонстрирующих, что отрицательные числа поддерживаются)
  • Отсутствиеподдержки обратной операции, например, преобразование 1.1k в 1100 (хотя это выходит за рамки исходного вопроса)

Java Solution

Это решение (расширение этот ответ ) решает вышеуказанные проблемы.

<code>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
     * }
     * 
* * @param source число, которое может иметь префикс метрики * @param pos, если синтаксический анализ завершается успешно, его следует обновить до индекса после последнего проанализированногосимвол * @return a Number, если строка является числом без префикса метрики, или Long, если он имеет префикс метрики * / @Override public Object parseObject (String source, ParsePosition pos) {if (NumberUtils.isNumber (source)) {// если значение является числом (без префиксаix) не возвращайте его как Long, иначе мы потеряем все десятичные числа pos.setIndex (source.length ());вернуть toNumber (источник);} else if (METRIC_PREFIXED_NUMBER.matcher (source) .matches ()) {boolean isNegative = source.charAt (0) == '-';int length = source.length ();String number = isNegative?source.substring (1, длина - 1): source.substring (0, длина - 1);String metricPrefix = Character.toString (source.charAt (длина - 1));Number absoluteNumber = toNumber (number);int index = 0;for (; index

Groovy Solution

Решение изначально было написано в Groovy, как показано ниже.

<code>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
     * }
     * 
* * @param source число, которое может иметь метрический префикс *@param pos, если синтаксический анализ выполнен успешно, его следует обновить до индекса после последнего проанализированного символа * @return a Number, если строка является числом без префикса метрики, или Long, если он имеет префикс метрики * / @Override ObjectparseObject (String source, ParsePosition pos) {if (source.isNumber ()) {// если значение является числом (без префикса), не возвращайте его как Long или мы потеряем все десятичные числа pos.index =source.size () toNumber (source)} иначе if (METRIC_PREFIXED_NUMBER.matcher (source) .matches ()) {логический isNegative = source [0] == '-' Строковый номер = isNegative?source [1 ..- 2]: source [0 ..- 2] Строка metricPrefix = источник [-1] Число absoluteNumber = toNumber (число) Целочисленный показатель = 3 * METRIC_PREFIXES.indexOf (metricPrefix) Длинный коэффициент = 10 ** показательфактор * = отрицательный?-1: 1 pos.index = source.size () (absoluteNumber * factor) as Long}} частный статический номер toNumber (номер строки) {NumberUtils.createNumber (number)}}

Тесты (Groovy)

Тесты написаны на Groovy, но их можно использовать для проверки либо Java, либо класса Groovy (поскольку они оба имеют одинаковое имя и 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')
        }
    }
}
11 голосов
/ 12 апреля 2011

ICU lib имеет основанный на правилах форматер для чисел, который можно использовать для записи чисел и т. Д. Я думаю, что использование ICU даст вам удобочитаемое и удобное решение.

[Использование]

Правильный класс - RuleBasedNumberFormat.Сам формат может быть сохранен как отдельный файл (или как строковая константа, IIRC).

Пример из 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);

На этой же странице показаны римские цифры, поэтому, я думаю, ваш случай тоже возможен.

8 голосов
/ 07 июня 2015

Важное замечание: Приведение ответов к double завершится с ошибкой для таких чисел, как 99999999999999999L и возврат 100P вместо 99P, поскольку double использует IEEE стандарт :

Если десятичная строка с не более 15 значащих цифр преобразуется в представление двойной точности IEEE 754, а затем обратно в строку с тем же количеством значащих цифр,тогда последняя строка должна соответствовать оригиналу.[long имеет до 19 значащих цифр .]

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

Это решение отсекает ненужные цифры и работает для всех значений long.Простая, но эффективная реализация (сравнение ниже).-120k нельзя выразить 4-мя символами, даже -0.1M слишком длинно, поэтому для отрицательных чисел должно быть 5 символов:

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

Тест в else if в началеявляется необходимым, поскольку минимальное значение равно -(2^63), а максимальное значение равно (2^63)-1, и, следовательно, назначение number = -number не будет выполнено, если number == Long.MIN_VALUE.Если нам нужно выполнить проверку, то мы можем также включить как можно больше чисел, вместо того, чтобы просто проверять number == Long.MIN_VALUE.

Сравнение этой реализации с тем, кто получил наибольшее количество голосов (сказаноБыть самым быстрым в настоящее время) показал, что это более чем в 5 раз быстрее (это зависит от настроек теста, но при большем количестве выигрыш становится больше, и эта реализация должна делать больше проверок, потому что она обрабатывает все случаи,так что, если другой будет исправлен, разница станет еще больше).Это происходит так быстро, потому что здесь нет операций с плавающей запятой, логарифма, мощности, рекурсии, регулярных выражений, сложных средств форматирования и минимизации количества создаваемых объектов.


Вот тестовая программа:

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

}

Возможный вывод: 2309 vs. 11591 (примерно то же самое, когда используются только положительные числа, и намного более экстремальный при изменении порядка выполнения, возможно, это как-то связано со сборкой мусора)

7 голосов
/ 11 июня 2015

Следующий код показывает, как вы можете сделать это, имея в виду простое расширение.

"Волшебство" заключается в основном в функции makeDecimal, которая при правильных переданных значениях гарантирует, что в выводе никогда не будет больше четырех символов.

Сначала извлекаются целые и десятые доли для данного делителя, поэтому, например, 12,345,678 с делителем 1,000,000 даст значение whole 12 и значение tenths 3.

Исходя из этого, он может решить, будет ли он выводить только всю часть или как целую, так и десятую часть, используя правила:

  • Если десятая часть равна нулю, просто выведите целую часть и суффикс.
  • Если целая часть больше девяти, просто выведите целую часть и суффикс.
  • В противном случае выведите целую часть, десятую часть и суффикс.

Код для этого:

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

Тогда достаточно просто вызвать вспомогательную функцию с правильными значениями, включая некоторые константы, чтобы облегчить жизнь разработчику:

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

Тот факт, что функция makeDecimal выполняет грубую работу, означает, что расширение за пределы 999,999,999 - это просто вопрос добавления дополнительной строки к Xlat, настолько легко, что я сделал это для вас.

Финальный return в Xlat не нуждается в условном выражении, так как наибольшее значение, которое вы можете хранить в 64-битной длинной со знаком, составляет всего около 9,2 квинтиллионов.

Но если по каким-то странным требованиям Oracle решит добавить 128-битный тип longer или 1024-битный damn_long, вы будете к этому готовы: -)


И, наконец, небольшой тестовый комплект, который вы можете использовать для проверки функциональности.

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

Из вывода видно, что он дает то, что вам нужно:

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

И, кроме того, помните, что передача отрицательного числа в эту функцию приведет к слишком длинной строке для ваших требований, поскольку она следует по пути < THOU). Я подумал, что все в порядке, поскольку в вопросе вы упоминаете только неотрицательные значения.

7 голосов
/ 17 июня 2016

Для тех, кто хочет округлить.Это отличное, простое для чтения решение, использующее преимущества библиотеки 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);
        }
    }
7 голосов
/ 11 июня 2015

Вот короткая реализация без рекурсии и просто очень маленький цикл.Не работает с отрицательными числами, но поддерживает все положительные long с до 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);
}

Выходы:

1k
5.8k
10k
101k
2 м
7,8 м
92 м
123 м
9,2 э (это Long.MAX_VALUE)

Я также провел несколько очень простых тестов производительности(форматирование 10 миллионов случайных длин) и это значительно быстрее, чем реализация Илии, и немного быстрее, чем реализация ассилий.

Mine: 1137.028 мс
Элайджа: 2664.396 мс
ассилий: 1373.473 мс

...