So formatieren Sie 1200 bis 1,2 KB in Java


156

Ich möchte folgende Zahlen mit Java in die Zahlen daneben formatieren:

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

Die Zahl rechts ist lang oder ganzzahlig, die Zahl links ist Zeichenfolge. Wie soll ich das angehen? Ich habe bereits wenig Algorithmus dafür gemacht, aber ich dachte, es gibt vielleicht schon etwas, das besser funktioniert und keine zusätzlichen Tests erfordert, wenn ich anfange, mich mit Milliarden und Billionen zu beschäftigen :)

Zusätzliche Anforderungen:

  • Das Format sollte maximal 4 Zeichen enthalten
  • Das obige bedeutet, dass 1.1k in Ordnung ist. 11.2k ist nicht in Ordnung. Gleiches gilt für 7,8 m ist 19,1 m nicht. Nur eine Ziffer vor dem Dezimalpunkt darf einen Dezimalpunkt haben. Zwei Ziffern vor dem Dezimalpunkt bedeuten, dass keine Ziffern nach dem Dezimalpunkt stehen.
  • Es ist keine Rundung erforderlich. (Zahlen, die mit k und m als Anhang angezeigt werden, sind eher analoge Anzeigen, die auf einen nicht präzisen logischen Artikel hinweisen. Daher ist die Rundung hauptsächlich aufgrund der Art der Variablen irrelevant, da sie mehrere Stellen erhöhen oder verordnen kann, selbst wenn Sie das zwischengespeicherte Ergebnis betrachten.)

1
Wenn niemand eine Bibliothek hat, würde es Ihnen etwas ausmachen, Ihren Code zu veröffentlichen?
Grammin

1
Dies kann hilfreich sein, obwohl dies kein Dup ist. stackoverflow.com/questions/529432
rfeak

1
@Mat Ich war neugierig, welche Lösung Sie zuvor verwendet haben. Wenn es Ihnen nichts ausmacht, würden Sie es auch als Antwort posten.
jzd

1
Was die Idee dahinter ist, No rounding is necessaryscheint mir absurd. Ist es nur um die Dinge zu komplizieren? Wäre es nicht besser, dies umzuformulieren Rounding is not necessary, but welcome?
Wolf

1
Falls Sie nicht bemerkt haben, dass die Zahlen, an die k und m angehängt sind, eher ein analoges Messgerät sind, das die Annäherung anzeigt, nicht den genauen Artikel der Logik. Daher ist die Rundung hauptsächlich aufgrund der Art der Variablen irrelevant, da sie mehrere Stellen erhöhen oder verordnen kann, selbst wenn Sie das eingelöste Ergebnis betrachten.
Mat B.

Antworten:


154

Hier ist eine Lösung, die für jeden langen Wert funktioniert und die ich gut lesbar finde (die Kernlogik wird in den unteren drei Zeilen der formatMethode ausgeführt).

Es nutzt TreeMap, um das passende Suffix zu finden. Es ist überraschend effizienter als eine frühere Lösung, die Arrays verwendet und schwieriger zu lesen ist.

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

Testcode

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
Schöne Lösung. Sieht so aus, als könnten Sie einfach mehr Suffixe für diese wirklich großen Zahlen (Billiarden, Billionen usw.) hinzufügen, und die Ausgabe wird weiter skaliert.
Cypher

Ihr Code ist mit negativen Zahlen nicht ganz korrekt: -5821sollte als -5k, nicht als formatiert sein -5.8k.
std.denis

1
@ std.denis Das OP hat nicht angegeben, wie negative Zahlen formatiert werden sollen. Ich entschied mich, sie wie positive Zahlen zu formatieren, aber mit -dem Präfix , um die gleiche Anzahl signifikanter Ziffern beizubehalten. Es gibt andere Möglichkeiten ...
Assylias

