Java'da 1200 - 1.2k biçimlendirme hakkında nasıl bilgi verilir


157

Java ile aşağıdaki sayıları yanlarındaki sayılara biçimlendirmek istiyorum:

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

Sağdaki sayı uzun veya soldaki sayı dize olacaktır. Buna nasıl yaklaşmalıyım. Zaten bunun için çok az algoritma yaptım ama zaten orada güzel bir iş yapan ve milyarlarca ve trilyonlarla uğraşmaya başlarsam ek test gerektirmeyen bir şey icat edilmiş olabileceğini düşündüm :)

Ek gereksinimler:

  • Biçim en fazla 4 karakterden oluşmalıdır
  • Yukarıda 1.1k iyi demektir 11.2k iyi değildir. 7.8m için de aynı sorun yok 19.1m değil. Ondalık noktasından önceki yalnızca bir basamakta ondalık basamağa izin verilir. Ondalık basamaktan önceki iki basamak, ondalık basamaktan sonraki basamak anlamına gelmez.
  • Yuvarlama gerekmez. (K ve m ile eklenmiş sayılar, mantığın tam olarak olmadığını gösteren analog göstergeden daha fazladır. Bu nedenle yuvarlama, değişkenin doğası nedeniyle, önbelleğe alınmış sonuca bakarken bile birkaç basamağı artırabilecek veya azaltabileceğinden önemli değildir.)

1
Hiç kimse bir kütüphaneye sahip değilse, kodunuzu göndermeyi düşünür müsünüz?
Grammin

1
Bu bir yardımcı olmasa da yardımcı olabilir. stackoverflow.com/questions/529432
rfeak

1
@ Mat Daha önce hangi çözümü kullandığınızı merak ediyordum. Eğer sakıncası yoksa bir cevap olarak da gönderir misiniz.
jzd

1
Bunun arkasındaki fikir No rounding is necessarybana saçma geliyor. Sadece işleri karmaşıklaştırmak mı? Bunu yeniden ifade etmek daha iyi olmaz Rounding is not necessary, but welcomemı?
Wolf

1
Eğer k ve m ile gösterilen sayıların fark edilmemesi durumunda, kesin mantık değil, yaklaşımı gösteren daha fazla analog gösterge vardır. Bu nedenle, yuvarlama, paralı sonuca bakarken bile birkaç basamağı artırabilecek veya azaltabileceğinden, değişkenin doğası nedeniyle önemsizdir.
Mat B.

Yanıtlar:


155

İşte herhangi bir uzun değer için çalışan ve oldukça okunabilir bulduğum bir çözüm (çekirdek mantık, alt üç satırda yapılırformat yöntemin ).

Bu güçlendirir TreeMapuygun ekini bulmak için. Diziler kullanan ve okunması daha zor olan önceki bir çözümden şaşırtıcı derecede daha etkilidir.

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

Test kodu

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
Güzel çözüm. Görünüşe göre bu çok büyük sayılara (katrilyon, quintillion, vb.) Daha fazla sonek ekleyebilirsiniz ve çıktı ölçeklenmeye devam ediyor.
Cypher

Kodunuz negatif sayılarla tam olarak doğru değil: -5821olarak -5kdeğil, olarak biçimlendirilmelidir -5.8k.
std.denis

1
@ std.denis OP negatif sayıların nasıl biçimlendirileceğini göstermedi. Onları pozitif sayılar gibi biçimlendirmeye karar verdim, ancak -aynı sayıda önemli basamağı tutmak için ön ek getirdim . Başka seçenekler de var ...
Assylias

1
Birincisi: Kötü yorumları sildim, çünkü bu senin hatan değil. İkincisi: İyi cevapların diğerlerinden daha fazla aldıkları sürece yeterince dikkat çekmemesi sorunu değildir, ancak genellikle iyi cevaplar için kazmanız gerekir ve sadece bazı yanlış, kötü veya genel cevaplar yükseltilir (gerçekten yeni şeyler öğrenmek kötü). Ve zaten birçok cevap varken nimet veren insanlar için neyin eksik olduğunu daha net bir şekilde belirtmeyi ve ardından kriterlere en uygun cevabı dikkatlice seçmeyi
beklerdim

1
ama bütün dünya bu standardı anlıyor mu? dünyadaki herkes için uygulama yaparsanız dikkatli olun. İngilizce için 10M ama Rusça için 10 млн ve benzeri
user924

101

Biliyorum, bu daha çok bir C programına benziyor, ama süper hafif!

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

}

Çıktıları:

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

