Jak przejść do formatowania 1200 do 1,2k w java

156

Chciałbym sformatować następujące liczby w liczby obok nich za pomocą 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

Liczba po prawej stronie będzie długa lub całkowita, a liczba po lewej będzie łańcuchem. Jak mam do tego podejść. Zrobiłem już mały algorytm do tego, ale pomyślałem, że może być już wymyślone coś, co robi fajniejszą robotę i nie wymaga dodatkowych testów, jeśli zacznę mieć do czynienia z miliardami i bilionami :)

Dodatkowe wymagania:

  • Format powinien mieć maksymalnie 4 znaki
  • Powyższe oznacza, że ​​1,1k jest OK 11,2k nie jest. To samo dla 7,8 m jest OK 19,1 m nie jest. Tylko jedna cyfra przed kropką dziesiętną może mieć kropkę dziesiętną. Dwie cyfry przed kropką dziesiętną oznaczają brak cyfr po przecinku.
  • Nie jest konieczne zaokrąglanie. (Liczby wyświetlane z dołączonymi k i m są raczej miernikiem analogowym wskazującym przybliżenie, a nie dokładny artykuł logiki. Dlatego zaokrąglanie jest nieistotne głównie ze względu na naturę zmiennej, ponieważ może zwiększyć lub zmniejszyć kilka cyfr, nawet gdy patrzysz na wynik zapisany w pamięci podręcznej.)
Mat B.
źródło
1
Jeśli nikt nie ma biblioteki, czy możesz opublikować swój kod?
Grammin,
1
Może to pomóc, chociaż to nie jest dupek. stackoverflow.com/questions/529432
rfeak
1
@Mat Byłem ciekawy, jakiego rozwiązania używasz wcześniej. Jeśli nie masz nic przeciwko, opublikowałbyś to również jako odpowiedź.
jzd
1
Jaka idea się za No rounding is necessarytym kryje, wydaje mi się absurdalna. Czy to tylko skomplikowanie sprawy? Czy nie lepiej byłoby to przeformułować Rounding is not necessary, but welcome?
Wolf
1
W przypadku, gdy nie zauważyłeś, że liczby wyświetlane z dołączonymi k i m są bardziej analogowymi miernikami wskazującymi przybliżenie, a nie dokładny artykuł logiczny. Dlatego zaokrąglanie jest nieistotne głównie ze względu na charakter zmiennej, która może zwiększyć lub zmniejszyć o kilka cyfr, nawet gdy patrzysz na spieniężony wynik.
Mat B.

Odpowiedzi:

155

Oto rozwiązanie, które działa dla każdej długiej wartości i które uważam za całkiem czytelne (podstawowa logika jest wykonana w trzech dolnych wierszach formatmetody).

Wykorzystuje, TreeMapaby znaleźć odpowiedni przyrostek. Jest zaskakująco wydajniejsze niż poprzednie rozwiązanie, które napisałem, które wykorzystywało tablice i było trudniejsze do odczytania.

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

Kod testowy

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);
  }
}
asylias
źródło
1
Niezłe rozwiązanie. Wygląda na to, że możesz po prostu dodać więcej przyrostków dla tych naprawdę dużych liczb (biliardów, kwintylionów itd.), A wynik będzie nadal skalowany.
Cypher
Twój kod nie jest całkiem poprawny z liczbami ujemnymi: -5821powinien być sformatowany jako -5k, a nie jako -5.8k.
std.denis
1
@ std.denis OP nie wskazał, jak sformatować liczby ujemne. Postanowiłem sformatować je jako liczby dodatnie, ale z prefiksem, -aby zachować tę samą liczbę cyfr znaczących. Są inne opcje ...
assylias
1
Po pierwsze: usunąłem złe komentarze, bo to oczywiście nie twoja wina. Po drugie: nie jest problemem to, że dobre odpowiedzi nie przyciągają wystarczającej uwagi, o ile przyciągają więcej uwagi niż inne, ale ponieważ często musisz szukać dobrych odpowiedzi, a tylko niektóre błędne, złe lub ogólne odpowiedzi są uznawane (naprawdę źle się uczyć nowych rzeczy). A dla osób przyznających nagrody, gdy jest już tyle odpowiedzi, spodziewałbym się, że sprecyzuję, czego brakuje, a następnie ostrożnie wybiorę odpowiedź, która najlepiej pasuje do kryteriów ...
maraca
1
ale czy cały świat rozumie ten standard? bądź ostrożny, jeśli tworzysz aplikację dla wszystkich na świecie. Dla języka angielskiego jest to 10 mln, ale dla języka rosyjskiego to 10 млн i tak dalej
user924
101

