Bagaimana cara memformat 1200 ke 1.2k di java

157

Saya ingin memformat angka berikut menjadi angka di sebelahnya dengan 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

Angka di sebelah kanan akan panjang atau bilangan bulat di sebelah kiri akan menjadi string. Bagaimana saya harus mendekati ini. Saya sudah melakukan sedikit algoritme untuk ini, tetapi saya pikir mungkin sudah ada sesuatu yang ditemukan di sana yang melakukan pekerjaan yang lebih baik dan tidak memerlukan pengujian tambahan jika saya mulai berurusan dengan miliaran dan triliunan :)

Persyaratan tambahan:

  • Formatnya harus maksimal 4 karakter
  • Di atas berarti 1.1k OK 11.2k tidak. Sama untuk 7.8m tidak apa-apa 19.1m tidak. Hanya satu digit sebelum titik desimal diizinkan memiliki titik desimal. Dua digit sebelum titik desimal berarti bukan digit setelah titik desimal.
  • Tidak diperlukan pembulatan. (Angka yang ditampilkan dengan k dan m ditambahkan lebih dari pengukur analog yang menunjukkan aproksimasi bukan artikel logika yang tepat. Oleh karena itu pembulatan tidak relevan terutama karena sifat variabel daripada dapat meningkatkan atau menurunkan beberapa digit bahkan ketika Anda melihat hasil dalam cache.)
Mat B.
sumber
1
Jika tidak ada yang memiliki perpustakaan, maukah Anda memposting kode Anda?
Grammin
1
Ini mungkin membantu, meskipun ini bukan dup. stackoverflow.com/questions/529432
rfeak
1
@Mat saya penasaran dengan solusi apa yang Anda gunakan sebelumnya. Jika Anda tidak keberatan Anda akan mempostingnya sebagai jawaban juga.
jzd
1
Apa ide di balik No rounding is necessaryini tampaknya tidak masuk akal bagi saya. Apakah hanya untuk memperumit masalah? Bukankah lebih baik untuk mengulangi ini Rounding is not necessary, but welcome?
Wolf
1
Jika Anda tidak melihat angka-angka yang ditampilkan dengan k dan m ditambahkan lebih dari pengukur analog yang menunjukkan perkiraan bukan artikel logika yang tepat. Oleh karena itu pembulatan tidak relevan terutama karena sifat variabel daripada yang dapat meningkatkan atau menurunkan beberapa digit bahkan ketika Anda melihat hasil yang diuangkan.
Mat B.

Jawaban:

155

Berikut ini adalah solusi yang bekerja untuk setiap nilai yang panjang dan yang menurut saya cukup mudah dibaca (logika inti dilakukan pada tiga baris terbawah dari formatmetode ini).

Ini memanfaatkan TreeMapuntuk menemukan akhiran yang sesuai. Secara mengejutkan ini lebih efisien daripada solusi sebelumnya yang saya tulis yang menggunakan array dan lebih sulit dibaca.

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

Kode uji