16
Gizli kod. Bugünlerde böyle kodlamak zorunda değiliz. Beklendiği gibi çalışabilir, ancak yazarı Roger C. Martin'e
Andreas Dolk

30
Gizlenmiş? Afedersiniz, ama muhtemelen bir kitap okudunuz ve bugünlerde bir şekilde farklı kod yazabileceğinizi düşünüyorsunuz. Joel'e ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) bunu anlatın . Muhtemelen benim yöntemimin hızına yakın bir yere almak için yazabilirsiniz herhangi bir kod cesaret!
Elijah Saounkine

11
D, c, n değişkenlerini daha okunabilir bir şeye değiştirmek (daha hızlı anlama) bu kodu benim görüşüme göre iyi yapar
Gennadiy Ryabkin

5
Neden bu performans takıntısı? Neden birisi performans hakkında düşünmeyi bile garanti altına almak için bu dönüşümlerden yeterince sayıda yürütmek istesin ki? Önce okunabilirlik, yalnızca gerektiğinde performans ayarı.
Amos M. Carpenter

10
@ AmosM.Carpenter ile aynı fikirdeyim. Bu yanıtı 4 yıl önce yazarken kodun sürdürülebilirliğini çok az biliyordum. Optimize etmek kötü değil, genel olarak konuşursak, ama önce okunabilirlik geliyor. Bu arada, performans açısından o kadar da kötü değil: maraca'nın yazdığından 5 kat daha yavaş değil - hemen hemen aynı (burada bir kıyaslama için bazı çözümleri koydum github.com/esaounkine/number-format- kıyaslama ).
Elijah Saounkine

43

İşte DecimalFormat'ın mühendislik gösterimini kullanan bir çözüm:

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

Çıktı:

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 Yeni gereksinimleri karşılamak için güncellendi
jzd

Para birimi ile benzer işlevsellik elde etmek için bunu Para Birimi Örneği ile birleştirmenin kolay bir yolu var mı?
xdumaine

@roviuser, ne demek istediğinden emin değilim, ama bu ayrı bir soru gibi geliyor.
jzd


4
Bu bozuk, 10000000000000.0 sayısını girdim ve 103 diyor.
Oliver Dixon

23

Biraz iyileştirme gerekiyor, ama: StrictMath kurtarmak için!
Soneki bir String veya diziye koyabilir ve güce veya buna benzer bir şeye göre getirebilirsiniz.
Bölünme de güç etrafında yönetilebilir, sanırım neredeyse her şey güç değeri ile ilgilidir. Umarım yardımcı olur!

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

çıktılar:

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


2
Geliştirilmiş okunabilirlik biraz, Sadece 4 char sorunu çözmek için jzd dönüş ifadesi eklemek gerekiyordu. Ve AIOOB istisnasını önlemek için t üzerinden giderseniz sonek eklemeyi unutmayın. ;)
jhurtado

Bu kod yerel ayara duyarlıdır, örneğin sv_SE yerel ayarı 1000 normal ifadeyle doğru şekilde eşleşmeyen 10x10³ değerine dönüştürülür.
Joakim Lundborg

2
0 için bir istisna atar, negatif sayılar için çalışmaz, 9,999,999'u düzgün yuvarlamaz (10m yazdırır) ...
assylias

16

Güncel Yanıtlarla İlgili Sorunlar

  • Mevcut çözümlerin birçoğu bu önekleri k = 10 kullanan 3 , m = 10, 6 , b = 10 olduğu 9 t = 10, 12 . Bununla birlikte, uygun olarak , çeşitli kaynaklardan , doğru ön ekleri, k = 10 olan 3 , E = 10 6 = 10, G, 9 , T = 10 12
  • Negatif sayılar için destek eksikliği (veya en azından negatif sayıların desteklendiğini gösteren test eksikliği)
  • Ters işlem için destek eksikliği, örneğin 1.1k'yi 1100'e dönüştürme (bu orijinal sorunun kapsamı dışında olsa da)

Java Çözümü

Bu çözüm ( bu cevabın bir uzantısı ) yukarıdaki sorunları ele almaktadır.

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

Mükemmel Çözüm

Çözelti başlangıçta Groovy'de aşağıda gösterildiği gibi yazılmıştır.

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

Testler (Harika)

