จะทำอย่างไรเกี่ยวกับการจัดรูปแบบ 1200 ถึง 1.2k ใน java


156

ฉันต้องการจัดรูปแบบตัวเลขต่อไปนี้เป็นตัวเลขถัดจากพวกเขาด้วย 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

ตัวเลขทางด้านขวาจะยาวหรือจำนวนเต็มจำนวนทางด้านซ้ายจะเป็นสตริง ฉันควรเข้าหาสิ่งนี้อย่างไร ฉันทำอัลกอริธึมเล็กน้อยสำหรับเรื่องนี้แล้ว แต่ฉันคิดว่าอาจมีบางสิ่งที่คิดค้นขึ้นมาแล้วซึ่งทำงานได้ดีกว่าและไม่ต้องการการทดสอบเพิ่มเติมหากฉันเริ่มรับมือกับพันล้านล้านล้าน :)

ข้อกำหนดเพิ่มเติม:

  • รูปแบบควรมีความยาวสูงสุด 4 อักขระ
  • วิธีการดังกล่าวข้างต้น 1.1k เป็น OK 11.2k ไม่ เช่นเดียวกันสำหรับ 7.8m ก็โอเค 19.1m ไม่ได้ เพียงหนึ่งหลักก่อนจุดทศนิยมจะได้รับอนุญาตให้มีจุดทศนิยม ตัวเลขสองหลักก่อนจุดทศนิยมหมายถึงไม่ใช่ตัวเลขหลังจุดทศนิยม
  • ไม่จำเป็นต้องปัดเศษ (ตัวเลขที่แสดงด้วย k และ m ต่อท้ายเป็นมาตรวัดแบบอะนาล็อกมากกว่าซึ่งบ่งบอกถึงการประมาณไม่แม่นยำของบทความตรรกะดังนั้นการปัดเศษนั้นไม่เกี่ยวข้องเนื่องจากลักษณะของตัวแปรมากกว่าที่จะสามารถเพิ่มหรือลดจำนวนหลักได้แม้ในขณะที่คุณดูผลแคช)

1
หากไม่มีห้องสมุดที่คุณคิดจะโพสต์รหัสของคุณ?
Grammin

1
สิ่งนี้อาจช่วยได้ แต่นี่ไม่ใช่การซ้ำซ้อน stackoverflow.com/questions/529432
rfeak

1
@ แมทฉันสงสัยว่าคุณใช้วิธีแก้ปัญหาอะไรมาก่อน ถ้าคุณไม่รังเกียจคุณจะโพสต์มันเป็นคำตอบเช่นกัน
jzd

1
ความคิดที่อยู่เบื้องหลังNo rounding is necessaryสิ่งนี้ดูเหมือนจะไร้สาระสำหรับฉัน มันเป็นเรื่องที่ซับซ้อนไหม มันจะไม่ดีกว่าที่จะใช้ถ้อยคำใหม่นี้เพื่อRounding is not necessary, but welcome?
Wolf

1
ในกรณีที่คุณไม่ได้สังเกตเห็นตัวเลขที่แสดงด้วย k และ m ต่อท้ายเป็นมาตรวัดแบบอะนาล็อกมากกว่าที่บ่งบอกถึงการประมาณไม่แม่นยำบทความตรรกะ ดังนั้นการปัดเศษนั้นไม่เกี่ยวข้องส่วนใหญ่เนื่องจากลักษณะของตัวแปรมากกว่าสามารถเพิ่มหรือ decrees ตัวเลขหลายแม้ในขณะที่คุณกำลังดูผลการ cashed
Mat B.

คำตอบ:


155

นี่เป็นวิธีแก้ปัญหาที่ใช้งานได้นานและฉันคิดว่าอ่านได้ง่าย (ตรรกะหลักนั้นทำในสามบรรทัดล่างของformatวิธี)

มันใช้ประโยชน์จากTreeMapการค้นหาคำต่อท้ายที่เหมาะสม มันน่าแปลกใจที่มีประสิทธิภาพมากกว่าโซลูชันก่อนหน้านี้ที่ฉันเขียนซึ่งใช้อาร์เรย์และอ่านยากขึ้น

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

รหัสทดสอบ

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
ทางออกที่ดี ดูเหมือนว่าคุณสามารถเพิ่มคำต่อท้ายเพิ่มเติมสำหรับจำนวนที่มากจริง ๆ เหล่านั้น (สี่แสนล้านล้านล้าน ฯลฯ ) และผลลัพธ์ยังคงขยาย
Cypher

รหัสของคุณไม่ได้ค่อนข้างถูกต้องกับตัวเลขติดลบ: -5821ควรจะจัดรูปแบบเป็นไม่เป็น-5k -5.8k
std.denis

