Как перейти к форматированию 1200 до 1,2 КБ в Java

157

Я хотел бы отформатировать следующие числа в числа рядом с ними с помощью 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, являются скорее аналоговыми индикаторами, указывающими на приближение, а не на точную статью логики. Следовательно, округление не имеет значения, главным образом из-за природы переменной, которое может увеличивать или уменьшать несколько цифр, даже если вы смотрите на результат в кэше.)
Мат Б.
источник
1
Если бы ни у кого нет библиотеки, не могли бы вы опубликовать свой код?
Грэммин
1
Это может помочь, хотя это не обман. stackoverflow.com/questions/529432
rfeak
1
@ Мне было любопытно, какое решение вы использовали раньше. Если вы не возражаете, вы также можете опубликовать это как ответ.
JZD
1
Какая идея стоит за No rounding is necessaryэтим, мне кажется абсурдной. Это просто усложнить вещи? Не лучше ли перефразировать это Rounding is not necessary, but welcome?
Вольф
1
В случае, если вы не заметили, что числа, отображаемые с добавленными символами k и m, являются скорее аналоговой шкалой, указывающей приближение, а не точную статью логики. Следовательно, округление не имеет значения в основном из-за природы переменной, которая может увеличивать или уменьшать несколько цифр, даже если вы смотрите на полученный результат.
Мат Б.

Ответы:

155

Вот решение, которое работает для любого длинного значения и которое я нахожу вполне читабельным (основная логика сделана в трех нижних строках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);
  }
}
assylias
источник
1
Хорошее решение. Похоже, вы можете просто добавить больше суффиксов для этих действительно больших чисел (квадриллион, квинтиллион и т. Д.), И результат будет продолжать масштабироваться.
Cypher
Ваш код не совсем корректен с отрицательными числами: -5821должен быть отформатирован как -5k, а не как -5.8k.
std.denis
1
@ std.denis ОП не указал, как форматировать отрицательные числа. Я решил отформатировать их как положительные числа, но с префиксом, -чтобы сохранить то же количество значащих цифр. Есть и другие варианты ...
assylias
1
Первое: я удалил плохие комментарии, потому что это, очевидно, не ваша вина. Второе: проблема не в том, что хорошие ответы не получают достаточного внимания, если они получают больше, чем другие, но поскольку вам часто приходится искать хорошие ответы, и просто за неправильный, плохой или общий ответ ставят вопрос (действительно плохо учиться новым вещам). И для людей, выдающих награды, когда уже так много ответов, я бы ожидал более четко указать, чего не хватает, а затем тщательно выбрать ответ, который лучше всего соответствует критериям ...
Марака
1
но понимает ли весь мир этот стандарт? будьте осторожны, если вы делаете приложение для всех в мире. Для английского - 10 млн, для русского - 10 млн и т. Д.
user924
101

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

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
Элайджа Саункин
источник
16
Запутанный код. Нам не нужно кодировать это в наше время. Может работать, как и ожидалось, но я бы посоветовал автору взглянуть на Роджера С. Мартина: Чистый код
Андреас Долк
30
Запутанный? Прошу прощения, но вы, вероятно, прочитали одну книгу и думаете, что в наши дни вы можете писать по-другому. Расскажите об этом Джоэлу ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ). Я смею любой код, который вы можете написать, чтобы приблизиться к скорости моего метода!
Илия Саункин
11
Изменение переменных d, c, n на что-то более читаемое (более быстрое понимание) делает этот код достойным, на мой взгляд,
Геннадий Рябкин
5
Почему эта одержимость работой? Зачем кому-то хотеть выполнить достаточно большое количество этих преобразований, чтобы даже думать о производительности ...? Читаемость в первую очередь, настройка производительности только при необходимости.
Амос М. Карпентер
10
Я должен согласиться с @ AmosM.Carpenter. Когда я писал этот ответ 4 года назад, я мало что знал о возможности сопровождения кода. В общем, неплохо оптимизировать, но читаемость на первом месте. Между прочим, это не так плохо с точки зрения производительности: не в 5 раз медленнее, чем та, которую написал Марака, - примерно так же (некоторые решения для эталона я выложил здесь github.com/esaounkine/number-format- эталон ).
Илия Саункин
43