Testler Groovy ile yazılmıştır, ancak Java veya Groovy sınıfını doğrulamak için kullanılabilir (çünkü her ikisi de aynı ada ve API'ye sahiptir).

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
Sanırım milyarlarca ve trilyondan bahsettiği için CS öneklerini düşünüyorsunuz sanırım kısa ölçekli rakamlar istiyor.
jhurtado

1
9999999, 9,9m olarak yazdırmalıdır (sayılar yuvarlatılmış, kesilmiş).
assylias

Bu çözüm, 1'den küçük değerler için önekleri desteklemez, örn. U (mikro) ve m (milli).
gbmhunter

13

Yoğun bakım lib yoğun bakım size okunabilir ve maintanable çözüm verecekti kullanarak düşünüyorum ben numara heceleme vb için kullanılabilecek numaraları için bir kural tabanlı biçimlendirici vardır.

[Kullanım]

Sağ sınıf RuleBasedNumberFormat. Biçimin kendisi ayrı bir dosya (veya String sabiti, IIRC) olarak saklanabilir.

Http://userguide.icu-project.org/formatparse/numbers adresinden bir örnek

double num = 2718.28;
NumberFormat formatter = 
    new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
String result = formatter.format(num);
System.out.println(result);

Aynı sayfada Romen rakamları gösterildiğinden, davanızın da mümkün olması gerektiğini düşünüyorum.


Yerelleştirmeye ihtiyacınız varsa, iş parçacığında tamamen parçalanmayan tek çözüm.
Grozz

2
Android geliştirme için ihtiyacınız varsa, bu zaten çerçeveye dahil edilmiştir. Bakın CompactDecimalFormat. API Seviyesi 24+
Gökhan Arik

10

İle Java 12 + kullanabileceğiniz NumberFormat.getCompactNumberInstancesayıları biçimlendirmek için. Olarak bir NumberFormatilk oluşturabilirsiniz

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

ve daha sonra aşağıdakileri yapmak için kullanın format:

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

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

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

8

Önemli: döküm Cevapları doublegibi numaralar için başarısız olur 99999999999999999Lve dönüş 100Pyerine 99Pçünkü doublekullanımları IEEEstandart :

En fazla 15 anlamlı basamağa sahip bir ondalık dize, IEEE 754 çift kesinlikli gösterime dönüştürülür ve daha sonra aynı sayıda önemli basamak içeren bir dizeye dönüştürülürse, son dize orijinal ile eşleşmelidir. [ longSahiptir kadar 19 önemli basamak .]

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

Bu çözüm istenmeyen rakamları keser ve tüm longdeğerler için çalışır . Basit ama performanslı uygulama (aşağıda karşılaştırma). -120k 4 karakterle ifade edilemez, hatta -0.1M çok uzun, bu nedenle negatif sayılar için 5 karakterin iyi olması gerekir:

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

Başlangıçtaki test else ifnecessairy'dir çünkü min -(2^63)ve max olur (2^63)-1ve bu nedenle ödev number = -numberbaşarısız olur number == Long.MIN_VALUE. Bir kontrol yapmak zorunda kalırsak, sadece kontrol etmek yerine mümkün olduğunca fazla sayı ekleyebiliriznumber == Long.MIN_VALUE .

Bu uygulamanın en yüksek oyu alan (şu anda en hızlı olduğu söylenen) ile karşılaştırılması, 5 kattan daha hızlı olduğunu (test ayarlarına bağlı olduğunu, ancak daha fazla sayıyla kazancın büyüdüğünü ve bu uygulamanın tüm vakaları ele aldığı için daha fazla kontrol yapmak, böylece diğeri düzeltilecekse fark daha da büyüyecektir) Bu kadar hızlıdır çünkü kayan nokta işlemleri, logaritma, güç yok, özyineleme yok, normal ifade yok, karmaşık biçimlendiriciler yok ve oluşturulan nesne miktarını en aza indiriyor.


İşte test programı:

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

}

Olası çıktı: 2309 vs. 11591(sadece pozitif sayılar kullanıldığında aynı ve yürütme sırasını ters çevirirken çok daha aşırı, belki çöp toplama ile ilgili bir şey var)


8

İşte özyineleme olmadan kısa bir uygulama ve sadece çok küçük bir döngü. Negatif sayılarla çalışmaz, ancak aşağıdakilere longkadar olan tüm pozitif değerleri destekler 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);
}

Çıktılar:

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

Ayrıca gerçekten basit bir kıyaslama yaptım (10 milyon rasgele uzunluğa formatlama) ve Elijah'ın uygulamasından önemli ölçüde daha hızlı ve asilliler uygulamasından biraz daha hızlı.

Maden: 1137.028 ms
Elijah's: 2664.396 ms
assylias ': 1373.473 ms


1
Son güncellemenize bir hata eklediniz. Şimdi 101800 numarası için 1k döndürüyor .
Sufyan

2
Fark

8

