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));
}
double num = 2718.28;
NumberFormat formatter =
new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
String result = formatter.format(num);
System.out.println(result);
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 Solution
解决方案最初是用 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)
}
}
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;
}
}
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.1 M 也太长了,这就是为什么对于负数,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 == Long.MIN_VALUE的分配 number = -number将会失败。如果我们必须做一个检查,那么我们可以包括尽可能多的数字,而不只是检查 number == Long.MIN_VALUE。
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(当只使用正数时大致相同,当反转执行顺序时更极端,可能与垃圾收集有关)
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);
}
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);
}
然后,只需使用正确的值调用 helper 函数,包括一些常量,使开发人员的工作变得更加容易:
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");
}
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);
}
}
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