Вот решение, которое использует инженерную нотацию 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
JZD
источник
@Mat Обновлено, чтобы соответствовать новым требованиям
JZD
Есть ли простой способ объединить это с экземпляром валюты, чтобы получить аналогичную функциональность с валютой?
xdumaine
@roviuser, не уверен, что ты имеешь в виду, но это звучит как отдельный вопрос.
JZD
7
округляет 160000 до 200k, а также округляет 120000 до 100k
k1komans
4
Это сломано, я ввел номер 10000000000000.0 и там написано 103.
Оливер Диксон
23

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

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.1m
11b
712b
34t

jhurtado
источник
2
Немного улучшена читаемость, просто нужно добавить оператор возврата из jzd, чтобы решить проблему с 4 символами. И не забудьте добавить суффикс при переходе через t, чтобы избежать исключения AIOOB. ;)
jhurtado
Этот код чувствителен к локали, например, в sv_SE локаль 1000 преобразуется в 10x10³, что не соответствует правильному регулярному выражению.
Йоаким Лундборг
2
генерирует исключение для 0, не работает для отрицательных чисел, не
округляет
16

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

  • Многие из существующих решений используют эти префиксы 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

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

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

Groovy Solution

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

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

Тесты (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')
        }
    }
}
Донал
источник
1
Я думаю, что вы думаете о префиксах CS, учитывая, что он говорит о миллиардах и триллионах, я думаю, что он хочет использовать короткие цифры.
jhurtado
1
Я считаю, что 9999999 должен быть напечатан как 9,9 м (числа обрезаны, а не округлены).
assylias
Это решение не поддерживает префиксы для значений, которые меньше 1, например, u (микро) и m (милли).
gbmhunter
13

Библиотека ICU имеет основанный на правилах форматер для чисел, который можно использовать для записи чисел и т. Д. Я думаю, что использование 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);

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

Landei
источник
Единственное решение в потоке, которое не полностью разваливается, если вам нужна локализация.
Грозз
2
Если вам это нужно для разработки под Android, это уже включено в фреймворк. Ищите CompactDecimalFormat. Уровень API 24+
Гохан Арик,
10

С Java-12 + вы можете использовать NumberFormat.getCompactNumberInstanceдля форматирования чисел. Вы можете создать NumberFormatпервый как

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

а затем использовать его для format:

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

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

fmt.format(1000000000)
$11 ==> "1B"
Naman
источник
8

Важное замечание: Приведение ответов к 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(примерно то же самое, когда используются только положительные числа, и гораздо более экстремальный при изменении порядка выполнения, возможно, это как-то связано со сборкой мусора)

маракас
источник
8

Вот короткая реализация без рекурсии и просто очень маленький цикл. Не работает с отрицательными числами, но поддерживает все положительные значения 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
2m
7.8m
92m
123m
9.2e (это Long.MAX_VALUE)

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

Шахта: 1137,028 мс
Илии: 2664,396 мс
Ассилии: 1373,473 мс

Raniz
источник
1
В вашем последнем обновлении вы добавили ошибку. Теперь он возвращает 1k для номера 101800 .
Суфий
2
Спасибо за
замечание
8

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

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

«Магия» заключается в основном в 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пути). Я подумал, что все в порядке, поскольку в вопросе вы упоминаете только неотрицательные значения.

paxdiablo
источник
6

Я не знаю, лучший ли это подход, но это то, что я сделал.

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

--- код ---

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
}
Эдуардо Авилес
источник
6

Моя функция для преобразования большого числа в маленькое число (с 2 цифрами). Вы можете изменить количество цифр путем изменения #.##вDecimalFormat

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

тестирование

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

Надеюсь, это поможет

Фан Ван Линь
источник
5

Мой Java ржавый, но вот как я реализовал его в 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");
    }

Было бы легко настроить это, чтобы использовать килограммы CS (1024), а не метрические, или добавить больше единиц. Он форматирует 1000 как «1.0 k», а не «1 k», но я уверен, что это несущественно.

