Cách định dạng 1200 đến 1,2k trong java


156

Tôi muốn định dạng các số sau thành các số bên cạnh chúng bằng 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

Số bên phải sẽ dài hoặc số nguyên, số bên trái sẽ là chuỗi. Làm thế nào tôi nên tiếp cận điều này. Tôi đã thực hiện một số thuật toán nhỏ cho việc này nhưng tôi nghĩ có thể đã có một thứ gì đó được phát minh ra ngoài đó có công việc tốt hơn và không yêu cầu thử nghiệm bổ sung nếu tôi bắt đầu xử lý hàng tỷ và hàng nghìn tỷ :)

Các yêu cầu bổ sung:

  • Định dạng phải có tối đa 4 ký tự
  • Trên có nghĩa là 1.1k là OK 11.2k thì không. Tương tự cho 7.8m là OK 19.1m thì không. Chỉ một chữ số trước dấu thập phân được phép có dấu thập phân. Hai chữ số trước dấu thập phân có nghĩa là không phải chữ số sau dấu thập phân.
  • Không làm tròn là cần thiết. .

1
Nếu không ai có thư viện, bạn có phiền đăng mã của mình không?
Grammin

1
Điều này có thể hỗ trợ, mặc dù đây không phải là bản sao. stackoverflow.com/questions/529432
rfeak

1
@Mat Tôi tò mò không biết bạn đang sử dụng giải pháp nào trước đây. Nếu bạn không phiền, bạn cũng sẽ đăng nó như một câu trả lời.
jzd

1
Ý tưởng đằng sau No rounding is necessaryđiều này có vẻ vô lý với tôi. Có phải chỉ để làm phức tạp mọi thứ? Sẽ không tốt hơn để viết lại điều này Rounding is not necessary, but welcome?
Sói

1
Trong trường hợp bạn không nhận thấy các số được hiển thị với k và m được nối thêm nhiều thước đo tương tự cho thấy phép tính gần đúng không phải là bài viết chính xác của logic. Do đó làm tròn là không liên quan chủ yếu do bản chất của biến hơn có thể tăng hoặc giảm một vài chữ số ngay cả khi bạn đang xem kết quả tiền mặt.
Mat B.

Câu trả lời:


154

Đây là một giải pháp hoạt động cho bất kỳ giá trị dài nào và tôi thấy khá dễ đọc (logic cốt lõi được thực hiện trong ba dòng dưới cùng của formatphương thức).

Nó tận dụng TreeMapđể tìm hậu tố thích hợp. Thật đáng ngạc nhiên là hiệu quả hơn một giải pháp trước đây tôi đã viết đó là sử dụng mảng và khó đọc hơn.

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

Mã kiểm tra

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
Giải pháp tốt đẹp. Có vẻ như bạn chỉ có thể thêm nhiều hậu tố cho những con số thực sự lớn đó (bốn triệu, triệu, v.v.), và đầu ra tiếp tục mở rộng.
Cypher

Mã của bạn không hoàn toàn chính xác với các số âm: -5821nên được định dạng là -5k, không phải là -5.8k.
std.denis

1
@ std.denis OP không cho biết cách định dạng số âm. Tôi quyết định định dạng chúng như các số dương nhưng có tiền tố -để giữ cùng một số chữ số có nghĩa. Có những lựa chọn khác ...
assylias

1
Đầu tiên: Tôi đã xóa các bình luận xấu, vì rõ ràng đó không phải là lỗi của bạn. Thứ hai: Không phải là vấn đề mà câu trả lời tốt không được chú ý miễn là chúng nhận được nhiều hơn những người khác, nhưng vì bạn thường phải tìm kiếm câu trả lời tốt và chỉ một số câu trả lời sai, xấu hoặc chung chung được đưa ra (thực sự xấu để học những thứ mới). Và đối với những người phát hành tiền thưởng khi đã có nhiều câu trả lời, tôi dự kiến ​​sẽ xác định rõ hơn những gì còn thiếu và sau đó cẩn thận chọn câu trả lời phù hợp nhất với tiêu chí này ...
marica

