如何去在java中格式化1200到1.2K

我想用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 

右边的数字是长整数,左边的数字是string。 我应该如何解决这个问题。 我已经做了这个小algorithm,但我认为可能已经发明了一些更好的工作,并不需要额外的testing,如果我开始处理数十亿和万亿:)

其他要求:

  • 格式应该有最多4个字符
  • 上面的意思是1.1k就行了11.2k不行。 7.8米就可以了19.1米不是。 小数点前一位只允许有小数点。 小数点前两位表示小数点后的数字。
  • 四舍五入是必要的。 (附加k和m后显示的数字更多的是模拟量表,表示近似不精确的逻辑项,因此舍入无关紧要,主要是由于variables的性质,即使在查看caching的结果时,也可能增加或减less几位数字。

这是一个解决scheme,适用于任何长期价值 ,我发现很可读(核心逻辑是在format方法的底部三行)。

它利用TreeMap来查找适当的后缀。 这比我以前写的使用数组的解决scheme更有效,而且更难以阅读。

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

testing代码

 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, ie '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的工程符号的解决scheme:

 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]+\\.[az]")){ 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来拯救!
你可以把后缀放在一个string或数组中,然后基于权力,或类似的东西。
这个师也可以围绕权力来pipe理,我想几乎所有的力量都是关于权力的。 希望它有帮助!

 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.1米
11B
712B
34吨

当前答案的问题

  • 目前许多解决scheme都使用这些前缀k = 10 3 ,m = 10 6 ,b = 10 9 ,t = 10 12 。 然而,根据各种 来源 ,正确的前缀是k = 10 3 ,M = 10 6 ,G = 10 9 ,T = 10 12
  • 缺乏对负数的支持(或者至less缺乏certificate支持负数的testing)
  • 缺乏逆向操作的支持,例如将1.1k转换为1100(尽pipe这不在原始问题的范围之内)

Java解决scheme

这个解决scheme( 这个答案的延伸)解决了上述问题。

 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, eg * * <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解决scheme

该解决scheme最初是用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, eg * * <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) } } 

testing(Groovy)

testing用Groovy编写,但可以用来validationJava或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会给你一个可读和可保存的解决scheme。

[用法]

正确的类是RuleBasedNumberFormat。 格式本身可以作为单独的文件存储(或作为string常量,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); 

同一页显示罗马数字,所以我想你的情况也应该是可能的。

重要提示:如果数字为99999999999999999L ,则99Pdouble答案将失败,并返回100P而不是99P因为double使用IEEE标准 :

如果最多15个有效数字的十进制string被转换为IEEE 754双精度表示,然后再转换回有效数字相同的string,则最终的string应该与原始string匹配。 [ 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 

这个解决scheme切断不需要的数字,并为所有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]; } } 

因为最小值是-(2^63) ,最大值是(2^63)-1 ,所以在else if的testing是必需的,因此如果number == Long.MIN_VALUE则赋值number = -number number == Long.MIN_VALUE将失败。 如果我们必须做一个检查,那么我们可以包含尽可能多的数字,而不是只检查number == Long.MIN_VALUE

这个实现与获得最多赞誉的人(据说是目前最快的)的比较表明它的速度提高了5倍以上 (这取决于testing设置,但是数量越多,获得的收益就越大,做更多的检查,因为它处理所有的情况下,所以如果另一个将被固定的差异会变得更大)。 这是因为没有浮点操作,没有对数,没有权力,没有recursion,没有正则expression式,没有复杂的格式化器和创build的对象数量的最小化。


这里是testing程序:

 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 (当只使用正数时大致相同,在颠倒执行顺序时更为极端,可能与垃圾收集有关)

我的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),而不是公制公斤,或添加更多的单位。 它将1000的格式设置为“1.0 k”而不是“1 k”,但我相信这并不重要。

为了满足更具体的要求“不超过四个字符”,删除后缀之前的空格,并像这样调整中间块:

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

我不知道这是否是最好的方法,但是,这是我做的。

 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 } 

我的最爱。 您也可以使用“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?"; } } 

这是一个没有recursion的简短实现,只是一个非常小的循环。 不支持负数,但支持所有正数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
2米
7.8米
92米
123米
9.2e(这是Long.MAX_VALUE

我也做了一些非常简单的基准testing(格式化了一千万个随机的长度),比起Elijah的实现要快得多,而且比assylias的实现要快一些。

矿:1137.028毫秒
以利亚的:2664.396毫秒
assylias“:1373.473毫秒

坚持我的评论,我会重视可读性高于性能,这里是一个版本,它应该清楚发生了什么(假设你已经使用BigDecimal s之前),没有过多的评论(我相信在自我logging代码),而不必担心性能(因为我不能想象一个场景,你想要做这个数以百万计的性能甚至成为考虑)。

这个版本:

  • 使用BigDecimal来精确并避免舍入问题
  • 按照OP的要求进行四舍五入
  • 适用于其他舍入模式,例如testing中的HALF_UP
  • 允许您调整精度(更改REQUIRED_PRECISION
  • 使用一个enum来定义阈值,即可以很容易地调整为使用KB / MB / GB / TB而不是k / m / b / t等等,如果需要的话,当然可以扩展到超过TRILLION
  • 因为testing用例没有testing边界,所以进行了彻底的unit testing
  • 应该为零和负数工作

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

(Uncomment the println statements or change to use your favourite logger to see what it's doing.)

And finally, the tests in NumberShortenerTest (plain 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)); } } } 

Feel free to point out in the comments if I missed a significant test case or if expected values should be adjusted.

For anyone that wants to round. This is a great, easy to read solution, that takes advantage of the Java.Lang.Math library

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

The following code shows how you can do this with easy expansion in mind.

The "magic" lies mostly in the makeDecimal function which, for the correct values passed in, guarantees you will never have more than four characters in the output.

It first extracts the whole and tenths portions for a given divisor so, for example, 12,345,678 with a divisor of 1,000,000 will give a whole value of 12 and a tenths value of 3 .

From that, it can decide whether it outputs just the whole part or both the whole and tenths part, using the rules:

  • If tenths part is zero, just output whole part and suffix.
  • If tenths part is greater than nine, just output whole part and suffix.
  • Otherwise, output whole part, tenths part and suffix.

The code for that follows:

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

Then, it's a simple matter of calling that helper function with the correct values, including some constants to make life easier for the developer:

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

The fact that the makeDecimal function does the grunt work means that expanding beyond 999,999,999 is just a matter of adding an extra line to Xlat , so easy that I've done it for you.

The final return in Xlat doesn't need a conditional since the largest value you can hold in a 64-bit signed long is only about 9.2 quintillion.

But if, by some bizarre requirement, Oracle decides to add a 128-bit longer type or a 1024-bit damn_long type, you'll be ready for it 🙂


And, finally, a little test harness you can use for validating the functionality.

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

You can see from the output that it gives you what you need:

 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 

And, as an aside, be aware that passing in a negative number to this function will result in a string too long for your requirements, since it follows the < THOU path). I figured that was okay since you only mention non-negative values in the question.

Adding my own answer, Java code, self explanatory code..

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

This code snippet just deadly simple, and clean code, and totally works:

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

尝试这个 :

 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 }