1
Erstens: Ich habe die schlechten Kommentare gelöscht, weil es offensichtlich nicht deine Schuld ist. Zweitens: Es ist nicht das Problem, dass gute Antworten nicht genug Aufmerksamkeit erhalten, solange sie mehr als andere erhalten, aber da es so ist, muss man oft nach guten Antworten suchen und nur einige falsche, schlechte oder generische Antworten werden (wirklich) positiv bewertet schlecht, um neue Sachen zu lernen). Und für Leute, die Kopfgelder ausgeben, wenn bereits so viele Antworten vorhanden sind, hätte ich erwartet, klarer anzugeben, was fehlt, und dann sorgfältig die Antwort auszuwählen, die am besten zu den Kriterien passt ...
Maraca

1
Aber versteht die ganze Welt diesen Standard? Seien Sie vorsichtig, wenn Sie eine App für alle auf der Welt erstellen. Für Englisch ist es 10M, aber für Russisch ist es 10 млн und so weiter
user924

101

Ich weiß, das sieht eher aus wie ein C-Programm, ist aber superleicht!

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

}

Es gibt aus:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m

16
Verschleierter Code. Wir müssen heutzutage nicht mehr so ​​codieren. Kann wie erwartet funktionieren, aber ich möchte den Autor ermutigen, sich Roger C. Martin
Andreas Dolk

29
Verschleiert? Ich bitte um Verzeihung, aber Sie haben wahrscheinlich ein Buch gelesen und denken, Sie können heutzutage irgendwie anders codieren. Erzählen Sie Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) davon. Ich wage jeden Code, den Sie möglicherweise schreiben können, um der Geschwindigkeit meiner Methode nahe zu kommen!
Elijah Saounkine

11
Das Ändern von d-, c- und n-Variablen in etwas
Lesbareres

5
Warum diese Besessenheit von Leistung? Warum sollte jemand eine ausreichend große Anzahl dieser Konvertierungen ausführen wollen, um überhaupt über die Leistung nachzudenken ...? Lesbarkeit zuerst, Leistungsoptimierung nur bei Bedarf.
Amos M. Carpenter

10
Ich muss @ AmosM.Carpenter zustimmen. Als ich diese Antwort vor 4 Jahren schrieb, wusste ich wenig über die Wartbarkeit von Code. Es ist nicht schlecht, im Allgemeinen zu optimieren, ABER die Lesbarkeit steht an erster Stelle. Übrigens ist es in Bezug auf die Leistung nicht so schlecht: nicht fünfmal langsamer als das, was Maraca geschrieben hat - es ist ungefähr das Gleiche (ich habe hier einige Lösungen für einen Benchmark vorgestellt: github.com/esaounkine/number-format- Benchmark ).
Elijah Saounkine

43

Hier eine Lösung, die die technische Notation von DecimalFormat verwendet:

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

Ausgabe:

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 Aktualisiert, um neuen Anforderungen gerecht zu werden
jzd

Gibt es eine einfache Möglichkeit, dies mit der Währungsinstanz zu kombinieren, um ähnliche Funktionen mit der Währung zu erhalten?
Xdumaine

@roviuser, nicht sicher, was du meinst, aber das klingt nach einer separaten Frage.
JZD

7
Runden 160000 bis 200k und Runden 120000 bis 100k
k1komans

4
Dies ist kaputt, ich habe die Nummer 10000000000000.0 eingegeben und es steht 103.
Oliver Dixon

23

Brauchen Sie etwas Verbesserung, aber: StrictMath zur Rettung!
Sie können das Suffix in einen String oder ein Array einfügen und sie basierend auf der Leistung oder ähnlichem abrufen.
Die Aufteilung kann auch um die Leistung herum verwaltet werden, ich denke, fast alles dreht sich um den Leistungswert. Ich hoffe es hilft!

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

Ausgänge:

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


2
Verbesserte Lesbarkeit ein wenig. Muss nur eine return-Anweisung von jzd hinzugefügt werden, um das 4-Zeichen-Problem zu lösen. Und denken Sie daran, ein Suffix hinzuzufügen, wenn Sie über t gehen, um eine AIOOB-Ausnahme zu vermeiden. ;)
jhurtado

