14

I need to convert double to string with given precision. String.format("%.3f", value) (or DecimalFormat) does the job, but benchmarks show that it is slow. Even Double.toString conversion takes about 1-3 seconds to convert 1 million numbers on my machine.

Are there any better way to do it?

UPDATE: Benchmarking results

Random numbers from 0 to 1000000, results are in operations per millisecond (Java 1.7.0_45), higher is better:

Benchmark                                    Mean   Mean error    Units

String_format                             747.394       13.197   ops/ms
BigDecimal_toPlainString                 1349.552       31.144   ops/ms
DecimalFormat_format                     1890.917       28.886   ops/ms
Double_toString                          3341.941       85.453   ops/ms
DoubleFormatUtil_formatDouble            7760.968       87.630   ops/ms
SO_User_format                          14269.388      168.206   ops/ms

UPDATE:

Java 10, +ryu, higher is better:

                                Mode  Cnt      Score      Error   Units
String_format                  thrpt   20    998.741 ±   52.704  ops/ms
BigDecimal_toPlainString       thrpt   20   2079.965 ±  101.398  ops/ms
DecimalFormat_format           thrpt   20   2040.792 ±   48.378  ops/ms
Double_toString                thrpt   20   3575.301 ±  112.548  ops/ms
DoubleFormatUtil_formatDouble  thrpt   20   7206.281 ±  307.348  ops/ms
ruy_doubleToString             thrpt   20   9626.312 ±  285.778  ops/ms
SO_User_format                 thrpt   20  17143.901 ± 1307.685  ops/ms
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
valodzka
  • 5,535
  • 4
  • 39
  • 50
  • the question is, do you need a specific precision, or localization features? If so, then you may want to use format. – Juan Alberto López Cavallotti May 11 '12 at 14:53
  • @JuanAlbertoLópezCavallotti Yes, I need specific precision. And format is too slow for my case. – valodzka May 11 '12 at 14:55
  • 1
    format is generic and powerfull but not very fast (converting from double to string is an incredibly complex problem). I think that in your case I'd round the number to a long, convert the long to a string and then insert the "." but I haven't benchmarked this. – Denys Séguret May 11 '12 at 14:56
  • 1
    @valodzka, So you're saying I need a way to format a number the way `String.format` and `DecimalFormat` does it, but I need to do it faster? If you find an answer, be sure to let the JDK-developers know, so they can include this algorithm in the next release. – aioobe May 11 '12 at 14:58
  • Do you mean it takes 1-3 seconds for 1 million numbers in general or is 1-3 seconds `Double.toString` faster? If it is the former, then 1-3 seconds doesn't seem that bad to me (depends on the machine though). And as already has been asked: _What's the use case for this?_ – Thomas May 11 '12 at 15:00
  • Also, after a second or two, there will be a speedup as JIT kicks in the freshly compiled code. Or did you count that in? – Petr Janeček May 11 '12 at 15:02
  • @aioobe JDK implementation far from optimal in many cases, and look like this is one of them – valodzka May 11 '12 at 15:04
  • @aioobe One example of speed up that I've found - https://github.com/netty/netty/issues/319#issuecomment-5643740 – valodzka May 11 '12 at 15:13
  • @JB Nizet data send in text format throughout network (API response) – valodzka May 11 '12 at 15:16
  • @Thomas it absolute time, and it slower then Double.toString in 15-20 times – valodzka May 11 '12 at 15:17
  • @valodzka: the time spent sending these strings on the network will probably be much higher (several orders of magnitude) than the time spent converting the numbers to strings. You're probably optimizing something that doesn't need to be optimized. Even you you manage to get a 500% gain on the conversion, the overall gain will probably be negligible. – JB Nizet May 11 '12 at 16:21
  • @valodzka do you have the code that was used to benchmark this ?, i'd like to update this for java 17. also is SO_User_format the chosen solution ?. – franklynd Nov 29 '22 at 19:27

4 Answers4

20

Disclaimer: I only recommend that you use this if speed is an absolute requirement.

On my machine, the following can do 1 million conversions in about 130ms:

 private static final int POW10[] = {1, 10, 100, 1000, 10000, 100000, 1000000};
 
 public static String format(double val, int precision) {
     StringBuilder sb = new StringBuilder();
     if (val < 0) {
         sb.append('-');
         val = -val;
     }
     int exp = POW10[precision];
     long lval = (long)(val * exp + 0.5);
     sb.append(lval / exp).append('.');
     long fval = lval % exp;
     for (int p = precision - 1; p > 0 && fval < POW10[p]; p--) {
         sb.append('0');
     }
     sb.append(fval);
     return sb.toString();
 }

The code as presented has several shortcomings: it can only handle a limited range of doubles, and it doesn't handle NaNs. The former can be addressed (but only partially) by extending the POW10 array. The latter can be explicitly handled in the code.


If you don't need thread-safe code, you can re-use the buffer for a little more speed (to avoid recreating a new object each time), such as:

  private static final int[] POW10 = {1, 10, 100, 1000, 10000, 100000, 1000000};
  private static final StringBuilder BUFFER = new StringBuilder();

  public String format( double value, final int precision ) {
    final var sb = BUFFER;
    sb.setLength( 0 );

    if( value < 0 ) {
      sb.append( '-' );
      value = -value;
    }

    final int exp = POW10[ precision ];
    final long lval = (long) (value * exp + 0.5);

    sb.append( lval / exp ).append( '.' );

    final long fval = lval % exp;

    for( int p = precision - 1; p > 0 && fval < POW10[ p ]; p-- ) {
      sb.append( '0' );
    }

    sb.append( fval );

    return sb.toString();
  }
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
NPE
  • 486,780
  • 108
  • 951
  • 1,012
8

If you need both speed and precision, I've developed a fast DoubleFormatUtil class at xmlgraphics-commons: http://xmlgraphics.apache.org/commons/changes.html#version_1.5rc1

You can see the code there: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/util/DoubleFormatUtil.java?view=markup

It's faster than both DecimalFormat/BigDecimal, as fast as Double.toString, it's precise, it's well tested. It's licensed under Apache License 2.0, so you can use it as you want.

Julien Aymé
  • 81
  • 1
  • 1
2

To my knowledge the fastest and most complete implementation is that of Jack Shirazi:

http://web.archive.org/web/20150623133220/http://archive.oreilly.com/pub/a/onjava/2000/12/15/formatting_doubles.html

Code: Original implementation is no longer available online (http://archive.oreilly.com/onjava/2000/12/15/graphics/DoubleToString.java). An implementation can be found here: https://raw.githubusercontent.com/openxal/openxal/57392be263b98565738d1962ba3b53e5ca60e64e/core/src/xal/tools/text/DoubleToString.java

It provides formatted (number of decimals) and unformatted doubleToString conversion. My observation is, that the JDK performance of unformatted conversion dramatically improved over the years, so here the gain is not so big anymore.

For formatted conversion it still is.

For benchmarkers: It often makes a big difference which kind of doubles are used, e.g. doubles very close to 0.

Henning
  • 1,289
  • 2
  • 11
  • 25
  • 1
    The first link is broken, WayBack Machine points to the following: http://web.archive.org/web/20150623133220/http://archive.oreilly.com/pub/a/onjava/2000/12/15/formatting_doubles.html (Second link is not archived at all.) – Volkan Yazıcı Dec 27 '19 at 12:40
-2

I haven't benchmarked this, but how about using BigDecimal?

BigDecimal bd = new BigDecimal(value).setScale(3, RoundingMode.HALF_UP);
return bd.toString();
Sean Reilly
  • 21,526
  • 4
  • 48
  • 62