public static void main(String args[]) {
  long[] numbers = {0, 5, 999, 1_000, -5_821, 10_500, -101_800, 2_000_000, -7_800_000, 92_150_000, 123_200_000, 9_999_999, 999_999_999_999_999_999L, 1_230_000_000_000_000L, Long.MIN_VALUE, Long.MAX_VALUE};
  String[] expected = {"0", "5", "999", "1k", "-5.8k", "10k", "-101k", "2M", "-7.8M", "92M", "123M", "9.9M", "999P", "1.2P", "-9.2E", "9.2E"};
  for (int i = 0; i < numbers.length; i++) {
    long n = numbers[i];
    String formatted = format(n);
    System.out.println(n + " => " + formatted);
    if (!formatted.equals(expected[i])) throw new AssertionError("Expected: " + expected[i] + " but found: " + formatted);
  }
}
assylias
sumber
1
Solusi bagus Sepertinya Anda bisa menambahkan sufiks lagi untuk angka-angka yang sangat besar (kuadriliun, kuintiliun, dll), dan hasilnya terus meningkat.
Cypher
Kode Anda tidak sepenuhnya benar dengan angka negatif: -5821harus diformat sebagai -5k, bukan sebagai -5.8k.
std.denis
1
@ std.denis OP tidak menunjukkan cara memformat angka negatif. Saya memutuskan untuk memformatnya seperti angka positif tetapi diawali dengan -untuk mempertahankan jumlah digit signifikan yang sama. Ada pilihan lain ...
assylias
1
Pertama: Saya menghapus komentar buruk, karena jelas itu bukan kesalahan Anda. Kedua: Bukan masalah bahwa jawaban yang baik tidak mendapatkan perhatian yang cukup selama mereka mendapatkan lebih banyak dari yang lain, tetapi karena Anda sering harus menggali jawaban yang baik dan hanya beberapa jawaban yang salah, buruk, atau generik akan terangkat (benar-benar buruk untuk mempelajari hal baru). Dan untuk orang-orang yang mengeluarkan hadiah ketika sudah ada banyak jawaban di sana, saya akan berharap untuk menentukan dengan lebih jelas apa yang hilang dan kemudian dengan hati-hati memilih jawaban yang paling sesuai dengan kriteria ...
maraca
1
tetapi apakah seluruh dunia memahami standar ini? hati-hati jika Anda membuat aplikasi untuk semua orang di dunia. Untuk Bahasa Inggris 10M tetapi untuk Bahasa Rusia 10M dan seterusnya
user924
101

Saya tahu, ini lebih mirip program C, tapi sangat ringan!

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

}

Ini menghasilkan:

1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Elijah Saounkine
sumber
16
Kode yang dikaburkan. Kami tidak perlu kode seperti ini saat ini. Mungkin bekerja seperti yang diharapkan, tetapi saya mendorong penulis untuk melihat Roger C. Martin: Clean Code
Andreas Dolk
30
Dikaburkan? Saya mohon maaf, tetapi Anda mungkin membaca satu buku dan berpikir Anda bisa membuat kode yang berbeda saat ini. Beri tahu Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) tentang itu. Saya berani kode apa pun yang dapat Anda tulis untuk mendekati kecepatan metode saya!
Elijah Saounkine
11
Mengubah variabel d, c, n menjadi sesuatu yang lebih mudah dibaca (pemahaman lebih cepat) membuat kode ini layak dalam pandangan saya
Gennadiy Ryabkin
5
Mengapa obsesi dengan kinerja ini? Mengapa ada orang yang ingin mengeksekusi sejumlah besar konversi ini untuk menjamin bahkan memikirkan kinerja ...? Keterbacaan pertama, tweaker kinerja hanya jika diperlukan.
Amos M. Carpenter
10
Saya harus setuju dengan @ AmosM.Carpenter. Sedikit yang saya tahu tentang perawatan kode ketika saya menulis jawaban ini 4 tahun yang lalu. Tidak buruk untuk mengoptimalkan, secara umum, TAPI keterbacaan lebih dulu. Ngomong-ngomong, ini bukan kinerja yang buruk: tidak 5 kali lebih lambat dari yang ditulis oleh maraca - hampir sama (saya sudah memasang beberapa solusi untuk benchmark di sini github.com/esaounkine/number-format- patokan ).
Elijah Saounkine
43

Berikut solusi yang menggunakan notasi teknik 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;
}

Keluaran:

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
sumber
@Mat Diperbarui untuk menangani persyaratan baru
jzd
Apakah ada cara mudah untuk menggabungkan ini dengan Instance Mata Uang untuk mendapatkan fungsionalitas yang sama dengan mata uang?
xdumaine
@roviuser, tidak yakin apa yang Anda maksud, tetapi ini terdengar seperti pertanyaan terpisah.
jzd
7
putaran 160000 hingga 200k dan juga putaran 120000 hingga 100k
k1komans
4
Ini rusak, saya memasukkan nomor 10000000000000.0 dan dikatakan 103.
Oliver Dixon
23