1
@ std.denis OP ไม่ได้ระบุวิธีจัดรูปแบบจำนวนลบ ฉันตัดสินใจที่จะจัดรูปแบบพวกเขาเช่นตัวเลขบวก แต่นำหน้าด้วย-เพื่อให้ตัวเลขที่สำคัญจำนวนเดียวกัน มีตัวเลือกอื่น ๆ ...
assylias

1
ครั้งแรก: ฉันลบความคิดเห็นที่ไม่ดีเพราะเห็นได้ชัดว่าไม่ใช่ความผิดของคุณ ข้อที่สอง: ไม่ใช่ปัญหาที่คำตอบที่ดีไม่ได้รับความสนใจเพียงพอตราบใดที่พวกเขาได้รับมากกว่าคนอื่น ๆ แต่เพราะคุณมักจะต้องขุดหาคำตอบที่ดีและเพียงแค่คำตอบที่ไม่ดี ไม่ดีที่จะเรียนรู้สิ่งใหม่) และสำหรับคนที่ออกเงินรางวัลเมื่อมีคำตอบมากมายอยู่ที่นั่นฉันก็คาดว่าจะระบุให้ชัดเจนยิ่งขึ้นว่าอะไรที่ขาดหายไปจากนั้นเลือกคำตอบที่เหมาะกับเกณฑ์ที่ดีที่สุด ...
maraca

1
แต่โลกทั้งโลกเข้าใจมาตรฐานนี้หรือไม่? ระวังถ้าคุณสร้างแอพสำหรับทุกคนในโลก สำหรับภาษาอังกฤษมัน10Mแต่สำหรับภาษารัสเซียมันคือ10 млнและอื่น ๆ
user924

101

ฉันรู้ว่ามันดูเหมือนโปรแกรม C แต่มันเบามาก!

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

}

มันออก:

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

16
รหัสที่สับสน ทุกวันนี้เราไม่ต้องใช้รหัสนี้ อาจทำงานได้ตามที่คาดหวัง แต่ฉันขอแนะนำให้ผู้เขียนดูที่Roger C. Martin: Clean Code
Andreas Dolk

30
obfuscated? ฉันขอให้อภัยคุณ แต่คุณอาจอ่านหนังสือเล่มหนึ่งและคิดว่าคุณสามารถเขียนรหัสได้ทุกวันนี้ บอก Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) เกี่ยวกับเรื่องนั้น ฉันกล้ารหัสใด ๆ ที่คุณสามารถเขียนได้ทุกที่ใกล้กับความเร็วของวิธีการของฉัน!
Elijah Saounkine

11
การเปลี่ยนตัวแปร d, c, n เป็นสิ่งที่อ่านได้ง่ายขึ้น (ทำความเข้าใจได้เร็วขึ้น) ทำให้โค้ดนี้เหมาะสมในมุมมองของฉัน
Gennadiy Ryabkin

5
ทำไมความลุ่มหลงกับประสิทธิภาพ ทำไมทุกคนต้องการดำเนินการแปลงเหล่านี้จำนวนมากพอที่จะรับประกันแม้กระทั่งความคิดเกี่ยวกับประสิทธิภาพ ... ความสามารถในการอ่านก่อนปรับเปลี่ยนประสิทธิภาพเฉพาะเมื่อจำเป็น
Amos M. Carpenter

10
ฉันต้องเห็นด้วยกับ @ AmosM.Carpenter ฉันรู้เรื่องการบำรุงรักษาโค้ดน้อยเมื่อฉันเขียนคำตอบนี้เมื่อ 4 ปีก่อน มันไม่ได้เลวร้ายที่จะเพิ่มประสิทธิภาพโดยทั่วไปการพูด แต่การอ่านมาก่อน อย่างไรก็ตามมันก็ไม่ได้มีประสิทธิภาพที่ดีนัก: ไม่ช้ากว่า maraca หนึ่งเท่าที่เขียน 5 เท่า - มันเกี่ยวกับสิ่งเดียวกัน (ฉันได้วางวิธีแก้ปัญหาสำหรับการวัดประสิทธิภาพไว้ที่นี่แล้วgithub.com/esaounkine/number-format- มาตรฐาน )
Elijah Saounkine

43

นี่เป็นวิธีแก้ปัญหาที่ใช้ประโยชน์จากสัญลักษณ์ทางวิศวกรรมของ 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;
}

เอาท์พุท:

7 = 7
12 = 12
856 = 856
1000 = 1k
5821 = 5.8k
10500 = 10k
101800 = 102k
2000000 = 2m
7800000 = 7.8m
92150000 = 92m
123200000 = 123m
9999999 = 10m

@ ปรับปรุงล่าสุดเพื่อรองรับความต้องการใหม่ ๆ
jzd