1
Nhưng cả thế giới có hiểu tiêu chuẩn này không? hãy cẩn thận nếu bạn làm ứng dụng cho mọi người trên thế giới. Đối với tiếng Anh, nó là 10 triệu nhưng đối với tiếng Nga là 10 giây và cứ thế
user924

101

Tôi biết, nó trông giống chương trình C hơn, nhưng nó siêu nhẹ!

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

}

Nó xuất ra:

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

16
Mã bị xáo trộn. Ngày nay chúng ta không phải viết mã như thế này. Có thể hoạt động như mong đợi, nhưng tôi khuyến khích tác giả hãy xem Roger C. Martin: Clean Code
Andreas Dolk

29
Bị xáo trộn? Tôi xin lỗi, nhưng có lẽ bạn đã đọc một cuốn sách và nghĩ rằng bạn có thể viết mã bằng cách nào đó ngày nay. Nói với Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) về điều đó. Tôi dám bất kỳ mã nào bạn có thể có thể viết để đạt được bất cứ nơi nào gần với tốc độ của phương pháp của tôi!
Elijah Saounkine

11
Thay đổi các biến d, c, n thành thứ gì đó dễ đọc hơn (hiểu nhanh hơn) làm cho mã này phù hợp với quan điểm của tôi
Gennadiy Ryabkin

5
Tại sao nỗi ám ảnh về hiệu suất này? Tại sao bất cứ ai cũng muốn thực hiện một số lượng lớn các chuyển đổi này để đảm bảo thậm chí nghĩ về hiệu suất ...? Khả năng đọc đầu tiên, hiệu chỉnh chỉ khi cần thiết.
Amos M. Carpenter

10
Tôi phải đồng ý với @ AmosM.Carpenter. Tôi không biết nhiều về khả năng duy trì mã khi tôi viết câu trả lời này 4 năm trước. Nói chung, việc tối ưu hóa NHƯNG là điều không tồi. Nhân tiện, nó không quá tệ về hiệu năng: không chậm hơn 5 lần so với bản marica đã viết - nó cũng tương tự (Tôi đã đưa ra một số giải pháp cho điểm chuẩn ở đây github.com/esaounkine/number-format- điểm chuẩn ).
Elijah Saounkine

43

Đây là một giải pháp sử dụng ký hiệu kỹ thuật của 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;
}

Đầu ra:

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 Cập nhật để xử lý các yêu cầu mới
jzd

Có cách nào dễ dàng để kết hợp điều này với Đơn vị tiền tệ để có được chức năng tương tự với tiền tệ không?
xdumaine

@roviuser, không chắc ý của bạn là gì, nhưng điều này nghe có vẻ như là một câu hỏi riêng biệt.
jzd

7
làm tròn 160000 đến 200k và cũng làm tròn 120000 xuống còn 100k
k1komans

4
Cái này bị hỏng, tôi đã nhập số 10000000000000.0 và nó ghi 103.
Oliver Dixon

23

Cần một số cải tiến, nhưng: StrictMath để giải cứu!
Bạn có thể đặt hậu tố trong Chuỗi hoặc mảng và tìm nạp dựa trên sức mạnh hoặc đại loại như thế.
Sự phân chia cũng có thể được quản lý xung quanh sức mạnh, tôi nghĩ rằng hầu hết mọi thứ là về giá trị sức mạnh. Hy vọng nó giúp!

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

đầu ra:

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


2
Cải thiện khả năng đọc một chút, Chỉ cần thêm câu lệnh return từ jzd để giải quyết vấn đề 4 char. Và nhớ thêm hậu tố nếu đi qua t để tránh ngoại lệ AIOOB. ;)
jhurtado