Perlu beberapa perbaikan, tetapi: StrictMath to the rescue!
Anda bisa meletakkan akhiran dalam sebuah String atau array dan mengambilnya berdasarkan kekuatan, atau sesuatu seperti itu.
Divisi ini juga dapat dikelola di sekitar daya, saya pikir hampir semuanya tentang nilai daya. Semoga ini bisa membantu!

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

output:

999
1.2k
98k
911k
1.1m
11b
712b
34t

melati
sumber
2
Peningkatan keterbacaan sedikit, Hanya perlu menambahkan pernyataan kembali dari jzd untuk menyelesaikan masalah 4 char. Dan ingat untuk menambahkan akhiran jika pergi t untuk menghindari pengecualian AIOOB. ;)
jhurtado
Kode ini sensitif terhadap lokal, misalnya di sv_SE lokal 1000 dikonversi ke 10x10³, yang tidak cocok dengan benar oleh regexp.
Joakim Lundborg
2
melempar pengecualian untuk 0, tidak bekerja untuk angka negatif, tidak membulatkan 9.999.999 dengan benar (mencetak 10m) ...
assylias
16

Masalah dengan Jawaban Saat Ini

  • Banyak solusi saat ini menggunakan awalan ini k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Namun, menurut berbagai sumber , awalan yang benar adalah k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Kurangnya dukungan untuk angka negatif (atau setidaknya kurangnya tes yang menunjukkan bahwa angka negatif didukung)
  • Kurangnya dukungan untuk operasi terbalik, mis., Konversi 1.1k ke 1100 (meskipun ini di luar ruang lingkup pertanyaan awal)

Solusi Java

Solusi ini (perpanjangan dari jawaban ini ) mengatasi masalah di atas.

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

Solusi Groovy

Solusinya awalnya ditulis dalam Groovy seperti yang ditunjukkan di bawah ini.

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

Tes (Groovy)

Tes ditulis dalam Groovy tetapi dapat digunakan untuk memverifikasi kelas Java atau Groovy (karena keduanya memiliki nama dan API yang sama).

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
sumber
1
Saya pikir Anda berpikir tentang awalan CS, mengingat dia berbicara tentang miliaran dan triliunan, saya kira dia ingin angka skala pendek.
jhurtado
1
9999999 harus mencetak sebagai 9,9 m saya percaya (jumlahnya terpotong, tidak bulat).
assylias
Solusi ini tidak memiliki dukungan untuk awalan untuk nilai yang lebih kecil dari 1, misalnya u (mikro) dan m (mili).
gbmhunter
13

The ICU lib memiliki formatter berbasis aturan untuk nomor, yang dapat digunakan untuk nomor spellout dll Saya pikir menggunakan ICU akan memberikan solusi yang dapat dibaca dan maintanable.

[Pemakaian]

Kelas yang tepat adalah RuleBasedNumberFormat. Format itu sendiri dapat disimpan sebagai file terpisah (atau sebagai String konstan, IIRC).

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

Halaman yang sama menunjukkan angka-angka Romawi, jadi saya kira kasus Anda juga harus dimungkinkan.

Landei
sumber
Satu-satunya solusi di utas yang tidak sepenuhnya berantakan jika Anda membutuhkan pelokalan.
Grozz
2
Jika Anda membutuhkannya untuk pengembangan Android, ini sudah termasuk dalam kerangka kerja. Carilah CompactDecimalFormat. API Level 24+
Gokhan Arik
10

Dengan Java-12 + , Anda dapat menggunakan NumberFormat.getCompactNumberInstanceformat angka. Anda dapat membuat yang NumberFormatpertama sebagai

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

dan kemudian menggunakannya untuk format:

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

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

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

Penting: Menjawab jawaban yang doubleakan gagal untuk angka suka 99999999999999999Ldan kembali 100Pbukan 99Pkarena doublemenggunakan IEEEstandar :

Jika string desimal dengan maksimal 15 digit signifikan dikonversi ke representasi presisi ganda IEEE 754 dan kemudian dikonversi kembali ke string dengan jumlah digit signifikan yang sama, maka string terakhir harus cocok dengan aslinya. [ longmemiliki hingga 19 digit signifikan .]

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

