Java에서 1200에서 1.2k로 포맷하는 방법


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가 정상임을 의미합니다. 11.2k는 그렇지 않습니다. 7.8m도 동일 19.1m이 아닙니다. 소수점 앞의 한 자리 만 소수점을 사용할 수 있습니다. 소수점 앞의 두 자리 숫자는 소수점 뒤의 숫자가 아님을 의미합니다.
  • 반올림이 필요하지 않습니다. (k 및 m이 추가 된 숫자는 근사값을 나타내는 근사값을 나타내는 아날로그 게이지에 가깝습니다. 따라서 반올림은 주로 변수의 특성으로 인해 캐시 된 결과를보고있는 동안에도 여러 자리수를 늘리거나 선언 할 수있는 것보다 중요하지 않습니다.)

1
라이브러리가없는 사람이 있다면 코드를 게시 하시겠습니까?
Grammin

1
이것은 dup이 아니지만 도움이 될 수 있습니다. stackoverflow.com/questions/529432
rfeak

1
@ Mat 이전에 어떤 솔루션을 사용하고 있는지 궁금했습니다. 마음에 들지 않으면 답변으로 게시하십시오.
jzd

1
No rounding is necessary이 배후의 아이디어는 나에게 터무니없는 것 같습니다. 단지 복잡한 일입니까? 이것을 다음과 같이 바꾸는 것이 낫지 Rounding is not necessary, but welcome않습니까?
Wolf

1
k 및 m이 추가 된 숫자가 표시되지 않는 경우에는 정확한 논리 기사가 아닌 근사치를 나타내는 아날로그 게이지가 더 많습니다. 따라서 반올림은 주로 변수의 특성으로 인해 중요하지 않습니다. 현금 인출 결과를보고있는 동안에도 몇 자릿수를 늘리거나 선언 할 수 있습니다.
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
좋은 해결책. 실제로 큰 숫자 (quadrillion, quintillion 등)에 접미사를 더 추가 할 수 있으며 출력이 계속 확장됩니다.
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의

29
난독 화? 나는 당신의 용서를 간청하지만, 당신은 아마 한 권의 책을 읽고 오늘날에는 어떻게 든 다르게 코딩 할 수 있다고 생각합니다. 이에 대해 Joel ( joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) 에게 알려주십시오 . 내 방법의 속도에 가까운 곳에서 얻을 수있는 코드를 감히!
Elijah Saounkine

11
더 읽기 (빠른 이해) 뭔가 D, C, N 변수를 변경하면 내보기에 괜찮은 코드합니다
나디 Ryabkin

5
이것이 왜 성과에 대한 집착입니까? 성능에 대해 생각조차하기 위해 왜 이러한 전환을 충분히 많이 수행하고 싶습니까? 가독성 우선, 필요한 경우에만 성능 조정.
Amos M. Carpenter

10
@ AmosM.Carpenter에 동의해야합니다. 4 년 전이 답변을 썼을 때 코드 유지 관리에 대해서는 거의 알지 못했습니다. 일반적으로 말해서, 그러나 가독성을 최우선으로하는 것은 나쁘지 않습니다. 그건 그렇고, 성능 측면에서는 그리 나쁘지 않습니다. 마라 카가 쓴 것보다 5 배 느리지 않습니다-그것은 거의 같습니다 (여기서는 벤치 마크 솔루션을 여기에 github.com/esaounkine/number-format- 벤치 마크 ).
Elijah Saounkine 2016 년

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

@Mat 새로운 요구 사항을 처리하도록 업데이트
jzd

통화와 유사한 기능을 얻기 위해 이것을 통화 인스턴스와 결합하는 쉬운 방법이 있습니까?
xdumaine

@roviuser, 무슨 뜻인지 확실하지 않지만 별도의 질문처럼 들립니다.
jzd

7
라운드 200K에 160000 또한 120000 아래로 100,000에 라운드
k1komans

4
이것은 깨졌다, 나는 숫자 10000000000000.0을 입력하고 103이라고 말한다.
Oliver Dixon

23

약간의 개선이 필요하지만 StrictMath to the rescue!
문자열이나 배열에 접미사를 넣고 힘 또는 이와 유사한 것을 기반으로 가져올 수 있습니다.
나눗셈은 또한 전력을 중심으로 관리 할 수 ​​있습니다. 거의 모든 것이 전력 가치에 관한 것이라고 생각합니다. 그것이 도움이되기를 바랍니다!

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
약간의 가독성 향상, 4 문자 문제를 해결하기 위해 jzd에서 return 문을 추가해야했습니다. AIOOB 예외를 피하기 위해 t를 넘으면 접미사를 추가하십시오. ;)
jhurtado