Yuvarlamak isteyen herkes için. Bu, Java.Lang.Math kütüphanesinden yararlanan harika, okunması kolay bir çözümdür

 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

Aşağıdaki kod, bunu kolay genişletmeyi göz önünde bulundurarak nasıl yapabileceğinizi gösterir.

"Büyü", çoğunlukla makeDecimal, doğru değerler için, çıktıda asla dörtten fazla karakterin olmayacağını garanti eden fonksiyonda yatar .

Önce belirli bir bölen için tüm ve onuncu bölümleri çıkarır, böylece örneğin 12,345,678bir bölen ile 1,000,000bir wholedeğer 12ve bir tenthsdeğer verecektir 3.

Bundan, kuralları kullanarak sadece tüm parçayı mı yoksa hem tamamını hem de onda birini mi çıkardığına karar verebilir:

  • Onuncu kısım sıfırsa, tüm parçayı ve soneki çıkarmanız yeterlidir.
  • Tüm parça dokuzdan büyükse, tüm parçayı ve soneki çıkarmanız yeterlidir.
  • Aksi takdirde, tüm parçayı, onda bir parçayı ve soneki çıkar.

Bunun kodu şöyledir:

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

Ardından, geliştirici için hayatı kolaylaştırmak için bazı sabitler de dahil olmak üzere bu yardımcı işlevi doğru değerlerle çağırmak basit bir konudur:

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

Aslında makeDecimalişlevi ötesinde, genişleyen bu homurtu çalışma araçları yapar 999,999,999ekstra bir satır ekleyerek meselesi olduğunuXlat o kadar kolay senin için yaptık.

nihai returniçindeXlat uzun imzalanan 64 bit tutabilir büyük değeri yalnızca 9,2 kentilyon yaklaşık olduğundan koşullu ihtiyacı yoktur.

Ancak, tuhaf bir gereksinimle, Oracle 128 bitlik bir longertür veya 1024 bitlik bir tür eklemeye karar verirse damn_long, buna hazır olacaksınız :-)


Ve son olarak, işlevselliği doğrulamak için kullanabileceğiniz küçük bir test takımı.

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

Çıktıdan size ihtiyacınız olanı verdiğini görebilirsiniz:

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

Ve bir kenara, bu işleve negatif bir sayı aktarmanın, < THOUyolu takip ettiği için gereksinimleriniz için çok uzun bir dizeyle sonuçlanacağını unutmayın ). Sadece soruda negatif olmayan değerlerden bahsettiğiniz için iyi olduğunu düşündüm.


6

En iyi yaklaşım olup olmadığını bilmiyorum ama ben de öyle yaptım.

7=>7
12=>12
856=>856
1000=>1.0k
5821=>5.82k
10500=>10.5k
101800=>101.8k
2000000=>2.0m
7800000=>7.8m
92150000=>92.15m
123200000=>123.2m
9999999=>10.0m

--- Kod ---

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

6

Büyük sayıyı küçük sayıya dönüştürme işlevim (2 basamaklı). Sen değişiklikten basamak sayısını değiştirebilirsiniz #.##içindeDecimalFormat

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

Test yapmak

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

Umarım yardımcı olur


5

Benim Java paslı, ama ben nasıl C # uygulamak istiyorum:

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

Bunu, metrik kilo yerine CS kilo (1,024) kullanacak veya daha fazla birim ekleyecek şekilde ayarlamak kolay olurdu. 1.000'i "1 k" yerine "1.0 k" olarak biçimlendiriyor, ancak bunun önemsiz olduğuna inanıyorum.

Daha özel bir gereksinimi "en fazla dört karakter" karşılamak için, soneklerin önündeki boşlukları kaldırın ve orta bloğu şu şekilde ayarlayın:

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

1
Ne yazık ki bu ToStringyöntem Java'da mevcut değildir - başka sorunlar (yerel ayara duyarlı vb.) Oluşturabilecek bir NumberFormat'a ihtiyacınız olacaktır.
assylias

5

Benim favorim. "K" ve benzerlerini, elektronik etki alanında yaygın olarak, ondalık göstergesi olarak da kullanabilirsiniz. Bu size ek alan olmadan fazladan bir rakam verecektir

İkinci sütun mümkün olduğunca fazla basamak kullanmaya çalışır

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

Bu kod

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

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

4

Performansın üzerinde okunabilirliğe değer vereceğime dair yorumuma sadık kalarak, burada ne olduğunu netleştirmesi gereken bir sürüm (kullandığınızı varsayarsak) BigDecimal performans hakkında endişelenmeden aşırı yorum yapmadan (kendimi belgeleme koduna inanıyorum) (çünkü bunu milyonlarca kez bu performansın dikkate alınması gereken bir senaryoyu hayal edemiyorum).