Solusi ini memotong angka yang tidak diinginkan dan bekerja untuk semua longnilai . Implementasi sederhana tetapi performan (perbandingan di bawah). -120k tidak dapat diekspresikan dengan 4 karakter, bahkan -0.1M terlalu panjang, itu sebabnya untuk angka negatif 5 karakter harus baik-baik saja:

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

Tes di else ifawal adalah keharusan karena min adalah -(2^63)dan maks adalah (2^63)-1dan karena itu tugas number = -numberakan gagal jika number == Long.MIN_VALUE. Jika kita harus melakukan pemeriksaan, maka kita juga bisa memasukkan angka sebanyak mungkin, bukan hanya memeriksa number == Long.MIN_VALUE.

Perbandingan implementasi ini dengan orang yang mendapat upvotes terbanyak (dikatakan sebagai yang tercepat saat ini) menunjukkan bahwa itu lebih dari 5 kali lebih cepat (tergantung pada pengaturan tes, tetapi dengan lebih banyak angka, gain bertambah besar dan implementasi ini memiliki untuk melakukan lebih banyak pemeriksaan karena menangani semua kasus, jadi jika yang lain diperbaiki perbedaannya akan menjadi lebih besar). Ini sangat cepat karena tidak ada operasi floating point, tidak ada logaritma, tidak ada daya, tidak ada rekursi, tidak ada regex, tidak ada formatters canggih dan minimalisasi jumlah objek yang dibuat.


Inilah program pengujiannya:

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

}

Output yang mungkin: 2309 vs. 11591(hampir sama ketika hanya menggunakan angka positif dan jauh lebih ekstrim ketika membalikkan urutan eksekusi, mungkin itu ada hubungannya dengan pengumpulan sampah)

maraca
sumber
8

Berikut ini adalah implementasi singkat tanpa rekursi dan hanya loop yang sangat kecil. Tidak bekerja dengan angka negatif tetapi mendukung semua positif longhingga 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);
}

Output:

1k
5.8k
10k
101k
2m
7.8m
92m
123m
9.2e (ini Long.MAX_VALUE)

Saya juga melakukan beberapa tolok ukur yang sangat sederhana (memformat 10 juta rindu acak) dan ini jauh lebih cepat daripada implementasi Elia dan sedikit lebih cepat dari implementasi assylias.

Milik saya: 1137,028 ms
Elijah's: 2664.396 ms
assylias ': 1373.473 ms

Raniz
sumber
1
Di pembaruan terakhir Anda, Anda menambahkan bug. Sekarang mengembalikan 1k untuk nomor 101800 .
Sufian
2
Terima kasih telah memperhatikan, ini sudah diperbaiki
Raniz
8

Untuk siapa saja yang ingin membulatkan tekad. Ini adalah solusi hebat dan mudah dibaca, yang memanfaatkan perpustakaan 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
sumber
8

Kode berikut menunjukkan bagaimana Anda dapat melakukan ini dengan ekspansi mudah dalam pikiran.

"Sihir" sebagian besar terletak pada makeDecimalfungsi yang, untuk nilai-nilai yang benar diteruskan, menjamin Anda tidak akan pernah memiliki lebih dari empat karakter dalam output.

Pertama-tama mengekstraksi seluruh dan sepersepuluh bagian untuk pembagi yang diberikan jadi, misalnya, 12,345,678dengan pembagi 1,000,000akan memberikan wholenilai 12dan tenthsnilai 3.

Dari itu, ia dapat memutuskan apakah itu menghasilkan hanya seluruh bagian atau keseluruhan dan kesepuluh bagian, menggunakan aturan:

  • Jika sepersepuluh bagian adalah nol, output saja seluruh bagian dan akhiran.
  • Jika seluruh bagian lebih besar dari sembilan, cukup output seluruh bagian dan akhiran.
  • Jika tidak, hasilkan seluruh bagian, sepersepuluh bagian dan akhiran.

