Принятый ответ (напишите свой собственный пользовательский форматер) правильный, но желаемый формат OP несколько необычен, поэтому, вероятно, не будет таким полезным для других?
Вот пользовательская реализация для чисел, которые: требуют запятых; иметь до двух знаков после запятой. Это полезно для корпоративных вещей, таких как валюты и проценты.
/**
* Formats a decimal to either zero (if an integer) or two (even if 0.5) decimal places. Useful
* for currency. Also adds commas.
* <p>
* Note: Java's <code>DecimalFormat</code> is neither Thread-safe nor particularly fast. This is our attempt to improve it. Basically we pre-render a bunch of numbers including their
* commas, then concatenate them.
*/
private final static String[] PRE_FORMATTED_INTEGERS = new String[500_000];
static {
for ( int loop = 0, length = PRE_FORMATTED_INTEGERS.length; loop < length; loop++ ) {
StringBuilder builder = new StringBuilder( Integer.toString( loop ) );
for ( int loop2 = builder.length() - 3; loop2 > 0; loop2 -= 3 ) {
builder.insert( loop2, ',' );
}
PRE_FORMATTED_INTEGERS[loop] = builder.toString();
}
}
public static String formatShortDecimal( Number decimal, boolean removeTrailingZeroes ) {
if ( decimal == null ) {
return "0";
}
// Use PRE_FORMATTED_INTEGERS directly for short integers (fast case)
boolean isNegative = false;
int intValue = decimal.intValue();
double remainingDouble;
if ( intValue < 0 ) {
intValue = -intValue;
remainingDouble = -decimal.doubleValue() - intValue;
isNegative = true;
} else {
remainingDouble = decimal.doubleValue() - intValue;
}
if ( remainingDouble > 0.99 ) {
intValue++;
remainingDouble = 0;
}
if ( intValue < PRE_FORMATTED_INTEGERS.length && remainingDouble < 0.01 && !isNegative ) {
return PRE_FORMATTED_INTEGERS[intValue];
}
// Concatenate our pre-formatted numbers for longer integers
StringBuilder builder = new StringBuilder();
while ( true ) {
if ( intValue < PRE_FORMATTED_INTEGERS.length ) {
String chunk = PRE_FORMATTED_INTEGERS[intValue];
builder.insert( 0, chunk );
break;
}
int nextChunk = intValue / 1_000;
String chunk = PRE_FORMATTED_INTEGERS[intValue - ( nextChunk * 1_000 ) + 1_000];
builder.insert( 0, chunk, 1, chunk.length() );
intValue = nextChunk;
}
// Add two decimal places (if any)
if ( remainingDouble >= 0.01 ) {
builder.append( '.' );
intValue = (int) Math.round( ( remainingDouble + 1 ) * 100 );
builder.append( PRE_FORMATTED_INTEGERS[intValue], 1, PRE_FORMATTED_INTEGERS[intValue].length() );
if ( removeTrailingZeroes && builder.charAt( builder.length() - 1 ) == '0' ) {
builder.deleteCharAt( builder.length() - 1 );
}
}
if ( isNegative ) {
builder.insert( 0, '-' );
}
return builder.toString();
}
Этот микро-тест показывает, что он в 2 раза быстрее, чем DecimalFormat
(но, конечно, YMMV в зависимости от вашего варианта использования). Улучшения приветствуются!
/**
* Micro-benchmark for our custom <code>DecimalFormat</code>. When profiling, we spend a
* surprising amount of time in <code>DecimalFormat</code>, as noted here
* https://bugs.openjdk.java.net/browse/JDK-7050528. It is also not Thread-safe.
* <p>
* As recommended here
* /6392805/bolee-bystraya-alternativa-decimalformat-format
* we can write a custom format given we know exactly what output we want.
* <p>
* Our code benchmarks around 2x as fast as <code>DecimalFormat</code>. See micro-benchmark
* below.
*/
public static void main( String[] args ) {
Random random = new Random();
DecimalFormat format = new DecimalFormat( "###,###,##0.##" );
for ( int warmup = 0; warmup < 100_000_000; warmup++ ) {
MathUtils.formatShortDecimal( random.nextFloat() * 100_000_000 );
format.format( random.nextFloat() * 100_000_000 );
}
// DecimalFormat
long start = System.currentTimeMillis();
for ( int test = 0; test < 100_000_000; test++ ) {
format.format( random.nextFloat() * 100_000_000 );
}
long end = System.currentTimeMillis();
System.out.println( "DecimalFormat: " + ( end - start ) + "ms" );
// Custom
start = System.currentTimeMillis();
for ( int test = 0; test < 100_000_000; test++ ) {
MathUtils.formatShortDecimal( random.nextFloat() * 100_000_000 );
}
end = System.currentTimeMillis();
System.out.println( "formatShortDecimal: " + ( end - start ) + "ms" );
}