이 코드는 로케일에 민감합니다. 예를 들어 sv_SE 로케일 1000은 10x10³로 변환되는데, 정규 표현식과 올바르게 일치하지 않습니다.
Joakim Lundborg

2
0에 대해 예외가 발생, ...하지 라운드 9,999,999 제대로 (인쇄 1,000 만)를 수행, 음수 일을하지 않습니다
assylias

16

현재 답변 관련 문제

  • 현재의 많은 솔루션들이 이들 접두사 k = 10 3 , m = 10 6 , b = 10 9 , t = 10 12를 사용하고 있습니다. 그러나 다양한 출처 에 따르면 올바른 접두사는 k = 10 3 , M = 10 6 , G = 10 9 , T = 10 12입니다.
  • 음수에 대한 지원 부족 (또는 음수가 지원됨을 나타내는 테스트 부족)
  • 역 연산에 대한 지원 부족 (예 : 1.1k를 1100으로 변환) (원래 질문의 범위를 벗어나더라도)

자바 솔루션

이 솔루션 ( 이 답변 의 확장 )은 위의 문제를 해결합니다.

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로 작성되었습니다.

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로 작성되었지만 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 2016 년

이 솔루션은 1보다 작은 값 (예 : u (micro) 및 m (milli))의 접두사를 지원하지 않습니다.
gbmhunter

13

ICU lib 디렉토리는 ICU는 당신에게 읽기 및 maintanable 솔루션을 줄 것이다 사용하여 생각 나는 번호를하면 spellout 등을 위해 사용할 수있는 번호에 대한 규칙 기반의 포맷을 가지고 있습니다.

[용법]

올바른 클래스는 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 레벨 24+
Gokhan Arik

10

함께 자바-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 자리 인 10 진수 문자열이 IEEE 754 배정 밀도 표현으로 변환 된 다음 같은 수의 유효 자릿수를 가진 문자열로 다시 변환되면 최종 문자열은 원본과 일치해야합니다. [ 최대 19 자리의 유효 숫자를long습니다 .]

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최소값 -(2^63)과 최대 값 이므로 시작 부분 의 테스트는 필요 (2^63)-1하므로 할당 number = -number이 실패합니다 number == Long.MIN_VALUE. 확인을해야하는 경우 확인 대신 가능한 많은 숫자를 포함 할 수 있습니다 number == Long.MIN_VALUE.

이 구현을 가장 많이 찬성 한 사람 (현재 가장 빠른 것으로 보임)과 비교 한 결과 5 배 이상 빠릅니다 (테스트 설정에 따라 달라 지지만 더 많은 수 일수록 이득이 커지고이 구현은 모든 사례를 처리하기 때문에 더 많은 검사를 수행하므로 다른 사례를 수정하면 차이가 훨씬 커집니다. 부동 소수점 연산, 로그, 전력, 재귀, 정규 표현식, 정교한 포맷터 및 생성되는 객체의 양이 최소화되므로 빠릅니다.


테스트 프로그램은 다음과 같습니다.

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)

또한 정말 간단한 벤치마킹 (무작위 천만 길이)과 Elijah의 구현보다 훨씬 빠르며 assylias의 구현보다 약간 빠릅니다.

광산 : 1137.028ms
엘리야 : 2664.396ms
assylias ': 1373.473ms


1
마지막 업데이트에서 버그를 추가했습니다. 이제 번호 101800에 대해 1k 를 반환합니다 .
수피

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전달 된 올바른 값에 대해 출력에 4자를 넘지 않도록 보장하는 기능에 있습니다.

먼저 주어진 제수에 대한 전체 및 열 번째 부분을 추출하므로, 예를 들어 12,345,678제수로 1,000,000whole12과의 tenths값을 제공 3합니다.

이를 통해 규칙을 사용하여 전체 부분 만 출력할지 아니면 전체 및 10/10 부분을 모두 출력할지 결정할 수 있습니다.

  • 열 번째 부분이 0이면 전체 부분과 접미사 만 출력하십시오.
  • 전체 부분이 9보다 큰 경우 전체 부분과 접미사 만 출력하십시오.
  • 그렇지 않으면 전체 부분, 십분의 일 부분 및 접미사를 출력하십시오.

그 코드는 다음과 같습니다.

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을 "1k"가 아닌 "1.0k"로 형식화하지만 그것이 중요하지 않다고 믿습니다.