Wiem, to wygląda bardziej jak program w C, ale jest super lekki!

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

}

Wyprowadza:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Elijah Saounkine
źródło
16
Zamaskowany kod. W dzisiejszych czasach nie musimy tak kodować. Może działać zgodnie z oczekiwaniami, ale zachęcam autora do przyjrzenia się Rogerowi C. Martinowi: Clean Code
Andreas Dolk
29
Zamfuskowany? Przepraszam, ale prawdopodobnie przeczytałeś jedną książkę i myślisz, że w dzisiejszych czasach możesz kodować inaczej. Powiedz o tym Joelowi ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ). Odważę się na każdy kod, który możesz napisać, aby zbliżyć się do szybkości mojej metody!
Elijah Saounkine,
11
Zmiana d, c, n zmiennych na coś bardziej czytelnego (szybsze zrozumienie) sprawia, że ​​moim zdaniem ten kod jest przyzwoity
Gennadiy Ryabkin
5
Skąd ta obsesja na punkcie wydajności? Dlaczego ktoś miałby chcieć wykonać wystarczająco dużą liczbę tych konwersji, aby uzasadnić nawet myślenie o wydajności ...? Przede wszystkim czytelność, poprawianie wydajności tylko w razie potrzeby.
Amos M. Carpenter
10
Musiałbym się zgodzić z @ AmosM.Carpenter. Niewiele wiedziałem o łatwości utrzymania kodu, kiedy pisałem tę odpowiedź 4 lata temu. Ogólnie rzecz biorąc, optymalizacja nie jest zła, ALE czytelność jest najważniejsza. Nawiasem mówiąc, nie jest tak źle, jeśli chodzi o wydajność: nie 5 razy wolniej niż ten, który napisał maraca - jest mniej więcej taki sam (niektóre rozwiązania do testu porównawczego umieściłem tutaj github.com/esaounkine/number-format- benchmark ).
Elijah Saounkine
43

Oto rozwiązanie, które korzysta z notacji inżynierskiej 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;
}

Wynik:

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
źródło
@Mat Zaktualizowano w celu obsługi nowych wymagań
jzd
Czy istnieje łatwy sposób na połączenie tego z wystąpieniem waluty, aby uzyskać podobną funkcjonalność z walutą?
xdumaine
@roviuser, nie wiem, co masz na myśli, ale brzmi to jak osobne pytanie.
jzd
7
zaokrągla 160000 do 200 tys., a także zaokrągla 120000 w dół do 100 tys.
k1komans
4
To jest zepsute, wpisałem numer 10000000000000.0 i jest napisane 103.
Oliver Dixon
23

Potrzebujesz poprawy, ale: StrictMath na ratunek!
Możesz umieścić sufiks w łańcuchu lub tablicy i pobrać je na podstawie mocy lub coś w tym rodzaju.
Podziałem można również zarządzać wokół mocy, myślę, że prawie wszystko dotyczy wartości mocy. Mam nadzieję, że to pomoże!

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

wyjścia:

999
1,2 tys.
98
tys. 911 tys.
1,1 mln
11b
712b
34t

jhurtado
źródło
2
Trochę poprawiona czytelność, wystarczyło dodać instrukcję return z jzd, aby rozwiązać problem z 4 znakami. I pamiętaj, aby dodać sufiks, jeśli przekraczasz t, aby uniknąć wyjątku AIOOB. ;)
jhurtado
Ten kod jest wrażliwy na locale, na przykład w sv_SE locale 1000 konwertuje do 10x10³, co nie jest poprawnie dopasowane przez wyrażenie regularne.
Joakim Lundborg,
2
zgłasza wyjątek dla 0, nie działa dla liczb ujemnych, nie zaokrągla prawidłowo 9 999 999 (wypisuje 10 m) ...
asylias
16

