Formatting Numbers

“The purpose of computing is insight, not numbers.”

Richard Hamming

A fundamental assumption of software developers is that the system libraries are error-free and perform well. Every now and then you are taught better, but after a while the memories fade and you hang on to the original misconceptions.

During the performance measurements of the FreshMarker library, it became apparent that the naive use of the DecimalFormat class results in a considerable loss of speed.

For this reason, the c Built-In for numbers is currently used in the benchmarks for FreshMarker. Other template engines do not use any formatter in the benchmarks, so this is not really a trick.

The following table lists some benchmarks with JMH (Java Microbenchmark Harness) whose values differ greatly from one another.

Benchmarkops/s
DecimalFormat long3.677.384,716
DecimalFormat double15.102.242,841
Formatter long7.555.020,987
String.valueOf long42.179.552,006
NumberFormatter long11.475.252,138
NumberFormatter double12.558.752,656
Number Formatting Benchmarks

The first test uses the DecimalFormat#format(long) method. Similar values are also obtained for DecimalFormat#format(Object). Compared to the other benchmarks, the performance is extremely poor. The second benchmark has a much better performance because the DecimalFormat#format(double) method used there contains an optimization called fast-path.

The Formatter Benchmark uses the Formatter, which has been included in the standard library since Java 5. It is significantly faster for long values but significantly slower than DecimalFormat for double values.

Of course, longs can also be formatted faster if you do not localize the values. This is shown by the third benchmark of the String#valueOf(long) method. Unfortunately, there is no variant of this method with a localization.

The last two lines show the performance of the next FreshMarker NumberFormatter. This formatter is a wrapper around DecimalFormat that encapsulates the details of the localized formatting of numbers for FreshMarker. Its performance is slightly below that of the DecimalFormat performance for double. Both for double and for long values. Some of you will have guessed that the good performance for long values is achieved by converting them to double values.

In the future, I will probably have to adapt the localized formatting of long values to the implementation of String#valueOf(long) so that the performance of the template engine is noticeably better at this point.

For all readers, however, it is worth knowing about the performance of the libraries used, even without the use of a template engine.

Leave a Comment