Mã này nhạy cảm với miền địa phương, ví dụ: trong địa chỉ sv_SE, 1000 chuyển đổi thành 10 x10³, không khớp chính xác với biểu thức chính quy.
Joakim Lundborg

2
ném một ngoại lệ cho 0, không hoạt động đối với các số âm, không làm tròn 9,999.999 đúng cách (in 10m) ...
assylias

16

Các vấn đề với câu trả lời hiện tại

  • Nhiều giải pháp hiện tại đang sử dụng các tiền tố này k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12 . Tuy nhiên, theo nhiều nguồn khác nhau , các tiền tố chính xác là k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • Thiếu hỗ trợ cho số âm (hoặc ít nhất là thiếu các bài kiểm tra chứng minh rằng số âm được hỗ trợ)
  • Thiếu hỗ trợ cho hoạt động nghịch đảo, ví dụ: chuyển đổi 1.1k sang 1100 (mặc dù điều này nằm ngoài phạm vi của câu hỏi ban đầu)

Giải pháp Java

Giải pháp này (một phần mở rộng của câu trả lời này ) giải quyết các vấn đề trên.

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

Giải pháp Groovy

Các giải pháp ban đầu được viết bằng Groovy như dưới đây.

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

Các xét nghiệm (Groovy)

Các thử nghiệm được viết bằng Groovy nhưng có thể được sử dụng để xác minh lớp Java hoặc Groovy (vì cả hai đều có cùng tên và API).

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

class RoundedMetricPrefixFormatTests extends GroovyTestCase {

    private Format roundedMetricPrefixFormat = new RoundedMetricPrefixFormat()

    void testNumberFormatting() {

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

            assertEquals expectedRoundValue, roundedMetricPrefixFormat.format(rawValue)
        }
    }

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

        ].each { String metricPrefixNumber, Number expectedValue ->

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

    void testStringParsingFail() {

        shouldFail(ParseException) {
            roundedMetricPrefixFormat.parseObject('notNumber')
        }
    }
}

1
Tôi nghĩ rằng bạn đang nghĩ về các tiền tố CS, cho rằng anh ta đang nói về hàng tỷ và hàng nghìn tỷ Tôi đoán anh ta muốn các số có tỷ lệ ngắn.
jhurtado

1
9999999 tôi nên in là 9,9m tôi tin (các số bị cắt ngắn, không được làm tròn).
assylias

Giải pháp này không hỗ trợ các tiền tố cho các giá trị nhỏ hơn 1, ví dụ u (micro) và m (milli).
gbmhunter

13

Các lib ICU có một quy tắc dựa formatter đối với số lượng, có thể được sử dụng cho số spellout vv Tôi nghĩ rằng sử dụng ICU sẽ cung cấp cho bạn một giải pháp có thể đọc được và maintanable.

[Sử dụng]

Lớp bên phải là RuleBasingNumberFormat. Bản thân định dạng có thể được lưu trữ dưới dạng tệp riêng biệt (hoặc dưới dạng hằng chuỗi, IIRC).

Ví dụ từ 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);

Cùng một trang hiển thị chữ số La Mã, vì vậy tôi đoán trường hợp của bạn cũng có thể xảy ra.


Giải pháp duy nhất trong luồng không hoàn toàn sụp đổ nếu bạn cần bản địa hóa.
Grozz

2
Nếu bạn cần nó để phát triển Android, điều này đã được bao gồm trong khung. Hãy tìm CompactDecimalFormat. API cấp 24+
Gokhan Arik

10

Với Java-12 + , bạn có thể sử dụng NumberFormat.getCompactNumberInstanceđể định dạng các số. Bạn có thể tạo NumberFormatđầu tiên như

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

và sau đó sử dụng nó để format:

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

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

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

8

Quan trọng: Câu trả lời truyền doublesẽ không thành công cho các số thích 99999999999999999Lvà trả về 100Pthay 99Pdoublesử dụng IEEEtiêu chuẩn :