Problemy z aktualnymi odpowiedziami

  • Wiele z obecnych rozwiązań używa tych przedrostków k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Jednak według różnych źródeł prawidłowe przedrostki to k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Brak wsparcia dla liczb ujemnych (lub przynajmniej brak testów wykazujących, że obsługiwane są liczby ujemne)
  • Brak wsparcia dla operacji odwrotnej, np. Zamiana 1.1k na 1100 (choć jest to poza zakresem pierwotnego pytania)

Rozwiązanie Java

To rozwiązanie (rozszerzenie tej odpowiedzi ) rozwiązuje powyższe problemy.

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

Rozwiązanie zostało pierwotnie napisane w Groovy, jak pokazano poniżej.

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

Testy (Groovy)

Testy są napisane w Groovy, ale można ich użyć do weryfikacji klasy Java lub Groovy (ponieważ obie mają tę samą nazwę i API).

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

class RoundedMetricPrefixFormatTests extends GroovyTestCase {

    private Format roundedMetricPrefixFormat = new RoundedMetricPrefixFormat()

    void testNumberFormatting() {

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

            assertEquals expectedRoundValue, roundedMetricPrefixFormat.format(rawValue)
        }
    }

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

        ].each { String metricPrefixNumber, Number expectedValue ->

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

    void testStringParsingFail() {

        shouldFail(ParseException) {
            roundedMetricPrefixFormat.parseObject('notNumber')
        }
    }
}
Dónal
źródło
1
Myślę, że myślisz o przedrostkach CS, biorąc pod uwagę, że mówi on o miliardach i bilionach, myślę, że chce liczb w małej skali.
jhurtado,
1
Wydaje mi się, że 9999999 powinno mieć 9,9 m (liczby są obcięte, a nie zaokrąglone).
assylias
To rozwiązanie nie obsługuje przedrostków dla wartości mniejszych niż 1, np. U (mikro) im (milli).
gbmhunter
13

Plik ICU lib ma formatowania reguł opartych na liczbach, które mogą być używane do liczby spellout itp myślę użyciu ICU nie daje czytelny i maintanable rozwiązanie.

[Stosowanie]

Właściwa klasa to RuleBasedNumberFormat. Sam format może być przechowywany jako oddzielny plik (lub jako stała String, IIRC).

Przykład z 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);

Ta sama strona pokazuje cyfry rzymskie, więc myślę, że twój przypadek też powinien być możliwy.

Landei
źródło
Jedyne rozwiązanie w wątku, które nie rozpada się całkowicie, jeśli potrzebujesz lokalizacji.
Grozz
2
Jeśli potrzebujesz go do programowania na Androida, jest to już uwzględnione we frameworku. Szukać CompactDecimalFormat. Poziom API 24+
Gokhan Arik,
10

W Javie-12 + możesz NumberFormat.getCompactNumberInstanceformatować liczby. Możesz utworzyć NumberFormatpierwszy plik jako

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

a następnie użyj go do format:

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

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

fmt.format(1000000000)
$11 ==> "1B"
Naman
źródło
8

Ważne: przesyłanie odpowiedzi na doublenie powiedzie się w przypadku liczb takich jak 99999999999999999Li zwraca 100Pzamiast, 99Pponieważ doubleużywa IEEEstandardu :

Jeśli ciąg dziesiętny zawierający co najwyżej 15 cyfr znaczących jest konwertowany na reprezentację podwójnej precyzji IEEE 754, a następnie konwertowany z powrotem na łańcuch o tej samej liczbie cyfr znaczących, to ostateczny ciąg powinien pasować do oryginału. [ longzawiera maksymalnie 19 cyfr znaczących ]

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

To rozwiązanie odcina niechciane cyfry i działa dla wszystkich longwartości . Prosta, ale wydajna implementacja (porównanie poniżej). -120k nie może być wyrażone za pomocą 4 znaków, nawet -0,1M jest za długie, dlatego dla liczb ujemnych 5 znaków musi być w porządku:

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

