1. Overview

In this quick article, we’ll learn about the different approaches to format a number with a thousand separators and how to implement them.

2. Setup

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.

3. Using String Format

In our first implementation, we’ll use String.format:

String.format(Locale.GERMANY, "%,d", number)

We use here the following parameters:

  • Locale.GERMANY makes formatting locale-sensitive. In the case of Germany, the dot character is used.
  • “%,d” follows the format convention of java.util.Formatter, which, in this case, adds a thousand separator.
  • number is the input parameter of our function.

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.

4. Using DecimalFormat

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.

5. Using Chunking

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.

6. Validation

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:

  • $givenNumber is the given integer to test
  • $expectedString is the expected output to test against
  • function is the implementation we test
  • $name is the approach name

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.

7. Conclusion

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.


原始标题:Format Number With Thousand Separator in Kotlin