มีวิธีง่าย ๆ ในการรวมสิ่งนี้กับ Currency Instance เพื่อให้ได้ฟังก์ชันการทำงานที่คล้ายคลึงกับสกุลเงินหรือไม่?
xdumaine

@roviuser ไม่แน่ใจว่าคุณหมายถึงอะไร แต่ดูเหมือนคำถามแยกต่างหาก
jzd

7
ปัดเศษ 160000 ถึง 200k และปัดเศษ 120000 ลงเหลือ 100k
k1komans

4
นี่มันพังแล้วฉันป้อนหมายเลข 10000000000000.0 และมันบอกว่า 103
Oliver Dixon

23

ต้องการการปรับปรุงบางอย่าง แต่: เข้มงวดในการช่วยเหลือ!
คุณสามารถใส่คำต่อท้ายใน String หรือ array และ fetch'em ตามกำลังงานหรืออะไรทำนองนั้น
แผนกยังสามารถจัดการกับพลังฉันคิดว่าเกือบทุกอย่างเกี่ยวกับค่าพลังงาน หวังว่ามันจะช่วย!

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

เอาท์พุท:

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


2
การปรับปรุงการอ่านง่ายขึ้นเพียงแค่ต้องการเพิ่มคำสั่ง return จาก jzd เพื่อแก้ปัญหาถ่าน 4 ตัว และอย่าลืมเพิ่มคำต่อท้ายหากข้ามไปเพื่อหลีกเลี่ยงการยกเว้น AIOOB ;)
jhurtado

รหัสนี้มีความอ่อนไหวต่อโลแคลตัวอย่างเช่นใน sv_SE locale 1000 แปลงเป็น10x10³ซึ่ง regexp ไม่ตรงกันอย่างถูกต้อง
Joakim Lundborg

2
โยนข้อยกเว้นสำหรับ 0 ไม่ทำงานสำหรับจำนวนลบไม่ได้รอบ 9,999,999 อย่างถูกต้อง (พิมพ์ 10m) ...
assylias

16

ปัญหากับคำตอบปัจจุบัน

  • หลายคนแก้ปัญหาในปัจจุบันมีการใช้คำนำหน้าเหล่านี้ k = 10 3เมตร = 10 6 , B = 10 9 , t = 10 12 อย่างไรก็ตามตามแหล่งข้อมูลต่าง ๆ คำนำหน้าที่ถูกต้องคือ k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12
  • ขาดการสนับสนุนสำหรับจำนวนลบ (หรืออย่างน้อยขาดการทดสอบแสดงให้เห็นว่าสนับสนุนตัวเลขติดลบ)
  • ขาดการสนับสนุนสำหรับการดำเนินการผกผันเช่นการแปลง 1.1k เป็น 1100 (แม้ว่าจะอยู่นอกขอบเขตของคำถามเดิม)

โซลูชัน Java

โซลูชันนี้ (ส่วนขยายของคำตอบนี้ ) แก้ไขปัญหาข้างต้น

import org.apache.commons.lang.math.NumberUtils;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.regex.Pattern;


/**
 * Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
 * For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
 */
class RoundedMetricPrefixFormat extends Format {

    private static final String[] METRIC_PREFIXES = new String[]{"", "k", "M", "G", "T"};

    /**
     * The maximum number of characters in the output, excluding the negative sign
     */
    private static final Integer MAX_LENGTH = 4;

    private static final Pattern TRAILING_DECIMAL_POINT = Pattern.compile("[0-9]+\\.[kMGT]");

    private static final Pattern METRIC_PREFIXED_NUMBER = Pattern.compile("\\-?[0-9]+(\\.[0-9])?[kMGT]");