Dieser Code reagiert empfindlich auf das Gebietsschema. Beispielsweise wird das Gebietsschema 1000 in sv_SE in 10x10³ konvertiert, was vom regulären Ausdruck nicht korrekt übereinstimmt.
Joakim Lundborg

2
wirft eine Ausnahme für 0, funktioniert nicht für negative Zahlen, rundet 9.999.999 nicht richtig (druckt 10m) ...
Assylias

16

Probleme mit aktuellen Antworten

  • Viele der aktuellen Lösungen verwenden diese Präfixe k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Nach verschiedenen Quellen sind die korrekten Präfixe jedoch k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Fehlende Unterstützung für negative Zahlen (oder zumindest fehlende Tests, die belegen, dass negative Zahlen unterstützt werden)
  • Mangelnde Unterstützung für die inverse Operation, z. B. Konvertieren von 1.1k in 1100 (obwohl dies außerhalb des Rahmens der ursprünglichen Frage liegt)

Java-Lösung

Diese Lösung (eine Erweiterung dieser Antwort ) behebt die oben genannten Probleme.

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

Groovige Lösung

Die Lösung wurde ursprünglich in Groovy geschrieben, wie unten gezeigt.

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

Tests (Groovy)

Die Tests sind in Groovy geschrieben, können jedoch verwendet werden, um entweder die Java- oder die Groovy-Klasse zu überprüfen (da beide denselben Namen und dieselbe API haben).

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
Ich denke, Sie denken über die CS-Präfixe nach, da er über Milliarden und Billionen spricht. Ich denke, er möchte kurze Ziffern.
Jhurtado

1
9999999 sollte meiner Meinung nach als 9,9 m gedruckt werden (die Zahlen sind abgeschnitten, nicht gerundet).
Assylias

Diese Lösung unterstützt keine Präfixe für Werte, die kleiner als 1 sind, z. B. u (Mikro) und m (Milli).
Gbmhunter

13

Die ICU-Bibliothek verfügt über einen regelbasierten Formatierer für Zahlen, der für die Rechtschreibung von Zahlen usw. verwendet werden kann. Ich denke, die Verwendung der ICU würde Ihnen eine lesbare und wartbare Lösung bieten.

[Verwendung]

Die richtige Klasse ist RuleBasedNumberFormat. Das Format selbst kann als separate Datei (oder als String-Konstante IIRC) gespeichert werden.

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

Die gleiche Seite zeigt römische Ziffern, also denke ich, dass Ihr Fall auch möglich sein sollte.


Die einzige Lösung im Thread, die nicht vollständig auseinander fällt, wenn Sie eine Lokalisierung benötigen.
Grozz

2
Wenn Sie es für die Android-Entwicklung benötigen, ist dies bereits im Framework enthalten. Suchen Sie nach CompactDecimalFormat. API Level 24+
Gokhan Arik

10

Mit Java-12 + können Sie NumberFormat.getCompactNumberInstancedie Zahlen formatieren. Sie können eine NumberFormaterste als erstellen

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

und dann verwenden Sie es, um format:

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

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

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

8

Wichtig: Antworten des Gießens doublewird für Zahlen nicht wie 99999999999999999Lund zurück 100Pstatt , 99Pweil doubleAnwendungen der IEEEStandard :

Wenn eine Dezimalzeichenfolge mit höchstens 15 signifikanten Stellen in eine IEEE 754-Darstellung mit doppelter Genauigkeit konvertiert und dann wieder in eine Zeichenfolge mit derselben Anzahl von signifikanten Stellen konvertiert wird, sollte die endgültige Zeichenfolge mit dem Original übereinstimmen. [ longhat bis zu 19 signifikante Stellen .]

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

Diese Lösung schneidet unerwünschte Ziffern ab und funktioniert für alle longWerte . Einfache aber performante Implementierung (Vergleich unten). -120k kann nicht mit 4 Zeichen ausgedrückt werden, selbst -0,1M ist zu lang. Deshalb müssen für negative Zahlen 5 Zeichen in Ordnung sein:

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

