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이 추가 된 숫자는 근사값을 나타내는 근사값을 나타내는 아날로그 게이지에 가깝습니다. 따라서 반올림은 주로 변수의 특성으로 인해 캐시 된 결과를보고있는 동안에도 여러 자리수를 늘리거나 선언 할 수있는 것보다 중요하지 않습니다.)
답변
다음은 긴 값으로 작동하고 읽을 수 있는 해결책입니다 (핵심 논리는 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);
}
}
답변
나는 이것이 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
답변
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
답변
약간의 개선이 필요하지만 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
답변
현재 답변 관련 문제
- 현재의 많은 솔루션들이 이들 접두사 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')
}
}
}
답변
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);
같은 페이지에는 로마 숫자가 표시되므로 귀하의 경우도 가능해야합니다.
답변
함께 자바-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"