    @Override
    public StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {

        Double number = Double.valueOf(obj.toString());

        // if the number is negative, convert it to a positive number and add the minus sign to the output at the end
        boolean isNegative = number < 0;
        number = Math.abs(number);

        String result = new DecimalFormat("##0E0").format(number);

        Integer index = Character.getNumericValue(result.charAt(result.length() - 1)) / 3;
        result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index]);

        while (result.length() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
            int length = result.length();
            result = result.substring(0, length - 2) + result.substring(length - 1);
        }

        return output.append(isNegative ? "-" + result : result);
    }

    /**
     * Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
     * the original number because <tt>format()</tt> is a lossy operation, e.g.
     *
     * <pre>
     * {@code
     * def formatter = new RoundedMetricPrefixFormat()
     * Long number = 5821L
     * String formattedNumber = formatter.format(number)
     * assert formattedNumber == '5.8k'
     *
     * Long parsedNumber = formatter.parseObject(formattedNumber)
     * assert parsedNumber == 5800
     * assert parsedNumber != number
     * }
     * </pre>
     *
     * @param source a number that may have a metric prefix
     * @param pos if parsing succeeds, this should be updated to the index after the last parsed character
     * @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
     */
    @Override
    public Object parseObject(String source, ParsePosition pos) {

        if (NumberUtils.isNumber(source)) {

            // if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
            pos.setIndex(source.length());
            return toNumber(source);

        } else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {

            boolean isNegative = source.charAt(0) == '-';
            int length = source.length();

            String number = isNegative ? source.substring(1, length - 1) : source.substring(0, length - 1);
            String metricPrefix = Character.toString(source.charAt(length - 1));

            Number absoluteNumber = toNumber(number);

            int index = 0;

            for (; index < METRIC_PREFIXES.length; index++) {
                if (METRIC_PREFIXES[index].equals(metricPrefix)) {
                    break;
                }
            }

            Integer exponent = 3 * index;
            Double factor = Math.pow(10, exponent);
            factor *= isNegative ? -1 : 1;

            pos.setIndex(source.length());
            Float result = absoluteNumber.floatValue() * factor.longValue();
            return result.longValue();
        }

        return null;
    }

    private static Number toNumber(String number) {
        return NumberUtils.createNumber(number);
    }
}

โซลูชั่น Groovy

แต่เดิมวิธีการแก้ปัญหาถูกเขียนใน Groovy ที่แสดงด้านล่าง

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

การทดสอบ (Groovy)

การทดสอบถูกเขียนขึ้นใน Groovy แต่สามารถใช้เพื่อตรวจสอบคลาส Java หรือ Groovy (เพราะทั้งคู่มีชื่อและ 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
ฉันคิดว่าคุณกำลังคิดเกี่ยวกับคำนำหน้า CS เนื่องจากเขากำลังพูดถึงพันล้านและล้านล้านฉันคิดว่าเขาต้องการตัวเลขขนาดสั้น
jhurtado

1
ฉันควรจะพิมพ์ 9999999 เป็น 9.9m ฉันเชื่อว่า (ตัวเลขถูกตัดทอนไม่ปัดเศษ)
assylias

โซลูชันนี้ไม่รองรับส่วนนำหน้าสำหรับค่าที่น้อยกว่า 1 เช่น u (micro) และ m (หนึ่งในพัน)
gbmhunter

13

lib ห้องไอซียูมีการจัดรูปแบบตามกฎสำหรับตัวเลขซึ่งสามารถนำมาใช้สำหรับจำนวน spellout ฯลฯ ผมคิดว่าการใช้ไอซียูจะให้คุณแก้ปัญหาอ่านและ maintanable

[ใช้]

คลาสที่เหมาะสมคือ RuleBasedNumberFormat รูปแบบสามารถจัดเก็บเป็นไฟล์แยกต่างหาก (หรือเป็นค่าคงที่สตริง, IIRC)

ตัวอย่างจาก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);

หน้าเดียวกันแสดงตัวเลขโรมันดังนั้นฉันเดาว่ากรณีของคุณควรเป็นไปได้เช่นกัน


ทางออกเดียวในเธรดที่ไม่แตกสลายอย่างสมบูรณ์หากคุณต้องการการแปล
Grozz

2
หากคุณต้องการสำหรับการพัฒนา Android สิ่งนี้จะรวมอยู่ในเฟรมเวิร์กแล้ว CompactDecimalFormatมองหา API Level 24+
Gokhan Arik

10

ด้วยJava-12 +คุณสามารถใช้NumberFormat.getCompactNumberInstanceเพื่อจัดรูปแบบตัวเลข คุณสามารถสร้างรายNumberFormatแรกเป็น

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

แล้วใช้มันเพื่อformat:

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

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

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

8

สำคัญ:คำตอบที่ส่งไปdoubleจะล้มเหลวสำหรับตัวเลขที่ต้องการ99999999999999999Lและส่งคืน100Pแทน99Pเนื่องจากdoubleใช้IEEEมาตรฐาน :

หากสตริงทศนิยมที่มีตัวเลขนัยสำคัญไม่เกิน 15 หลักจะถูกแปลงเป็นการแทนความแม่นยำสองเท่าของ IEEE 754 จากนั้นแปลงกลับเป็นสตริงที่มีตัวเลขนัยสำคัญเท่ากันดังนั้นสตริงสุดท้ายควรตรงกับต้นฉบับ [ longมีตัวเลขนัยสำคัญสูงสุด 19 หลัก ]

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

การแก้ปัญหานี้ตัดตัวเลขที่ไม่พึงประสงค์และผลงานสำหรับทุกlongค่า ใช้งานง่าย แต่มีประสิทธิภาพ (เปรียบเทียบด้านล่าง) -120k ไม่สามารถแสดงได้ด้วย 4 ตัวอักษรแม้ -0.1M ยาวเกินไปนั่นคือสาเหตุที่ตัวเลขติดลบ 5 ตัวอักษรต้องไม่เป็นไร:

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