Nếu một chuỗi thập phân có tối đa 15 chữ số có nghĩa được chuyển đổi thành biểu diễn chính xác kép của IEEE 754 và sau đó được chuyển đổi trở lại thành một chuỗi có cùng số chữ số có nghĩa, thì chuỗi cuối cùng phải khớp với bản gốc. [ longtới 19 chữ số có nghĩa .]

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

Giải pháp này cắt bỏ các chữ số không mong muốn và hoạt động cho tất cả các longgiá trị . Thực hiện đơn giản nhưng thực hiện (so sánh dưới đây). -120k không thể được biểu thị bằng 4 ký tự, thậm chí -0.1M là quá dài, đó là lý do tại sao đối với số âm 5 ký tự phải ổn:

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

Bài kiểm tra else ifban đầu là cần thiết bởi vì min là -(2^63)và max là (2^63)-1và do đó bài tập number = -numbersẽ thất bại nếu number == Long.MIN_VALUE. Nếu chúng ta phải thực hiện kiểm tra, thì chúng ta cũng có thể bao gồm càng nhiều số càng tốt thay vì chỉ kiểm tra number == Long.MIN_VALUE.

So sánh việc thực hiện này với người có nhiều lượt upvote nhất (được cho là nhanh nhất hiện nay) cho thấy rằng nó nhanh hơn gấp 5 lần (nó phụ thuộc vào cài đặt thử nghiệm, nhưng với số lượng nhiều hơn thì mức tăng sẽ lớn hơn và việc thực hiện này có để thực hiện nhiều kiểm tra hơn vì nó xử lý tất cả các trường hợp, vì vậy nếu một trường hợp khác được khắc phục, sự khác biệt sẽ còn lớn hơn nữa). Nó nhanh như vậy bởi vì không có hoạt động điểm nổi, không logarit, không sức mạnh, không đệ quy, không regex, không có trình định dạng tinh vi và giảm thiểu số lượng đối tượng được tạo.


Đây là chương trình thử nghiệm:

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

}

Đầu ra có thể: 2309 vs. 11591(tương tự khi chỉ sử dụng số dương và cực hơn nhiều khi đảo ngược thứ tự thực hiện, có thể nó có liên quan đến bộ sưu tập rác)


8

Đây là một triển khai ngắn mà không cần đệ quy và chỉ là một vòng lặp rất nhỏ. Không hoạt động với số âm nhưng hỗ trợ tất cả các số dương longlên đến 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);
}

Đầu ra:

1k
5.8k
10k
101k
2m
7,8 m
92m
123m
9.2e (đây là Long.MAX_VALUE)

Tôi cũng đã thực hiện một số điểm chuẩn thực sự đơn giản (định dạng 10 triệu thời gian ngẫu nhiên) và nó nhanh hơn đáng kể so với triển khai của Elijah và nhanh hơn một chút so với triển khai của assylias.

Của tôi: 1137.028 ms
Elijah's: 2664.396 ms
assylias ': 1373.473 ms


1
Trong bản cập nhật cuối cùng của bạn, bạn đã thêm một lỗi. Bây giờ trả về 1k cho số 101800 .
Sufian

2
Cảm ơn vì đã chú ý, nó đã được sửa
Raniz

8

Đối với bất cứ ai muốn làm tròn. Đây là một giải pháp tuyệt vời, dễ đọc, tận dụng thư viện Java.Lang.Math

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

8

Đoạn mã sau cho thấy cách bạn có thể làm điều này với ý tưởng mở rộng dễ dàng.

"Phép thuật" chủ yếu nằm ở makeDecimalchức năng, với các giá trị chính xác được truyền vào, đảm bảo bạn sẽ không bao giờ có nhiều hơn bốn ký tự trong đầu ra.

Đầu tiên, nó trích xuất toàn bộ và một phần mười cho một ước số đã cho, ví dụ, 12,345,678với một ước số 1,000,000sẽ cho một wholegiá trị 12tenthsgiá trị của 3.

