Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: February 22, 2025
In this quick article, we’ll learn about the different approaches to format a number with a thousand separators and how to implement them.
First, let’s define our method signature, which we’ll use for our implementations:
fun formatted(number: Int): String
Our function will accept an Int as input and return a String. The output is a String representation of the input, formatted with a dot separator after each three-character sequence.
For example, putting 1000 into the function should result in a ‘1.000’ string; and inserting 0 should result in ‘0’.
Now, let’s look at a few approaches.
In our first implementation, we’ll use String.format:
String.format(Locale.GERMANY, "%,d", number)
We use here the following parameters:
Using this implementation, we take full advantage of a long-established functionality in Kotlin & Java and the adaptability to region-specific settings.
It is also a very concise, single-line expression, which brings benefits to simplicity and readability.
The second implementation will make use of DecimalFormat:
DecimalFormat("#,###")
.format(number)
.replace(",", ".")
The “#,###” pattern will format our number to a thousand-separated String, but with a comma separator. That’s why we need to replace them with a dot.
We could design it even simpler by using the locale-sensitive approach:
DecimalFormat("#,###", DecimalFormatSymbols(Locale.GERMANY)).format(number)
Just like the implementation with String.format, the parameter DecimalFormatSymbols(Locale.GERMANY) will produce the dot separator in the formatted String.
In our last implementation, we’ll get creative with the use of String chunking:
number.toString()
.reversed() // 15000 -> 00051
.chunked(3) // [000] [51]
.joinToString(".") // 000.51
.reversed() // 15.000
The number as a String is reversed at first because the character sequences must be read from right to left. Then, we chunk it into three-character Strings and join the list with a dot separator. As the last step, the String is reversed again, back to the original order.
Notably, this method lacks localization and adds more complexity, which makes other approaches more desirable.
Now that we’ve seen all our different implementations, let’s test each one for correctness.
We’ll use the Kotlin native framework Kotest with the writing style ShouldSpec. Its features to write dynamic parameterized tests will help us greatly.
Here is what our parameterized test would look like:
should("return '$expectedString' for $givenNumber with $name implementation") {
assertThat(function(givenNumber)).isEqualTo(expectedString)
}
As we can notice, there are four parameters:
The assertion checks whether the output of our implementing function is equal to the expected output. The parameter data to iterate through would be:
private val givenToExpectedPairs = listOf(
0 to "0",
12 to "12",
456 to "456",
100_000 to "100.000",
1_234_567 to "1.234.567"
).forEach { (givenNumber , expectedString) -> /* execute test here */ }
With these pairs, we cover multiple examples, which should be valid for all of the approaches.
In this article, we’ve learned how to produce a String representation of an Integer with a thousands separator, including the adaptation to locale-sensitive formatting. In the end, we wrote a dynamic parameterized test to get a good coverage.