การทดสอบในelse ifที่จุดเริ่มต้นคือ necessairy เพราะนาทีเป็น-(2^63)และสูงสุดคือ(2^63)-1และดังนั้นจึงได้รับมอบหมายจะล้มเหลวหากnumber = -number number == Long.MIN_VALUEหากเราต้องทำการตรวจสอบเราก็สามารถใส่ตัวเลขให้มากที่สุดเท่าที่จะทำได้แทนการตรวจสอบnumber == Long.MIN_VALUEถ้าเราต้องทำการตรวจสอบแล้วเราได้เป็นอย่างดีสามารถรวมเป็นตัวเลขที่มากที่สุดเท่าที่เป็นไปได้แทนเพียงการตรวจสอบ

การเปรียบเทียบการติดตั้งใช้งานกับผู้ที่ได้รับการอัปโหลดมากที่สุด (กล่าวว่าเร็วที่สุดในปัจจุบัน) แสดงให้เห็นว่าเร็วกว่า 5 เท่า (ขึ้นอยู่กับการตั้งค่าการทดสอบ แต่ด้วยจำนวนที่เพิ่มขึ้น ทำการตรวจสอบเพิ่มเติมเพราะจัดการทุกกรณีดังนั้นหากอีกอันหนึ่งได้รับการแก้ไขความแตกต่างจะยิ่งใหญ่กว่า) มันเร็วมากเพราะไม่มีการดำเนินการจุดลอยตัว, ไม่มีลอการิทึม, ไม่มีพลังงาน, ไม่มีการเรียกซ้ำ, ไม่มี regex, ไม่มีตัวจัดรูปแบบที่ซับซ้อนและการลดจำนวนของวัตถุที่สร้างขึ้นให้น้อยที่สุด


นี่คือโปรแกรมทดสอบ:

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

}

เอาต์พุตที่เป็นไปได้: 2309 vs. 11591(ประมาณเดียวกันเมื่อใช้ตัวเลขบวกเท่านั้นและรุนแรงมากขึ้นเมื่อย้อนกลับลำดับการดำเนินการบางทีอาจเป็นเรื่องเกี่ยวกับการรวบรวมขยะ)


8

นี่คือการใช้งานสั้น ๆ โดยไม่ต้องเรียกซ้ำและวนซ้ำเพียงเล็กน้อย ใช้งานไม่ได้กับจำนวนลบ แต่รองรับค่าบวกทั้งหมดlongจนถึง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);
}

ขาออก:

1k
5.8k
10k
101k
2m
7.8m
92m
123m
9.2e (นี่คือLong.MAX_VALUE)

ฉันยังทำการเปรียบเทียบง่าย ๆ (จัดรูปแบบความยาว 10 ล้านสุ่ม) และมันเร็วกว่าการนำเอลียาห์ไปใช้และเร็วกว่าการใช้ assylias เล็กน้อย

ของฉัน: 1137.028 ms
ของ Elijah: 2664.396 ms
assylias ': 1373.473 ms


1
ในการอัปเดตล่าสุดของคุณคุณเพิ่มข้อผิดพลาด ตอนนี้มันกลับ1kหมายเลข101800
Sufian

2
ขอขอบคุณที่สังเกตเห็นมันได้รับการแก้ไขแล้ว
Raniz

8

สำหรับใครที่ต้องการปัดเศษ นี่เป็นโซลูชันที่ยอดเยี่ยมและง่ายต่อการอ่านซึ่งใช้ประโยชน์จากไลบรารี 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

รหัสต่อไปนี้แสดงวิธีที่คุณสามารถทำได้โดยคำนึงถึงการขยายตัวที่ง่าย

"เวทมนต์" ส่วนใหญ่อยู่ใน makeDecimalฟังก์ชั่นซึ่งสำหรับค่าที่ถูกต้องที่ผ่านเข้าไปรับประกันว่าคุณจะไม่มีตัวอักษรเกินสี่ตัวในเอาต์พุต

มันแยกส่วนทั้งหมดและส่วนที่สิบสำหรับตัวหารที่กำหนดเช่น12,345,678ด้วยตัวหารของ1,000,000จะให้wholeค่า12และtenthsค่าของ3ค่าของ

จากนั้นก็สามารถตัดสินใจได้ว่าจะส่งออกเพียงส่วนทั้งหมดหรือทั้งส่วนและส่วนที่สิบโดยใช้กฎ:

  • ถ้าส่วนที่สิบเป็นศูนย์ให้ส่งออกทั้งส่วนและคำต่อท้าย
  • ถ้าส่วนทั้งหมดมากกว่าเก้าให้เอาท์พุททั้งส่วนและคำต่อท้าย
  • มิฉะนั้นเอาต์พุตทั้งส่วนส่วนที่สิบและส่วนต่อท้าย