Từ đó, nó có thể quyết định liệu nó chỉ xuất ra toàn bộ phần hay cả phần thứ mười và phần mười, bằng cách sử dụng các quy tắc:

  • Nếu phần mười bằng không, chỉ cần xuất toàn bộ phần và hậu tố.
  • Nếu toàn bộ phần lớn hơn chín, chỉ cần xuất toàn bộ phần và hậu tố.
  • Mặt khác, đầu ra toàn bộ phần, phần mười và hậu tố.

Mã cho sau:

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

Sau đó, việc gọi hàm trợ giúp đó với các giá trị chính xác là một vấn đề đơn giản, bao gồm một số hằng số để giúp nhà phát triển dễ dàng hơn:

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

Thực tế là makeDecimalchức năng này hoạt động tốt có nghĩa là mở rộng ra ngoài 999,999,999chỉ là vấn đề thêm một dòng bổ sung Xlat, dễ dàng đến mức tôi đã thực hiện nó cho bạn.

Trận chung kết returntrong Xlatkhông cần một điều kiện vì giá trị lớn nhất bạn có thể giữ trong một 64-bit đã ký dài chỉ khoảng 9,2 quintillion.

Nhưng nếu, theo một số yêu cầu kỳ lạ, Oracle quyết định thêm loại 128 bit longerhoặc loại 1024 bit damn_long, bạn sẽ sẵn sàng cho nó :-)


Và cuối cùng, một khai thác thử nghiệm nhỏ mà bạn có thể sử dụng để xác nhận chức năng.

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

Bạn có thể thấy từ đầu ra mà nó cung cấp cho bạn những gì bạn cần:

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

Và, như một bên, lưu ý rằng việc chuyển một số âm cho hàm này sẽ dẫn đến một chuỗi quá dài so với yêu cầu của bạn, vì nó đi theo < THOUđường dẫn). Tôi cho rằng điều đó là ổn vì bạn chỉ đề cập đến các giá trị không âm trong câu hỏi.


6

Tôi không biết đó có phải là cách tiếp cận tốt nhất không, nhưng đây là những gì tôi đã là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

--- Mã ---

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

Hàm của tôi để chuyển đổi số lớn thành số nhỏ (có 2 chữ số). Bạn có thể thay đổi số chữ số bằng cách thay đổi #.##trongDecimalFormat

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

Kiểm tra

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

Hy vọng nó sẽ giúp


5

Java của tôi bị gỉ, nhưng đây là cách tôi triển khai nó trong 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");
    }

Thật dễ dàng để điều chỉnh điều này để sử dụng kilôgam CS (1.024) thay vì kilôgam hoặc để thêm nhiều đơn vị hơn. Nó định dạng 1.000 là "1.0 k" thay vì "1 k", nhưng tôi tin rằng đó là phi vật chất.

Để đáp ứng yêu cầu cụ thể hơn "không quá bốn ký tự", hãy xóa khoảng trắng trước hậu tố và điều chỉnh khối giữa như thế này:

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

1
Thật không may, ToStringphương thức này không tồn tại trong Java - bạn sẽ cần NumberFormat có thể tạo ra các vấn đề khác (nhạy cảm cục bộ, v.v.).
assylias

5

Yêu thích của tôi. Bạn cũng có thể sử dụng "k", v.v. làm chỉ báo cho số thập phân, như là phổ biến trong miền điện tử. Điều này sẽ cung cấp cho bạn một chữ số thêm mà không cần thêm không gian

Cột thứ hai cố gắng sử dụng càng nhiều chữ số càng tốt

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

Đây là mã

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

Theo đúng nhận xét của tôi rằng tôi đánh giá cao khả năng đọc trên hiệu suất, đây là phiên bản cần rõ ràng những gì đang xảy ra (giả sử bạn đã sử dụng BigDecimal trước đó) mà không cần bình luận quá mức (tôi tin vào mã tự viết tài liệu), mà không phải lo lắng về hiệu suất (vì tôi không thể hình dung ra một kịch bản mà bạn muốn thực hiện điều này nhiều triệu lần mà hiệu suất thậm chí trở thành một sự cân nhắc).