Test else ifna początku jest konieczny, ponieważ min jest, -(2^63)a maksimum jest, (2^63)-1a zatem przypisanie number = -numbernie powiedzie się, jeśli number == Long.MIN_VALUE. Jeśli musimy zrobić czek, możemy równie dobrze uwzględnić jak najwięcej liczb, zamiast tylko sprawdzać number == Long.MIN_VALUE.

Porównanie tej implementacji z tą, która uzyskała najwięcej głosów pozytywnych (podobno jest obecnie najszybsza) pokazało, że jest ona ponad 5 razy szybsza (zależy to od ustawień testowych, ale przy większej liczbie liczb zysk jest większy i ta implementacja ma aby wykonać więcej kontroli, ponieważ obsługuje wszystkie przypadki, więc jeśli drugi zostanie naprawiony, różnica będzie jeszcze większa). Jest tak szybki, ponieważ nie ma operacji zmiennoprzecinkowych, logarytmu, potęgi, rekursji, wyrażenia regularnego, wyrafinowanych elementów formatujących i minimalizacji ilości tworzonych obiektów.


Oto program testowy:

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

}

Możliwe dane wyjściowe: 2309 vs. 11591(mniej więcej to samo, gdy używamy tylko liczb dodatnich i znacznie bardziej ekstremalne podczas odwracania kolejności wykonywania, może ma to coś wspólnego z usuwaniem elementów bezużytecznych)

maraca
źródło
8

Oto krótka implementacja bez rekursji i tylko bardzo mała pętla. Nie działa z liczbami ujemnymi, ale obsługuje wszystkie dodatnie longdo 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);
}

Wyjścia:

1 km
5,8 km
10 km
101 km
2 m
7,8 km
92 m
123 m 9,2e
(to jest Long.MAX_VALUE)

Wykonałem też naprawdę proste testy porównawcze (formatowanie 10 milionów losowych długości) i jest to znacznie szybsze niż implementacja Elijaha i nieco szybsza niż implementacja asylias.

Mój: 1137.028 ms
Eliasz: 2664.396 ms
asylias ': 1373.473 ms

Raniz
źródło
1
W swojej ostatniej aktualizacji dodałeś błąd. Teraz zwraca 1k dla liczby 101800 .
Sufian,
2
Dzięki za zauważenie, to naprawione
Raniz
8

Dla każdego, kto chce zaokrąglić. To świetne, łatwe do odczytania rozwiązanie, które korzysta z biblioteki Java.Lang.Math

 public static String formatNumberExample(Number number) {
        char[] suffix = {' ', 'k', 'M', 'B', 'T', 'P', 'E'};
        long numValue = number.longValue();
        int value = (int) Math.floor(Math.log10(numValue));
        int base = value / 3;
        if (value >= 3 && base < suffix.length) {
            return new DecimalFormat("~#0.0").format(numValue / Math.pow(10, base * 3)) + suffix[base];
        } else {
            return new DecimalFormat("#,##0").format(numValue);
        }
    }
Chris
źródło
8

Poniższy kod pokazuje, jak możesz to zrobić, mając na uwadze łatwe rozszerzenie.

„Magia” tkwi głównie w makeDecimal funkcji, która przy prawidłowych przekazanych wartościach gwarantuje, że na wyjściu nie będzie więcej niż cztery znaki.

Najpierw wyodrębnia całość i części dziesiąte dla danego dzielnika, więc na przykład 12,345,678z dzielnikiem 1,000,000da wholewartość 12i tenthswartość3 .

Na tej podstawie może zdecydować, czy wyprowadza tylko całą część, czy zarówno całość, jak i części dziesiąte, stosując zasady:

  • Jeśli część dziesiąta jest równa zero, po prostu wypisz całą część i sufiks.
  • Jeśli cała część jest większa niż dziewięć, po prostu wypisz całą część i przyrostek.
  • W przeciwnym razie wypisz całą część, część dziesiątą i przyrostek.

Kod do tego jest następujący:

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

Następnie wystarczy wywołać tę funkcję pomocniczą z poprawnymi wartościami, w tym pewnymi stałymi, aby ułatwić życie programistom:

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