Der Test else ifam Anfang ist notwendig, da das Min -(2^63)und das Max ist (2^63)-1und daher die Zuordnung number = -numberfehlschlagen würde, wenn number == Long.MIN_VALUE. Wenn wir eine Überprüfung durchführen müssen, können wir auch so viele Zahlen wie möglich einfügen, anstatt nur nach zu suchen number == Long.MIN_VALUE.

Der Vergleich dieser Implementierung mit derjenigen, die die meisten Upvotes erhalten hat (derzeit die schnellste), hat gezeigt, dass sie mehr als fünfmal schneller ist (dies hängt von den Testeinstellungen ab, aber mit mehr Zahlen wird der Gewinn größer und diese Implementierung hat mehr Überprüfungen durchführen, da alle Fälle behandelt werden. Wenn also der andere behoben würde, würde der Unterschied noch größer werden. Es ist so schnell, weil es keine Gleitkommaoperationen, keinen Logarithmus, keine Leistung, keine Rekursion, keinen regulären Ausdruck, keine ausgeklügelten Formatierer und keine Minimierung der Anzahl der erstellten Objekte gibt.


Hier ist das Testprogramm:

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

}

Mögliche Ausgabe: 2309 vs. 11591(ungefähr gleich, wenn nur positive Zahlen verwendet werden und viel extremer, wenn die Ausführungsreihenfolge umgekehrt wird, hat dies möglicherweise etwas mit der Speicherbereinigung zu tun)


8

Hier ist eine kurze Implementierung ohne Rekursion und nur eine sehr kleine Schleife. Funktioniert nicht mit negativen Zahlen, unterstützt aber alle positiven longs bis zu 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);
}

Ausgänge:

1k
5,8k
10k
101k
2m
7,8m
92m
123m
9,2e (dies ist Long.MAX_VALUE)

Ich habe auch ein wirklich einfaches Benchmarking durchgeführt (Formatieren von 10 Millionen zufälligen Longs) und es ist erheblich schneller als die Implementierung von Elijah und etwas schneller als die Implementierung von Assylias.

Meins: 1137.028 ms
Elijahs: 2664.396 ms
Assylias ': 1373.473 ms


1
In Ihrem letzten Update haben Sie einen Fehler hinzugefügt. Es gibt jetzt 1k für die Nummer 101800 zurück .
Sufian

2
Vielen Dank für das Bemerken, es ist behoben
Raniz

8

Für alle, die abrunden wollen. Dies ist eine großartige, einfach zu lesende Lösung, die die Java.Lang.Math-Bibliothek nutzt

 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

Der folgende Code zeigt, wie Sie dies mit Blick auf eine einfache Erweiterung tun können.

Die "Magie" liegt hauptsächlich in der makeDecimalFunktion, die für die korrekten Werte garantiert, dass Sie nie mehr als vier Zeichen in der Ausgabe haben.

Es extrahiert zuerst den gesamten und den zehnten Teil für einen gegebenen Divisor, so dass beispielsweise 12,345,678mit einem Divisor von 1,000,000ein wholeWert von 12und ein tenthsWert von erhalten wird 3.

Daraus kann er anhand der folgenden Regeln entscheiden, ob nur der gesamte Teil oder sowohl der gesamte als auch der zehnte Teil ausgegeben wird:

  • Wenn der Zehntelteil Null ist, geben Sie einfach den gesamten Teil und das Suffix aus.
  • Wenn der gesamte Teil größer als neun ist, geben Sie einfach den gesamten Teil und das Suffix aus.
  • Andernfalls geben Sie den gesamten Teil, den Zehntelteil und das Suffix aus.

Der Code dafür lautet:

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

Dann ist es einfach, diese Hilfsfunktion mit den richtigen Werten aufzurufen, einschließlich einiger Konstanten, um dem Entwickler das Leben zu erleichtern:

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