Чтобы удовлетворить более конкретное требование «не более четырех символов», удалите пробелы перед суффиксами и отрегулируйте средний блок следующим образом:

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

источник
1
К сожалению, этот ToStringметод не существует в Java - вам понадобится NumberFormat, который может создавать другие проблемы (чувствительные к локали и т. Д.).
assylias
5

Моя любимая. Вы можете использовать «k» и т. Д. В качестве индикатора для десятичной дроби, как это часто бывает в электронном домене. Это даст вам дополнительную цифру без дополнительного пробела

Второй столбец пытается использовать как можно больше цифр

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

Это код

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?";
}
}
Стефан Бахерт
источник
4

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

Эта версия:

  • использует BigDecimals для точности и чтобы избежать проблем округления
  • работает для округления в соответствии с просьбой ОП
  • работает для других режимов округления, например, HALF_UPкак в тестах
  • позволяет настроить точность (изменить REQUIRED_PRECISION)
  • использует enumдля определения пороговых значений, то есть может быть легко откорректирован для использования КБ / МБ / ГБ / ТБ вместо k / m / b / t и т. д. и, конечно, может быть расширен за пределыTRILLION если требуется
  • идет с тщательными юнит-тестами, так как тестовые случаи в вопросе не проверяли границы
  • должно работать на ноль и отрицательные числа

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

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

И, наконец, тесты в NumberShortenerTest (обычный 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));
    }
  }
}

Не стесняйтесь указывать в комментариях, если я пропустил значительный контрольный пример или ожидаемые значения должны быть скорректированы.

Амос М. Карпентер
источник
Единственным очевидным недостатком вашего решения являются полосы прокрутки V + H для вашего кода, что снижает читабельность. Как вы думаете, переформатирование будет возможно без потери ясности?
Вольф
@Wolf: Я надеялся избежать копирования / вставки из моей IDE, но вы правы, я лицемерно заявляю о читабельности и требую горизонтальной прокрутки, так что спасибо за указание на это. ;-) Я обновил первые два бита кода, так как это те, на которые вы будете смотреть, чтобы увидеть, что происходит, но оставил тестовый код, поскольку сам по себе просмотр этого не очень полезен - вы ' Возможно, вы захотите вставить это в свою собственную среду IDE, чтобы запустить модульные тесты, если хотите убедиться, что тесты работают. Надеюсь, что все в порядке.
Амос М. Карпентер
А, хорошо. Но в последнем блоке, в тестовых случаях, ожидаемые результаты могли бы - оптически - лучше соотноситься с входными данными (я имею в виду литералы в первых 6 массивах).
Вольф
@Wolf: я не фанат попыток выровнять элементы в строке с пробелами или табуляциями - это не может быть легко настроено последовательно для всех случаев в моем любимом форматере (eclipse), и делать это вручную ... это безумие из-за всех настроек, которые вы должны делать каждый раз, когда добавляете или удаляете элемент. Если бы я действительно хотел, чтобы они были выровнены, я просто вставил бы числа / значения в электронную таблицу как CSV.
Амос М. Карпентер
1
Все зависит от того, что вы ищете, @assylias. Если вы только после решения разового варианта использования, ваше решение должно работать нормально; Мне нравится TreeMapподход. «Читабельность», конечно, субъективна. ;-) А что если кто-то захочет округлить иначе, чем усечь в вашей версии? (Например, когда вы используете это, чтобы указать размер файла, кто бы хотел усечь?) Если вам нужны степени 2, а не 10? Тебе придется переписать немало, не так ли? Как я уже сказал, я сознательно не пытался «сыграть в гольф» свой код, большую часть которого можно было бы сократить (например, я никогда не держал бы строку «если-тогда»).
Амос М. Карпентер
4

это мой код чисто и просто.

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

}
Эбрахим Садеги
источник
2

Добавление моего собственного ответа, Java-код, понятный код ..

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
источник
1

Этот фрагмент кода просто смертельно простой, чистый код и полностью работает:

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
источник
1

попробуй это :

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
источник
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+"";
        }
    }
}

Вывод:

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
Анкит Сингх
источник
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
источник
2
это не работает для всех ваших крайних случаев. Попробуйте 999 например.
JZD