Phiên bản này:

  • sử dụng BigDecimal s cho độ chính xác và để tránh các vấn đề làm tròn
  • làm việc để làm tròn xuống theo yêu cầu của OP
  • hoạt động cho các chế độ làm tròn khác, ví dụ HALF_UP như trong các thử nghiệm
  • cho phép bạn điều chỉnh độ chính xác (thay đổi REQUIRED_PRECISION )
  • sử dụng một enumđể xác định ngưỡng, nghĩa là có thể dễ dàng điều chỉnh để sử dụng KB / MB / GB / TB thay vì k / m / b / t, v.v., và tất nhiên có thể được mở rộng ra ngoàiTRILLION nếu cần
  • đi kèm với các bài kiểm tra đơn vị kỹ lưỡng, vì các trường hợp kiểm tra trong câu hỏi không kiểm tra các đường viền
  • nên làm việc cho số không và số âm

Ngưỡng.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;
  }
}

(Bỏ ghi chú printlnhoặc thay đổi để sử dụng trình ghi nhật ký yêu thích của bạn để xem nó đang làm gì.)

Và cuối cùng, các thử nghiệm trong NumberShortenerTest (JUnit 4):

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.junit.Test;

public class NumberShortenerTest {

  private static final long[] NUMBERS_FROM_OP = new long[] { 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000 };
  private static final String[] EXPECTED_FROM_OP = new String[] { "1k", "5.8k", "10k", "101k", "2m", "7.8m", "92m", "123m" };
  private static final String[] EXPECTED_FROM_OP_HALF_UP = new String[] { "1k", "5.8k", "11k", "102k", "2m", "7.8m", "92m", "123m" };
  private static final long[] NUMBERS_TO_TEST = new long[] { 1, 500, 999, 1000, 1001, 1009, 1049, 1050, 1099, 1100, 12345, 123456, 999999, 1000000,
      1000099, 1000999, 1009999, 1099999, 1100000, 1234567, 999999999, 1000000000, 9123456789L, 123456789123L };
  private static final String[] EXPECTED_FROM_TEST = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1k", "1k", "1.1k", "12k", "123k",
      "999k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.2m", "999m", "1b", "9.1b", "123b" };
  private static final String[] EXPECTED_FROM_TEST_HALF_UP = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1.1k", "1.1k", "1.1k", "12k",
      "123k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.1m", "1.2m", "1b", "1b", "9.1b", "123b" };

  @Test
  public void testThresholdFor() {
    assertEquals(Threshold.ZERO, Threshold.thresholdFor(1));
    assertEquals(Threshold.ZERO, Threshold.thresholdFor(999));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1000));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1234));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(9999));
    assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(999999));
    assertEquals(Threshold.MILLION, Threshold.thresholdFor(1000000));
  }

  @Test
  public void testToPrecision() {
    RoundingMode mode = RoundingMode.DOWN;
    assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
    assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
    assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
    assertEquals(new BigDecimal("1.234"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode).stripTrailingZeros()
        .toPlainString());

    mode = RoundingMode.HALF_UP;
    assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
    assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
    assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
    assertEquals(new BigDecimal("1.235"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
        .toPlainString());
    assertEquals(new BigDecimal("1000").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode)
        .stripTrailingZeros().toPlainString());
  }

  @Test
  public void testNumbersFromOP() {
    for (int i = 0; i < NUMBERS_FROM_OP.length; i++) {
      assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP[i],
          NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP_HALF_UP[i],
          NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.HALF_UP));
    }
  }

  @Test
  public void testBorders() {
    assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.DOWN));
    assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.HALF_UP));
    for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
      assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST[i],
          NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST_HALF_UP[i],
          NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
    }
  }

  @Test
  public void testNegativeBorders() {
    for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
      assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST[i],
          NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.DOWN));
      assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST_HALF_UP[i],
          NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
    }
  }
}