รหัสสำหรับสิ่งต่อไปนี้:

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

จากนั้นเป็นเรื่องง่าย ๆ ในการเรียกใช้ฟังก์ชันผู้ช่วยที่มีค่าที่ถูกต้องรวมถึงค่าคงที่บางอย่างเพื่อให้ชีวิตง่ายขึ้นสำหรับผู้พัฒนา:

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

ความจริงที่ว่าmakeDecimalฟังก์ชั่นทำงานเสียงฮึดฮัดหมายความว่าการขยายเกิน999,999,999นั้นเป็นเพียงเรื่องของการเพิ่มบรรทัดพิเศษXlatให้ง่ายที่ฉันได้ทำเพื่อคุณ

รอบชิงชนะเลิศreturnในXlatไม่จำเป็นต้องมีเงื่อนไขตั้งแต่ค่าที่มากที่สุดที่คุณสามารถถือใน 64 บิตลงนามยาวเพียงประมาณ 9.2 Quintillion

แต่ถ้าตามความต้องการที่แปลกประหลาดบางอย่าง Oracle ตัดสินใจที่จะเพิ่มประเภท 128 บิตlongerหรือ 1024 บิตdamn_longคุณจะพร้อม :-)


และในที่สุดชุดทดสอบเล็ก ๆ ที่คุณสามารถใช้สำหรับตรวจสอบการทำงาน

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

คุณสามารถดูได้จากผลลัพธ์ที่ให้สิ่งที่คุณต้องการ:

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

และโปรดทราบว่าการส่งผ่านจำนวนลบไปยังฟังก์ชันนี้จะส่งผลให้สตริงยาวเกินไปสำหรับความต้องการของคุณเนื่องจากเป็นไปตาม< THOUเส้นทาง) ฉันคิดว่าไม่เป็นไรเพราะคุณพูดถึงค่าที่ไม่เป็นลบในคำถามเท่านั้น


6

ฉันไม่รู้ว่ามันเป็นวิธีที่ดีที่สุด แต่นี่คือสิ่งที่ฉันทำ

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

--- รหัส ---

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

ฟังก์ชั่นของฉันสำหรับแปลงจำนวนมากเป็นจำนวนน้อย (ด้วย 2 หลัก) คุณสามารถเปลี่ยนจำนวนตัวเลขจากการเปลี่ยนแปลง#.##ในDecimalFormat

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

การทดสอบ

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

หวังว่ามันจะช่วย


5

Java ของฉันเป็นสนิม แต่นี่คือวิธีที่ฉันจะใช้ใน 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");
    }

การปรับค่านี้เพื่อใช้ CS กิโลกรัม (1,024) ง่ายกว่าการวัดเป็นกิโลกรัมหรือเพื่อเพิ่มหน่วยเพิ่มเติม มันจัดรูปแบบ 1,000 เป็น "1.0 k" แทนที่จะเป็น "1 k" แต่ฉันเชื่อว่ามันไม่มีสาระ

เพื่อให้ตรงกับข้อกำหนดที่เฉพาะเจาะจงมากขึ้น "ไม่เกินสี่อักขระ" ให้ลบช่องว่างก่อนส่วนต่อท้ายและปรับบล็อกกลางดังนี้:

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

1
น่าเสียดายที่ToStringวิธีการนี้ไม่มีอยู่ใน Java - คุณต้องมี NumberFormat ซึ่งอาจสร้างปัญหาอื่น ๆ (ที่มีความสำคัญต่อภาษา ฯลฯ )
assylias

5

ของโปรด. คุณสามารถใช้ "k" และอื่น ๆ เป็นตัวบ่งชี้ทศนิยมเช่นเดียวกับในโดเมนอิเล็กทรอนิกส์ นี่จะให้ตัวเลขพิเศษโดยไม่มีพื้นที่เพิ่มเติม

คอลัมน์ที่สองพยายามใช้ตัวเลขให้มากที่สุด

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

นี่คือรหัส

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

