Як піти про форматування від 1200 до 1.2k у Java


156

Я хотів би відформатувати наступні номери в номери поруч із ними:

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,1 к нормально, 11,2 к не є Те саме для 7,8м гаразд 19,1м ні. До десяткової коми дозволено мати лише одну цифру перед десятковою комою. Двозначні цифри перед десятковою комою означають не цифри після десяткових знаків.
  • Закруглення не потрібно. (Числа, що відображаються з доданими k і m, - це більше аналоговий калібр, що вказує на наближення, а не на точну статтю логіки. Отже, округлення не має значення в основному через характер змінної, ніж може збільшувати або зменшувати кілька цифр, навіть якщо ви дивитесь на кешований результат.)

1
Якщо у вас немає бібліотеки, ви б не хотіли розмістити свій код?
Граммін

1
Це може допомогти, хоча це не копія. stackoverflow.com/questions/529432
rfeak

1
@Mat мені було цікаво, яке рішення ви використовували раніше. Якщо ви не заперечуєте, чи не опублікуєте це як відповідь.
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);
  }
}

1
Приємне рішення. Схоже, ви можете просто додати більше суфіксів для таких дійсно великих чисел (квадрильйон, квінтільйон тощо), і результат продовжує масштабуватися.
Cypher

Ваш код не зовсім правильний з від'ємними числами: його -5821слід відформатувати як -5k, а не як -5.8k.
std.denis

1
@ std.denis В ОП не вказано, як форматувати від'ємні числа. Я вирішив відформатувати їх як позитивні числа, але з префіксом, -щоб зберегти однакову кількість значущих цифр. Є й інші варіанти ...
assylias

1
По-перше: я видалив погані коментарі, адже це, очевидно, не ваша вина. По-друге: Справа не в тому, що хороші відповіді не приділяють належної уваги до тих пір, поки вони отримують більше, ніж інші, але, оскільки саме вам часто доводиться копатись за хорошими відповідями, і лише якась неправильна, погана або загальна відповідь отримує перевагу (дійсно погано вивчати нові речі). А для людей, які видають щедрості, коли вже є багато відповідей, я б очікував чіткіше уточнити, чого не вистачає, а потім ретельно вибирати відповідь, яка найкраще відповідає критеріям ...
maraca

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
Неясний код. Нам нині не потрібно кодувати так. Може працювати, як очікувалося, але я б закликав автора ознайомитись з Роджером К. Мартін: Чистий кодекс
Андреас Долк

29
Заплутаний? Прошу пробачення, але ви, мабуть, прочитали одну книгу і думаєте, що в даний час можете кодувати якось інакше. Розкажіть про це Джоелу ( 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

@Mat Оновлено для вирішення нових вимог
квітня 1111

Чи є простий спосіб поєднати це з екземпляром валюти, щоб отримати подібну функціональність з валютою?
xdumaine

@roviuser, не впевнений, що ти маєш на увазі, але це звучить як окреме запитання.
jzd

7
раунди 160000 до 200k, а також 120000 вниз до 100k
k1komans

4
Це зламано, я ввів число 10000000000000.0 і на ньому написано 103.
Олівер Діксон,

23

Потрібно певне вдосконалення, але: StrictMath на допомогу!
Ви можете помістити суфікс у String або масив і отримати fetch'em на основі потужності, або щось подібне.
Поділом також можна керувати навколо влади, я думаю, майже все стосується значення потужності. Сподіваюся, це допомагає!

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


2
Трохи поліпшена читабельність, Просто потрібно було додати операцію повернення від jzd, щоб вирішити 4 питання char. І не забудьте додати суфікс, якщо переходите 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 Рішення

Рішення спочатку було написано 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 Lib має форматчік правило , заснований для чисел, які можуть бути використані для числа spellout і т.д. Я думаю , що з допомогою ICU дасть вам читане і maintanable рішення.

[Використання]

Правильний клас - RuleBasedNumberFormat. Сам формат може зберігатися як окремий файл (або як String константа, 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);

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


Єдине рішення в потоці, яке не розпадається повністю, якщо вам потрібна локалізація.
Grozz

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"

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на початку є необхідним, тому що min є, -(2^63)а max є, (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

Ось коротка реалізація без рекурсії та просто дуже маленького циклу. Не працює з негативними числами, але підтримує всі додатні longs до 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 мс


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шляху). Я подумав, що це нормально, оскільки в питанні ви зазначаєте лише негативні значення.


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

Моя функція перетворення великого числа в мале число (з двома цифрами). Ви можете змінити кількість цифр шляхом зміни #.##в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 к", а не "1 к", але я вважаю, що це не має значення.

Щоб відповідати більш конкретній вимозі "не більше чотирьох символів", видаліть пробіли перед суфіксами та відрегулюйте середній блок так:

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), без зайвих коментарів (я вірю в код самодокументування), не турбуючись про продуктивність (оскільки я не можу уявити сценарій, коли ви хочете зробити це стільки мільйонів разів, що продуктивність навіть стає врахуванням).

Ця версія:

  • використовує BigDecimals для точності та уникнення проблем округлення
  • працює для округлення, як того вимагає ОП
  • працює для інших режимів округлення, наприклад, HALF_UPяк у тестах
  • дозволяє регулювати точність (зміна REQUIRED_PRECISION)
  • використовує enumдля визначення порогових значень, тобто легко може бути налаштований на використання кбайт / MB / GB / ТБ замість к / м / б / т, і т.д., і може бути , звичайно, виходить за рамки , TRILLIONякщо потрібно
  • поставляється з ретельними одиничними тестами, оскільки тестові випадки у питанні не перевіряли межі
  • повинні працювати для нульових і від’ємних чисел

Поріг.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: Я не прихильник намагатися вирівняти елементи в рядку з пробілами або вкладками - це не можна легко конфігурувати послідовно для всіх випадків у моєму улюбленому форматері (затемнення), і робити це вручну ... таким чином лежить божевілля , через всі налаштування, які ви повинні вносити щоразу, коли ви додаєте або видаляєте елемент. Якби мені дуже хотілося, щоб їх вирівняли, я б просто вставити числа / значення в електронну таблицю як 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;
    }

}

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

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
}

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

2
це не працює для всіх ваших крайових випадків. Спробуйте, наприклад, 999
jzd
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.