Fakt, że makeDecimalfunkcja wykonuje podstawową pracę, oznacza, że ​​rozszerzenie poza 999,999,999to tylko kwestia dodania dodatkowej linii Xlat, tak łatwej, że zrobiłem to za Ciebie.

Ostateczna returnw Xlatnie potrzebuje warunkowa ponieważ największa wartość można trzymać w wersji 64-bitowej długości wynosi tylko około 9,2 trylionów.

Ale jeśli z jakiegoś dziwacznego wymagania Oracle zdecyduje się dodać 128-bitowy longerlub 1024-bitowy damn_longtyp, będziesz na to gotowy :-)


I na koniec mała wiązka testowa, której możesz użyć do sprawdzenia funkcjonalności.

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

Na podstawie wyników widać, że zapewnia to, czego potrzebujesz:

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

A na marginesie, pamiętaj, że przekazanie liczby ujemnej do tej funkcji spowoduje, że łańcuch będzie zbyt długi dla twoich wymagań, ponieważ podąża on < THOUścieżką). Uznałem, że to w porządku, ponieważ w pytaniu wspominasz tylko o wartościach nieujemnych.

paxdiablo
źródło
6

Nie wiem, czy to najlepsze podejście, ale tak właśnie zrobiłem.

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

--- Kod---

public String Format(Integer number){
    String[] suffix = new String[]{"k","m","b","t"};
    int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
    if (size >= 3){
        while (size % 3 != 0) {
            size = size - 1;
        }
    }
    double notation = Math.pow(10, size);
    String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
    return result
}
Eduardo Aviles
źródło
6

Moja funkcja do konwersji dużej liczby na małą (z 2 cyframi). Można zmienić liczbę cyfr, przez zmiany #.##wDecimalFormat

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

Testowanie

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

Mam nadzieję, że to pomoże

Phan Van Linh
źródło
5

Moja Java jest zardzewiała, ale oto jak zaimplementuję ją w 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");
    }

Łatwo byłoby to zmienić, aby użyć kilogramów CS (1024) zamiast kilogramów metrycznych lub dodać więcej jednostek. Formatuje 1000 jako „1,0 k” zamiast „1 k”, ale uważam, że to nieistotne.

Aby spełnić bardziej szczegółowy wymóg „nie więcej niż cztery znaki”, usuń spacje przed przyrostkami i dostosuj środkowy blok w następujący sposób:

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

źródło
1
Niestety ta ToStringmetoda nie istnieje w Javie - będziesz potrzebować NumberFormat, który może powodować inne problemy (wrażliwe na ustawienia regionalne itp.).
assylias
5

Mój ulubiony. Możesz użyć „k” i tak dalej jako wskaźnika dla liczb dziesiętnych, co jest powszechne w domenie elektronicznej. To da ci dodatkową cyfrę bez dodatkowego miejsca

Druga kolumna próbuje użyć jak największej liczby cyfr

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

To jest kod

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

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

Pozostając wiernym mojemu komentarzowi, że ceniłbym czytelność ponad wydajność, oto wersja, w której powinno być jasne, co się dzieje (zakładając, że użyłeś BigDecimal używałeś wcześniej) bez nadmiernego komentowania (wierzę w samodokumentujący się kod), bez martwienia się o wydajność (ponieważ nie mogę sobie wyobrazić scenariusza, w którym chciałbyś to zrobić tyle milionów razy, że wydajność nawet staje się brana pod uwagę).

Ta wersja:

  • używa BigDecimals do precyzji i uniknięcia problemów z zaokrąglaniem
  • działa w celu zaokrąglenia w dół zgodnie z wnioskiem PO
  • działa dla innych trybów zaokrąglania, np. HALF_UPjak w testach
  • umożliwia regulację precyzji (zmianę REQUIRED_PRECISION)
  • używa an enumdo określenia progów, tj. można go łatwo dostosować tak, aby używał KB / MB / GB / TB zamiast k / m / b / t itp., i oczywiście można go rozszerzyć pozaTRILLION jeśli jest to wymagane
  • zawiera dokładne testy jednostkowe, ponieważ przypadki testowe w pytaniu nie testowały granic
  • powinno działać dla liczb zerowych i ujemnych

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