Bu versiyon:

  • BigDecimalHassasiyet ve yuvarlama sorunlarından kaçınmak için s'yi kullanır
  • OP tarafından istendiği gibi aşağı yuvarlama işleri
  • diğer yuvarlama modları için çalışır, örneğin HALF_UPtestlerde olduğu gibi
  • hassasiyeti ayarlamanıza olanak tanır (değiştir REQUIRED_PRECISION )
  • enumeşikleri tanımlamak için a kullanır , yani k / m / b / t vb. yerine KB / MB / GB / TB kullanmak üzere kolayca ayarlanabilir ve elbette ötesine uzatılabilirTRILLION gerekirse
  • söz konusu test senaryoları sınırları test etmediği için kapsamlı birim testleri ile birlikte gelir
  • sıfır ve negatif sayılar için çalışmalı

Eşik.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;
  }
}

( printlnAçıklamaların önerilerini kaldırın veya ne yaptığını görmek için favori günlükçünüzü kullanmak üzere değiştirin.)

Ve son olarak, NumberShortenerTest'teki testler (düz 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));
    }
  }
}

Önemli bir test senaryosunu kaçırdıysam veya beklenen değerlerin ayarlanması gerekiyorsa yorumlarda belirtmekten çekinmeyin.


Çözümünüzdeki tek belirgin dezavantaj, kodunuz için V + H kaydırma çubukları gibi görünüyor, bu da okunabilirliği düşürüyor. Sizce netliği kaybetmeden yeniden biçimlendirmenin mümkün olacağını düşünüyor musunuz?
Kurt

@Wolf: IDE'mden kopyalama / yapıştırma ile kurtulmayı umuyordum, ama haklısın, okunabilirlik iddiasında bulunmak ve yatay kaydırma yapmak benim için ikiyüzlü, bu yüzden işaret ettiğiniz için teşekkürler. ;-) Ne olduğunu görmek için bakacağınız kodların ilk iki parçasını güncelledim, ancak test kodunu bıraktım, çünkü tek başına bakmak çok yararlı değil - d, testlerin işe yaradığını görmek istiyorsanız, birim testleri çalıştırmak için bunu kendi IDE'nize yapıştırmak istersiniz. Umarım bu tamam.
Amos M. Carpenter

Ah iyi. Ancak son kutuda, test senaryolarında, beklenen sonuçlar - optik olarak - girdilerle daha iyi ilişkili olabilir (ilk 6 dizideki değişmezleri kastediyorum).
Kurt

@Wolf: Bir satırdaki öğeleri boşluklar veya sekmelerle hizalamaya çalışmaktan hoşlanmıyorum - bu benim favori biçimlendiricimdeki (tutulması) tüm durumlar için tutarlı bir şekilde kolayca yapılandırılamıyor ve elle yapılıyor ... bu şekilde delilik yatıyor , her öğe eklediğinizde veya kaldırdığınızda yapmanız gereken tüm ayarlamalar nedeniyle. Eğer gerçekten hizalı görmek isteseydim, sadece sayıları / değerleri bir elektronik tabloya CSV olarak yapıştırırdım.
Amos M. Carpenter

1
Her şey senin peşinden gitmene bağlı, @assylias. Bir kerelik kullanım durumunu çözdükten hemen sonraysanız, çözümünüz iyi çalışmalıdır; TreeMapYaklaşımı seviyorum . "Okunabilirlik" elbette özneldir. ;-) Şimdi ya birisi sürümünüzü kısaltmaktan farklı bir şekilde yuvarlamak isterse? (Örneğin, bunu dosya boyutunu belirtmek için kullanırken, kim kısaltmak ister?) 10 yerine 2 güç istiyorsanız? Adil bir şekilde yeniden yazmak zorunda kalırdın, değil mi? Dediğim gibi, kasıtlı olarak kodumu golf etmeye çalışmıyordum, bunların çoğu kısaltılabilirdi (örneğin bir satırda asla if-then tutmazdım).
Amos M. Carpenter

4

bu benim kodum. temiz ve basit.

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

Kendi cevabımı, Java kodunu, açıklayıcı kodumu ekleyerek ..

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

Bu kod snippet'i sadece ölümcül basit ve temiz kod ve tamamen çalışıyor:

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

bunu dene :

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

Çıktı:

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
bu, tüm kenar durumlarınız için çalışmaz. Örneğin 999'u deneyin.
jzd
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.