1. Overview
In Java, we can use the printf format string to format numbers.
In this quick tutorial, let’s explore how to format numbers, mainly decimal numbers, in Kotlin.
2. Introduction to the Problem
When we need to convert a Float or Double number to a String, we usually want to specify the number’s precision or scale. An example can explain it quickly. Let’s say we have a Double instance PI:
private const val PI = 3.141592653589793
Now, we want to build a method to convert the Double number into a String and apply the given scale, for example:
private const val EXPECTED_SCALE_0 = "3"
private const val EXPECTED_SCALE_2 = "3.14"
private const val EXPECTED_SCALE_4 = "3.1416"
In this tutorial, we’ll explore a few approaches to doing the job. For simplicity, we’ll use the unit test’s assertions to verify if our formatting methods work as expected.
3. Using Java’s String.format Method
Java’s String.format method is convenient for formatting an input using the printf format string. We can easily build a method in Kotlin that invokes the Java method to convert a Kotlin Double number to a String:
fun usingJavaStringFormat(input: Double, scale: Int) = String.format("%.${scale}f", input)
As the code above shows, the usingJavaStringFormat method receives two parameters, the Double number input, and the scale.
Next, let’s write a small test method to verify if the method returns the expected String:
val out0 = usingJavaStringFormat(PI, 0)
assertThat(out0).isEqualTo(EXPECTED_SCALE_0)
val out2 = usingJavaStringFormat(PI, 2)
assertThat(out2).isEqualTo(EXPECTED_SCALE_2)
val out4 = usingJavaStringFormat(PI, 4)
assertThat(out4).isEqualTo(EXPECTED_SCALE_4)
If we execute it, the test passes. So, Java’s String.format approach does the job.
It’s worth mentioning that, in the usingJavaStringFormat method, we’ve used Kotlin’s String template to set the scale: ${scale}.
In fact, the standard printf format string’s syntax supports ‘***‘ to define a dynamic width. Let’s take shell’s printf command to show some examples:
$ printf "%.*f\n" 0 3.1415926
3
$ printf "%.*f\n" 2 3.1415926
3.14
$ printf "%.*f\n" 4 3.1415926
3.1416
However, Java’s format method doesn’t support using ‘*’ as the placeholder for a dynamic width in the format string. Therefore, if we would like to make our format support dynamic width, we need to concatenate different parts in Java, such as “%.” + scale + “f”, or use the string template in Kotlin, as we’ve done in the method above.
4. Using Kotlin’s String.format Function
To make Java’s String.format method easier to use, Kotlin has defined an extension function String.format in the standard library:
public inline fun String.format(vararg args: Any?): String = java.lang.String.format(this, *args)
As we can see, Kotlin’s String.format function is calling Java’s String.format method internally.
Using this convenient extension function, we can create a similar function to accept the specified scale for the formatting:
fun usingKotlinStringFormat(input: Double, scale: Int) = "%.${scale}f".format(input)
Let’s test if it works for our PI example:
val out0 = usingKotlinStringFormat(PI, 0)
assertThat(out0).isEqualTo(EXPECTED_SCALE_0)
val out2 = usingKotlinStringFormat(PI, 2)
assertThat(out2).isEqualTo(EXPECTED_SCALE_2)
val out4 = usingKotlinStringFormat(PI, 4)
assertThat(out4).isEqualTo(EXPECTED_SCALE_4)
When we run the test, it turns out our usingKotlinStringFormat function works as expected.
5. Creating an Extension Function on the Double Class
Now, we’ve already mentioned Kotlin’s extension functions. Another solution to this problem is to create an extension function to do the formatting on the input data class.
For example, since our example input is a Double number, we can create an extension function on the Double class:
fun Double.format(scale: Int) = "%.${scale}f".format(this)
As we can see, the extension function above calls Kotlin’s String.format function. However, we can call it more fluently, for example:
val text = "Pi (${PI.format(4)}) is an important concept that appears in all aspects of math!"
Finally, let’s verify if our extension function works:
val out0 = PI.format(0)
assertThat(out0).isEqualTo(EXPECTED_SCALE_0)
val out2 = PI.format(2)
assertThat(out2).isEqualTo(EXPECTED_SCALE_2)
val out4 = PI.format(4)
assertThat(out4).isEqualTo(EXPECTED_SCALE_4)
The test passes when we execute it.
6. Conclusion
In this article, we’ve addressed three ways to format decimal numbers in Kotlin.
As always, the full source code used in the article can be found on GitHub.