보다 구체적인 요구 사항 "4 자 이하"를 충족 시키려면 접미사 앞의 공백을 제거하고 다음과 같이 중간 블록을 조정하십시오.

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

1
불행히도이 ToString방법은 Java에 존재하지 않습니다-다른 문제 (로케일 구분 등)를 생성 할 수있는 NumberFormat이 필요합니다.
assylias 2016 년

5

내가 좋아하는 것. 전자 도메인에서 공통적으로 "k"등을 10 진수 표시기로 사용할 수 있습니다. 이것은 추가 공간없이 여분의 숫자를 줄 것입니다

두 번째 열은 가능한 한 많은 자릿수를 사용하려고합니다.

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대한 걱정없이 과도한 주석 (자체 문서화 코드를 믿는다)없이 무슨 일이 일어나고 있는지 ( 이전에 사용했다고 가정) 명확한 버전이 있습니다. (수백만 번이나 수행하여 성능을 고려하는 시나리오를 그림으로 볼 수 없기 때문에).

이 버전 :

  • BigDecimal정밀도를 높이고 반올림 문제를 피하기 위해 s를 사용 합니다.
  • OP의 요청에 따라 반올림합니다.
  • HALF_UP테스트와 같이 다른 라운딩 모드에서 작동
  • 정밀도를 조정할 수 있습니다 (변경 REQUIRED_PRECISION)
  • 를 사용 enum하여 임계 값을 정의합니다. 즉, k / m / b / t 대신 KB / MB / GB / TB를 사용하도록 쉽게 조정할 수 있으며 물론 TRILLION필요한 경우를 넘어 확장 할 수도 있습니다.
  • 문제의 테스트 사례가 테두리를 테스트하지 않았기 때문에 철저한 단위 테스트가 제공됩니다.
  • 0과 음수로 작동해야합니다.

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하거나 좋아하는 로거를 사용하여 수행중인 작업을 확인하도록 변경하십시오.

마지막으로 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 스크롤 막대처럼 보이므로 가독성이 떨어집니다. 명확성을 잃지 않으면 서 다시 포맷 할 수 있다고 생각하십니까?
Wolf

@ Wolf : IDE에서 복사 / 붙여 넣기를 피하고 싶었지만 가독성을 주장하고 가로 스크롤이 필요하다는 것은 위선적이므로 지적 해 주셔서 감사합니다. ;-) 코드의 처음 두 비트를 업데이트했습니다.이 비트 코드는 현재 상황을 확인하기 위해보고 있지만 테스트 코드 자체는 그다지 도움이되지 않기 때문에 남겨 두었습니다. d 테스트가 작동하는지 확인하려면 유닛 테스트를 실행하기 위해 IDE에 붙여 넣기를 원할 것입니다. 잘 됐으면 좋겠다.
Amos M. Carpenter

오 굿. 그러나 마지막 상자 인 테스트 사례에서 예상 결과는 광학적으로 입력과 더 잘 관련 될 수 있습니다 (처음 6 배열의 리터럴을 의미합니다).
Wolf

@ 늑대 : 나는 공백이나 탭으로 줄에 항목을 맞추려고 애쓰는 팬이 아닙니다. 좋아하는 포맷터 (일식)의 모든 경우에 대해 일관되게 쉽게 구성 할 수는 없으며 수동으로 수행하는 것은 광기입니다. , 항목을 추가하거나 제거 할 때마다 조정해야하는 모든 조정으로 인해. 실제로 정렬 된 것을보고 싶다면 숫자 / 값을 스프레드 시트에 CSV로 붙여 넣으십시오.
Amos M. Carpenter

1
@assylias는 모두 당신이 무엇을하는지에 달려 있습니다. 일회용 사용 사례를 해결 한 직후에 솔루션이 제대로 작동합니다. 나는 TreeMap접근 방식을 좋아한다 . "가독성"은 물론 주관적입니다. ;-) 이제 누군가 버전에서 잘리는 것과 다르게 반올림하려면 어떻게해야합니까? (예를 들어, 파일 크기를 나타 내기 위해 이것을 사용할 때 누가 자르기를 원합니까?) 10이 아닌 2의 제곱을 원한다면? 공정한 비트를 다시 작성해야합니까? 내가 말했듯이, 나는 의도적으로 내 코드를 골프화하려고 시도하지 않았으며, 그 중 많은 부분이 단축 될 수있었습니다 (예를 들어 if-then을 한 줄에 두지 않을 것입니다).
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.