Die Tatsache, dass die makeDecimalFunktion die Grunzarbeit erledigt, bedeutet, dass das Erweitern darüber hinaus 999,999,999nur eine Frage des Hinzufügens einer zusätzlichen Zeile ist Xlat, so einfach, dass ich es für Sie getan habe.

Das Finale returnin Xlatbenötigt keine Bedingung, da der größte Wert, den Sie in einem 64-Bit-Long mit Vorzeichen halten können, nur etwa 9,2 Billionen beträgt.

Aber wenn Oracle aufgrund einer bizarren Anforderung beschließt, einen 128-Bit- longerTyp oder einen 1024-Bit- damn_longTyp hinzuzufügen , sind Sie bereit dafür :-)


Und schließlich ein kleines Testgeschirr, mit dem Sie die Funktionalität überprüfen können.

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

Sie können an der Ausgabe erkennen, dass Sie das bekommen, was Sie brauchen:

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

Beachten Sie außerdem, dass die Übergabe einer negativen Zahl an diese Funktion zu einer Zeichenfolge führt, die für Ihre Anforderungen zu lang ist, da sie dem < THOUPfad folgt . Ich dachte, das wäre in Ordnung, da Sie in der Frage nur nicht negative Werte erwähnen.


6

Ich weiß nicht, ob es der beste Ansatz ist, aber das habe ich getan.

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

--- Code ---

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

Meine Funktion zum Konvertieren großer Zahlen in kleine Zahlen (mit 2 Ziffern). Sie können die Anzahl der Ziffern durch Ändern #.##in ändernDecimalFormat

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

Testen

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

Hoffe es hilft


5

Mein Java ist rostig, aber so würde ich es in C # implementieren:

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

Es wäre einfach, dies anzupassen, um CS-Kilo (1.024) anstelle von metrischen Kilo zu verwenden oder um weitere Einheiten hinzuzufügen. Es formatiert 1.000 als "1.0 k" anstatt als "1 k", aber ich vertraue darauf, dass dies unerheblich ist.

Um die spezifischere Anforderung "nicht mehr als vier Zeichen" zu erfüllen, entfernen Sie die Leerzeichen vor den Suffixen und passen Sie den mittleren Block wie folgt an:

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

1
Leider gibt es diese ToStringMethode in Java nicht - Sie benötigen ein NumberFormat, das andere Probleme verursachen kann (Gebietsschemasensitiv usw.).
Assylias

5

Mein Favorit. Sie können auch "k" usw. als Indikator für die Dezimalzahl verwenden, wie dies im elektronischen Bereich üblich ist. Dadurch erhalten Sie eine zusätzliche Ziffer ohne zusätzlichen Platz

In der zweiten Spalte wird versucht, so viele Ziffern wie möglich zu verwenden

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

Dies ist der Code

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

Um meinem Kommentar treu zu bleiben, dass ich die Lesbarkeit über die Leistung stellen würde, ist hier eine Version, in der klar sein sollte, was passiert (vorausgesetzt, Sie haben BigDecimals zuvor verwendet), ohne übermäßige Kommentare abzugeben (ich glaube an selbstdokumentierenden Code), ohne sich um die Leistung zu sorgen (Da ich mir kein Szenario vorstellen kann, in dem Sie dies so viele Millionen Mal tun möchten, dass die Leistung sogar in Betracht gezogen wird).

Diese Version:

  • verwendet BigDecimals zur Präzision und zur Vermeidung von Rundungsproblemen
  • arbeitet zum Abrunden, wie vom OP gefordert
  • funktioniert für andere Rundungsmodi, zB HALF_UPwie in den Tests
  • ermöglicht es Ihnen, die Genauigkeit anzupassen (ändern REQUIRED_PRECISION)
  • verwendet a enum, um die Schwellenwerte zu definieren, dh kann leicht angepasst werden, um KB / MB / GB / TB anstelle von k / m / b / t usw. zu verwenden, und kann natürlich bei Bedarf darüber hinaus erweitert TRILLIONwerden
  • kommt mit gründlichen Unit-Tests, da die Testfälle in der Frage nicht die Grenzen getestet haben
  • sollte für Null und negative Zahlen funktionieren

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