คอยติดตามความคิดเห็นของฉันว่าฉันให้ความสำคัญกับความสามารถในการอ่านเหนือประสิทธิภาพนี่เป็นเวอร์ชันที่ควรมีความชัดเจนว่าเกิดอะไรขึ้น (สมมติว่าคุณเคยใช้ BigDecimalก่อน) โดยไม่แสดงความคิดเห็นมากเกินไป (ฉันเชื่อมั่นใน (เนื่องจากฉันไม่สามารถนึกภาพสถานการณ์ที่คุณต้องการทำสิ่งนี้ได้หลายล้านครั้งแล้วที่การแสดงนั้นได้รับการพิจารณา)

รุ่นนี้:

  • ใช้BigDecimals เพื่อความแม่นยำและเพื่อหลีกเลี่ยงปัญหาการปัดเศษ
  • ทำงานสำหรับการปัดเศษลงตามที่ร้องขอโดย OP
  • ใช้ได้กับโหมดการปัดเศษอื่น ๆ เช่นHALF_UPในการทดสอบ
  • ช่วยให้คุณปรับความแม่นยำ (เปลี่ยนREQUIRED_PRECISION)
  • ใช้enumเพื่อกำหนดเกณฑ์เช่นสามารถปรับได้อย่างง่ายดายเพื่อใช้ KB / MB / GB / TB แทนที่จะเป็น k / m / b / t ฯลฯ และแน่นอนสามารถขยายได้เกินTRILLIONหากจำเป็น
  • มาพร้อมกับการทดสอบหน่วยอย่างละเอียดเนื่องจากกรณีทดสอบในคำถามไม่ได้ทำการทดสอบชายแดน
  • ควรใช้กับจำนวนศูนย์และจำนวนลบ

Threshold.java :

import java.math.BigDecimal;

public enum Threshold {
  TRILLION("1000000000000", 12, 't', null),
  BILLION("1000000000", 9, 'b', TRILLION),
  MILLION("1000000", 6, 'm', BILLION),
  THOUSAND("1000", 3, 'k', MILLION),
  ZERO("0", 0, null, THOUSAND);

  private BigDecimal value;
  private int zeroes;
  protected Character suffix;
  private Threshold higherThreshold;

  private Threshold(String aValueString, int aNumberOfZeroes, Character aSuffix,
      Threshold aThreshold) {
    value = new BigDecimal(aValueString);
    zeroes = aNumberOfZeroes;
    suffix = aSuffix;
    higherThreshold = aThreshold;
  }

  public static Threshold thresholdFor(long aValue) {
    return thresholdFor(new BigDecimal(aValue));
  }

  public static Threshold thresholdFor(BigDecimal aValue) {
    for (Threshold eachThreshold : Threshold.values()) {
      if (eachThreshold.value.compareTo(aValue) <= 0) {
        return eachThreshold;
      }
    }
    return TRILLION; // shouldn't be needed, but you might have to extend the enum
  }

  public int getNumberOfZeroes() {
    return zeroes;
  }

  public String getSuffix() {
    return suffix == null ? "" : "" + suffix;
  }

  public Threshold getHigherThreshold() {
    return higherThreshold;
  }
}

NumberShortener.java :

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

public class NumberShortener {

  public static final int REQUIRED_PRECISION = 2;

  public static BigDecimal toPrecisionWithoutLoss(BigDecimal aBigDecimal,
      int aPrecision, RoundingMode aMode) {
    int previousScale = aBigDecimal.scale();
    int previousPrecision = aBigDecimal.precision();
    int newPrecision = Math.max(previousPrecision - previousScale, aPrecision);
    return aBigDecimal.setScale(previousScale + newPrecision - previousPrecision,
        aMode);
  }

  private static BigDecimal scaledNumber(BigDecimal aNumber, RoundingMode aMode) {
    Threshold threshold = Threshold.thresholdFor(aNumber);
    BigDecimal adjustedNumber = aNumber.movePointLeft(threshold.getNumberOfZeroes());
    BigDecimal scaledNumber = toPrecisionWithoutLoss(adjustedNumber, REQUIRED_PRECISION,
        aMode).stripTrailingZeros();
    // System.out.println("Number: <" + aNumber + ">, adjusted: <" + adjustedNumber
    // + ">, rounded: <" + scaledNumber + ">");
    return scaledNumber;
  }

  public static String shortenedNumber(long aNumber, RoundingMode aMode) {
    boolean isNegative = aNumber < 0;
    BigDecimal numberAsBigDecimal = new BigDecimal(isNegative ? -aNumber : aNumber);
    Threshold threshold = Threshold.thresholdFor(numberAsBigDecimal);
    BigDecimal scaledNumber = aNumber == 0 ? numberAsBigDecimal : scaledNumber(
        numberAsBigDecimal, aMode);
    if (scaledNumber.compareTo(new BigDecimal("1000")) >= 0) {
      scaledNumber = scaledNumber(scaledNumber, aMode);
      threshold = threshold.getHigherThreshold();
    }
    String sign = isNegative ? "-" : "";
    String printNumber = sign + scaledNumber.stripTrailingZeros().toPlainString()
        + threshold.getSuffix();
    // System.out.println("Number: <" + sign + numberAsBigDecimal + ">, rounded: <"
    // + sign + scaledNumber + ">, print: <" + printNumber + ">");
    return printNumber;
  }
}

(ยกเลิกการใส่ข้อคิดเห็นprintlnงบหรือเปลี่ยนแปลงเพื่อใช้ logger ที่คุณชื่นชอบเพื่อดูว่ามันทำอะไร)

และสุดท้ายการทดสอบใน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));
    }
  }
}