Kode untuk itu adalah sebagai berikut:

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

Kemudian, ini masalah sederhana memanggil fungsi pembantu dengan nilai-nilai yang benar, termasuk beberapa konstanta untuk membuat hidup lebih mudah bagi pengembang:

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

Fakta bahwa makeDecimalfungsi melakukan pekerjaan kasar berarti memperluas di luar 999,999,999hanya masalah menambahkan garis tambahan Xlat, begitu mudah bahwa saya telah melakukannya untuk Anda.

Final returnin Xlattidak membutuhkan persyaratan karena nilai terbesar yang dapat Anda tahan dalam 64-bit lama ditandatangani hanya sekitar 9,2 triliun.

Tetapi jika, dengan persyaratan aneh, Oracle memutuskan untuk menambahkan tipe 128-bit longeratau tipe 1024-bit damn_long, Anda akan siap untuk itu :-)


Dan, akhirnya, sedikit test harness yang dapat Anda gunakan untuk memvalidasi fungsionalitas.

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

Anda dapat melihat dari hasilnya bahwa itu memberi Anda apa yang Anda butuhkan:

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

Dan, sebagai tambahan, ketahuilah bahwa meneruskan angka negatif ke fungsi ini akan menghasilkan string yang terlalu lama untuk kebutuhan Anda, karena mengikuti < THOUjalur). Saya pikir itu baik-baik saja karena Anda hanya menyebutkan nilai-nilai non-negatif dalam pertanyaan.

paxdiablo
sumber
6

Saya tidak tahu apakah itu pendekatan terbaik tetapi, inilah yang saya lakukan.

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

--- kode ---

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
sumber
6

Fungsi saya untuk mengonversi angka besar ke angka kecil (dengan 2 digit). Anda dapat mengubah jumlah digit perubahan #.##diDecimalFormat

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

Pengujian

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

Semoga ini bisa membantu

Phan Van Linh
sumber
5

Java saya sudah berkarat, tapi beginilah cara saya mengimplementasikannya di 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");
    }

Akan mudah untuk menyesuaikan ini untuk menggunakan CS kilo (1.024) daripada metrik kilo, atau untuk menambahkan lebih banyak unit. Ini format 1.000 sebagai "1.0 k" daripada "1 k", tapi saya percaya itu tidak penting.

Untuk memenuhi persyaratan yang lebih spesifik "tidak lebih dari empat karakter", hapus spasi sebelum sufiks dan sesuaikan blok tengah seperti ini:

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

sumber
1
Sayangnya ToStringmetode ini tidak ada di Jawa - Anda memerlukan NumberFormat yang dapat membuat masalah lain (peka lokal, dll.).
assylias
5

Kesukaanku. Anda dapat menggunakan "k" dan seterusnya sebagai indikator untuk desimal juga, seperti yang umum dalam domain elektronik. Ini akan memberi Anda digit tambahan tanpa ruang tambahan

Kolom kedua mencoba menggunakan sebanyak mungkin digit

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

Ini kodenya

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
sumber
4

Tetap setia pada komentar saya bahwa saya akan menghargai keterbacaan di atas kinerja, inilah versi di mana harus jelas apa yang terjadi (dengan asumsi Anda telah menggunakan BigDecimalsebelumnya) tanpa komentar berlebihan (saya percaya pada kode yang mendokumentasikan diri), tanpa khawatir tentang kinerja (Karena saya tidak bisa menggambarkan skenario di mana Anda ingin melakukan ini jutaan kali bahkan kinerja menjadi pertimbangan).