Vui lòng chỉ ra trong các nhận xét nếu tôi bỏ lỡ một trường hợp thử nghiệm quan trọng hoặc nếu cần điều chỉnh các giá trị dự kiến.


Nhược điểm rõ ràng duy nhất trong giải pháp của bạn là thanh cuộn V + H cho mã của bạn, điều này làm giảm khả năng đọc. Bạn có nghĩ rằng một định dạng lại sẽ có thể mà không mất đi sự rõ ràng?
Sói

@Wolf: Đã hy vọng thoát khỏi việc sao chép / dán từ IDE của tôi, nhưng bạn nói đúng, tôi đạo đức giả để yêu cầu khả năng đọc và yêu cầu cuộn ngang, vì vậy cảm ơn vì đã chỉ ra điều đó. ;-) Tôi đã cập nhật hai bit mã đầu tiên, vì đó là những đoạn mã bạn đang xem để xem điều gì đang xảy ra, nhưng để lại mã kiểm tra, vì tự nó nhìn vào nó không hữu ích lắm - bạn ' Có lẽ bạn muốn dán nó vào IDE của riêng bạn để chạy các bài kiểm tra đơn vị nếu bạn muốn thấy các bài kiểm tra đó hoạt động. Hy vọng là ok.
Amos M. Carpenter

À! Giỏi. Nhưng trong hộp cuối cùng, các trường hợp thử nghiệm, kết quả dự kiến ​​có thể - về mặt quang học - liên quan tốt hơn đến các yếu tố đầu vào (ý tôi là nghĩa đen trong 6 mảng đầu tiên).
Sói

@Wolf: Tôi không phải là người thích thử sắp xếp các mục trong một dòng bằng dấu cách hoặc tab - không thể dễ dàng cấu hình nhất quán cho tất cả các trường hợp trong trình định dạng yêu thích của tôi (nhật thực) và thực hiện thủ công ... theo cách đó là sự điên rồ , bởi vì tất cả các điều chỉnh bạn phải thực hiện mỗi lần bạn thêm hoặc xóa một mục. Nếu tôi thực sự muốn thấy chúng được căn chỉnh, tôi chỉ cần dán các số / giá trị vào bảng tính dưới dạng CSV.
Amos M. Carpenter

1
Tất cả phụ thuộc vào những gì bạn đang theo đuổi, @assylias. Nếu bạn chỉ sau khi giải quyết trường hợp sử dụng một lần, giải pháp của bạn sẽ hoạt động tốt; Tôi thích TreeMapcách tiếp cận. "Khả năng đọc" là chủ quan, tất nhiên. ;-) Bây giờ nếu ai đó muốn làm tròn khác với cắt ngắn trong phiên bản của bạn thì sao? (Ví dụ: khi sử dụng phần này để biểu thị kích thước tệp, ai muốn cắt bớt?) Nếu bạn muốn quyền hạn bằng 2 chứ không phải 10? Bạn sẽ phải viết lại một chút công bằng, phải không? Như tôi đã nói, tôi đã cố tình không chơi mã của mình, phần lớn trong số đó có thể được rút ngắn (chẳng hạn, tôi sẽ không bao giờ giữ một if-then trên một dòng).
Amos M. Carpenter

4

đây là mã của tôi sạch sẽ và đơn giản.

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

Thêm câu trả lời của riêng tôi, mã Java, mã tự giải thích ..

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

Đoạn mã này chỉ đơn giản chết người, mã sạch và hoàn toàn hoạt động:

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

thử cái này :

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

Đầu ra:

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
điều này không làm việc cho tất cả các trường hợp cạnh của bạn. Hãy thử 999 chẳng hạn.
jzd
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.