(Kommentieren Sie die printlnAnweisungen aus oder ändern Sie sie, um Ihren bevorzugten Logger zu verwenden, um zu sehen, was er tut.)

Und schließlich die Tests in NumberShortenerTest (Plain 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));
    }
  }
}

Fühlen Sie sich frei, in den Kommentaren darauf hinzuweisen, wenn ich einen signifikanten Testfall verpasst habe oder wenn die erwarteten Werte angepasst werden sollten.


Der einzige offensichtliche Nachteil in Ihrer Lösung scheinen die V + H-Bildlaufleisten für Ihren Code zu sein, dies verringert die Lesbarkeit. Denken Sie, dass eine Neuformatierung möglich wäre, ohne an Klarheit zu verlieren?
Wolf

@ Wolf: Ich hatte gehofft, mit dem Kopieren / Einfügen von meiner IDE davonzukommen, aber Sie haben Recht, es ist scheinheilig von mir, Lesbarkeit zu beanspruchen und horizontales Scrollen zu erfordern. Vielen Dank, dass Sie darauf hingewiesen haben. ;-) Ich habe die ersten beiden Codebits aktualisiert, da dies diejenigen sind, auf die Sie schauen würden, um zu sehen, was passiert, aber den Testcode verlassen haben, da das Betrachten allein nicht so hilfreich ist - Sie ' Ich möchte das wahrscheinlich in Ihre eigene IDE einfügen, um die Komponententests auszuführen, wenn Sie sehen möchten, dass die Tests funktionieren. Hoffe das ist ok
Amos M. Carpenter

Ah gut. Aber in der letzten Box, den Testfällen, könnten die erwarteten Ergebnisse - optisch - besser mit den Eingaben zusammenhängen (ich meine die Literale in den ersten 6 Arrays).
Wolf

@Wolf: Ich bin kein Fan von dem Versuch, Elemente in einer Zeile an Leerzeichen oder Tabulatoren auszurichten - das kann nicht einfach für alle Fälle in meinem Lieblingsformatierer (Eclipse) konsistent konfiguriert werden, und das manuell ... auf diese Weise liegt der Wahnsinn , wegen all der Anpassungen, die Sie jedes Mal vornehmen müssen, wenn Sie ein Element hinzufügen oder entfernen. Wenn ich wirklich wollte, dass sie ausgerichtet sind, füge ich die Zahlen / Werte einfach als CSV in eine Tabelle ein.
Amos M. Carpenter

1
Alles hängt davon ab, wonach Sie suchen, @assylias. Wenn Sie gerade einen einmaligen Anwendungsfall gelöst haben, sollte Ihre Lösung einwandfrei funktionieren. Ich mag den TreeMapAnsatz. "Lesbarkeit" ist natürlich subjektiv. ;-) Was ist nun, wenn jemand anders runden möchte als in deiner Version abschneiden? (Wenn Sie dies beispielsweise verwenden, um die Dateigröße anzugeben, wer möchte dann abschneiden?) Wenn Sie Potenzen von 2 statt 10 möchten? Sie müssten ein gutes Stück umschreiben, nicht wahr? Wie gesagt, ich habe absichtlich nicht versucht, meinen Code zu spielen, von dem ein Großteil hätte verkürzt werden können (ich würde zum Beispiel niemals ein Wenn-Dann in einer Zeile behalten).
Amos M. Carpenter

4

Das ist mein Code. sauber und einfach.

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

Hinzufügen meiner eigenen Antwort, Java-Code, selbsterklärenden Code ..

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

Dieses Code-Snippet ist einfach tödlich einfach und sauber und funktioniert vollständig:

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

Versuche dies :

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

Ausgabe:

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
Dies funktioniert nicht für alle Ihre Randfälle. Versuchen Sie zum Beispiel 999.
jzd
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.