Versi ini:

  • menggunakan BigDecimals untuk presisi dan untuk menghindari masalah pembulatan
  • bekerja untuk pembulatan seperti yang diminta oleh OP
  • bekerja untuk mode pembulatan lainnya, misalnya HALF_UPseperti dalam tes
  • memungkinkan Anda untuk menyesuaikan presisi (perubahan REQUIRED_PRECISION)
  • menggunakan a enumuntuk mendefinisikan ambang batas, yaitu dengan mudah dapat disesuaikan untuk menggunakan KB / MB / GB / TB daripada k / m / b / t, dll, dan tentu saja dapat diperluas melampaui TRILLIONjika diperlukan
  • dilengkapi dengan unit test menyeluruh, karena kasus uji dalam pertanyaan tidak menguji perbatasan
  • harus bekerja untuk angka nol dan negatif

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

(Batalkan komentar printlnatau perubahan untuk menggunakan logger favorit Anda untuk melihat apa yang dilakukannya.)

Dan akhirnya, tes di 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));
    }
  }
}

Jangan ragu untuk menunjukkan di komentar jika saya melewatkan kasus uji yang signifikan atau jika nilai yang diharapkan harus disesuaikan.

Amos M. Carpenter
sumber
Satu-satunya downside jelas dalam solusi Anda tampaknya V + H-scrollbar untuk kode Anda, ini menurunkan keterbacaan. Apakah Anda pikir pemformatan ulang akan dimungkinkan tanpa kehilangan kejelasan?
Wolf
@ Wolf: Berharap untuk pergi dengan copy / paste dari IDE saya, tetapi Anda benar, itu munafik bagi saya untuk mengklaim keterbacaan dan memerlukan pengguliran horizontal, jadi terima kasih telah menunjukkannya. ;-) Saya telah memperbarui dua bit kode pertama, karena itulah yang akan Anda lihat untuk melihat apa yang terjadi, tetapi meninggalkan kode pengujian, karena melihatnya sendiri tidak begitu membantu - Anda ' d mungkin ingin menempelkannya ke IDE Anda sendiri untuk menjalankan tes unit jika Anda ingin melihat bahwa tes bekerja. Semoga tidak apa-apa.
Amos M. Carpenter
Ah bagus. Tetapi dalam kotak terakhir, kasus uji, hasil yang diharapkan bisa - secara optik - lebih baik terkait dengan input (maksud saya literal dalam 6 array pertama).
Wolf
@ Wolf: Saya bukan penggemar mencoba untuk menyelaraskan item dalam garis dengan spasi atau tab - yang tidak dapat dengan mudah dikonfigurasi secara konsisten untuk semua kasus di formatter favorit saya (gerhana), dan melakukannya secara manual ... dengan cara itu ada kegilaan , karena semua penyesuaian yang harus Anda lakukan setiap kali Anda menambah atau menghapus item. Jika saya benar-benar ingin melihat mereka selaras, saya hanya akan menempelkan angka / nilai ke dalam spreadsheet sebagai CSV.
Amos M. Carpenter
1
Semua tergantung pada apa yang Anda kejar, @assylias. Jika Anda baru saja menyelesaikan kasus penggunaan sekali pakai, solusi Anda akan berfungsi dengan baik; Saya suka TreeMappendekatannya. "Keterbacaan" itu subjektif, tentu saja. ;-) Sekarang bagaimana jika seseorang ingin membulatkan berbeda daripada memotong dalam versi Anda? (Misalnya, ketika menggunakan ini untuk menunjukkan ukuran file, siapa yang ingin memotong?) Jika Anda ingin kekuatan 2 daripada 10? Anda harus menulis ulang sedikit adil, bukan? Seperti yang saya katakan, saya sengaja tidak mencoba kode golf saya, banyak yang bisa dipersingkat (saya tidak akan pernah menyimpan if-then pada satu baris, misalnya).
Amos M. Carpenter
4

ini kode saya. bersih dan sederhana.

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
sumber
2

Menambahkan jawaban saya sendiri, kode Java, kode penjelasan sendiri ..

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

}
Pengembang Pembelajaran
sumber
1

Cuplikan kode ini sangat sederhana, dan bersih, dan benar-benar berfungsi:

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
sumber
1

coba ini :

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

Keluaran:

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
sumber
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);
}
pengguna2089762
sumber
2
ini tidak bekerja untuk semua kasing tepi Anda. Coba 999 misalnya.
jzd