อย่าลังเลที่จะชี้ให้เห็นในความคิดเห็นหากฉันพลาดกรณีทดสอบที่สำคัญหรือควรปรับค่าที่คาดหวัง


ข้อเสียที่ชัดเจนเพียงอย่างเดียวในโซลูชันของคุณคือ V + H-scrollbars สำหรับโค้ดของคุณซึ่งจะช่วยลดความสามารถในการอ่าน คุณคิดว่าการจัดรูปแบบจะเป็นไปได้โดยไม่สูญเสียความชัดเจน?
Wolf

@ Wolf: หวังว่าจะได้ออกไปพร้อมกับคัดลอก / วางจาก IDE ของฉัน แต่คุณพูดถูกมันเป็นหน้าซื่อใจคดของฉันที่จะเรียกร้องการอ่านและต้องเลื่อนแนวนอนดังนั้นขอบคุณที่ชี้ให้เห็น ;-) ฉันได้อัปเดตโค้ดสองบิตแรกเนื่องจากเป็นบิตที่คุณต้องการดูเพื่อดูว่าเกิดอะไรขึ้น แต่ออกจากโค้ดทดสอบเนื่องจากการดูด้วยตนเองนั้นไม่ได้มีประโยชน์อะไร - คุณ ' อาจต้องการที่จะวางลงใน IDE ของคุณเองเพื่อเรียกใช้การทดสอบหน่วยหากคุณต้องการที่จะเห็นว่าการทดสอบทำงาน หวังว่าไม่เป็นไร
Amos M. Carpenter

ก็ดี. แต่ในกล่องสุดท้ายกรณีทดสอบผลลัพธ์ที่คาดหวังอาจเป็น - เชิงทัศนภาพ - เกี่ยวข้องกับอินพุตดีกว่า (ฉันหมายถึงตัวอักษรใน 6 อาร์เรย์แรก)
Wolf

@ Wolf: ฉันไม่ได้เป็นแฟนของพยายามจัดรายการในแนวเดียวกับช่องว่างหรือแท็บ - ที่ไม่สามารถกำหนดค่าได้อย่างง่ายดายอย่างสม่ำเสมอสำหรับทุกกรณีในฟอร์แมตที่ชื่นชอบ (eclipse) และทำด้วยตนเอง ... ด้วยวิธีบ้า เนื่องจากการปรับเปลี่ยนทั้งหมดที่คุณต้องทำในแต่ละครั้งที่คุณเพิ่มหรือลบรายการ หากฉันต้องการเห็นพวกเขาอยู่ในแนวเดียวกันฉันจะวางตัวเลข / ค่าลงในสเปรดชีตเป็น CSV
Amos M. Carpenter

1
ทั้งหมดขึ้นอยู่กับสิ่งที่คุณตามมา @assylias หากคุณเพิ่งแก้ปัญหากรณีใช้ครั้งเดียวโซลูชันของคุณควรทำงานได้ดี ฉันชอบTreeMapวิธีการ "ความสามารถในการอ่าน" เป็นเรื่องส่วนตัวแน่นอน ;-) ทีนี้ถ้ามีใครต้องการปัดเศษแตกต่างจากการตัดทอนในเวอร์ชั่นของคุณล่ะ? (ตัวอย่างเช่นเมื่อใช้สิ่งนี้เพื่อระบุขนาดไฟล์ใครจะตัดทอน?) หากคุณต้องการพลังของ 2 มากกว่า 10? คุณต้องเขียนให้ถูกต้องใช่มั้ย อย่างที่ฉันบอกว่าฉันตั้งใจไม่พยายามตีกอล์ฟโค้ดของฉันซึ่งส่วนใหญ่อาจสั้นลง (ยกตัวอย่างเช่นฉันจะไม่เก็บบรรทัดต่อไปนี้ไว้)
Amos M. Carpenter

4

นี่คือรหัสของฉัน สะอาดและเรียบง่าย

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

เพิ่มคำตอบของฉันเอง, รหัส Java, รหัสอธิบายตนเอง ..

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

ข้อมูลโค้ดนี้เป็นโค้ดที่เรียบง่ายและเป็นระเบียบและสามารถใช้งานได้ทั้งหมด:

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

ลองนี้:

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

เอาท์พุท:

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
สิ่งนี้ไม่สามารถใช้ได้กับเคสขอบทั้งหมดของคุณ ลอง 999 เช่น
jzd
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.