(Odkomentuj printlninstrukcje lub zmień, aby użyć ulubionego rejestratora, aby zobaczyć, co robi.)

I wreszcie testy w NumberShortenerTest (zwykły 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));
    }
  }
}

Zapraszam do wskazania w komentarzach, czy przegapiłem istotny przypadek testowy lub czy oczekiwane wartości powinny zostać skorygowane.

Amos M. Carpenter
źródło
Jedynym oczywistym minusem twojego rozwiązania są paski przewijania V + H dla twojego kodu, to obniża czytelność. Czy uważasz, że ponowne formatowanie byłoby możliwe bez utraty przejrzystości?
Wolf
@Wolf: Miałem nadzieję, że uniknie kopiowania / wklejania z mojego IDE, ale masz rację, twierdzenie, że jest czytelne i wymaga przewijania w poziomie, jest hipokryzją, więc dziękuję za wskazanie. ;-) Zaktualizowałem pierwsze dwa fragmenty kodu, ponieważ to są te, na które będziesz patrzył, aby zobaczyć, co się dzieje, ale zostawiłem kod testowy, ponieważ samo spojrzenie na niego nie jest tak pomocne - ty ' d prawdopodobnie zechcesz wkleić to do swojego własnego IDE, aby uruchomić testy jednostkowe, jeśli chcesz zobaczyć, że testy działają. Mam nadzieję, że wszystko w porządku.
Amos M. Carpenter
Ach, dobrze. Ale w ostatnim polu, w przypadkach testowych, oczekiwane wyniki mogłyby być - optycznie - lepiej powiązane z danymi wejściowymi (mam na myśli literały w pierwszych 6 tablicach).
Wolf
@Wolf: Nie jestem fanem prób wyrównywania elementów w linii ze spacjami lub tabulatorami - tego nie można łatwo skonfigurować konsekwentnie dla wszystkich przypadków w moim ulubionym programie formatującym (zaćmienie) i robić to ręcznie ... w ten sposób leży szaleństwo , ze względu na wszystkie dostosowania, które trzeba wprowadzać za każdym razem, gdy dodajesz lub usuwasz element. Gdybym naprawdę chciał zobaczyć je wyrównane, po prostu wkleiłbym liczby / wartości do arkusza kalkulacyjnego jako CSV.
Amos M. Carpenter
1
Wszystko zależy od tego, czego szukasz, @assylias. Jeśli jesteś tuż po rozwiązaniu jednorazowego przypadku użycia, Twoje rozwiązanie powinno działać dobrze; Podoba mi się to TreeMappodejście. „Czytelność” jest oczywiście subiektywna. ;-) A co, jeśli ktoś chce zaokrąglić inaczej niż obcinać w twojej wersji? (Na przykład, jeśli używasz tego do wskazania rozmiaru pliku, kto chciałby obciąć?) Jeśli chcesz potęgi 2 zamiast 10? Musiałbyś trochę przepisać, prawda? Jak powiedziałem, celowo nie próbowałem zagrać w golfa w swoim kodzie, z którego wiele można było skrócić (na przykład nigdy nie trzymałbym jeśli-to w jednej linii).
Amos M. Carpenter,
4

to jest mój kod. czyste i proste.

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

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

}
Ebrahim sadeghi
źródło
2

Dodanie własnej odpowiedzi, kodu Java, kodu zrozumiałego ...

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
źródło
1

Ten fragment kodu jest po prostu śmiertelnie prosty i czysty, i całkowicie działa:

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
źródło
1

Spróbuj tego :

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
źródło
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+"";
        }
    }
}

Wynik:

1000 => 1.0 K

5821 => 5.8 K

10500 => 10.5 K

101800 => 1.0 L

2000000 => 20.0 L

7800000 => 78.0 L

92150000 => 9.2 Cr

123200000 => 12.3 Cr

9999999 => 100.0 L

999 => 999
Ankit Singh
źródło
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
źródło
2
nie działa to we wszystkich przypadkach skrajnych. Wypróbuj na przykład 999.
jzd