1. Overview

In this tutorial, we’ll show different ways to format a number using the Scala standard library and Java interoperability features.

2. String Operations

The first approach we will see is a very naive one. Let’s convert our number into a String and make the necessary string operations. For this example, let’s assume we want to have at most seven digits:

scala> val x = 12.34
x: Double = 12.34

scala> val n = 7
n: Int = 7

scala> ("0"*n + x).takeRight(n)
res0: String = 0012.34

By adding 7 leading zeroes, adding the given number, and finally selecting the last seven digits, we get the original number with enough leading zeroes.

Another more convenient approach is to use the String.padTo method:

scala> x.toString.reverse.padTo(7, '0').reverse
res1: String = 0012.34

This method does the same as our previous approach, but in a much cleaner way: it adds enough given leading chars (in our case, leading zeroes) until the original string reaches the specified size.

3. Using Java Methods

Another possible approach is to leverage the interoperability between Scala and Java code. This will allow us to use the excellent existing support in the Java standard library. Note that this approach hides the fact that we are using the Java standard lib, as Scala wraps it in the String interpolation methods. This gives a Scala look and feel, but it’s just using Java underneath:

scala> val x = 12.34
x: Double = 12.34

scala> f"$x%.1f"
res2: String = 12.3

This specifies that we only want one digit in the decimal part, so the digit 4 is cropped.

If we specify a bigger amount of decimal digits, the number won’t be cropped:

scala> f"$x%.2f"
res3: String = 12.34

And if we specify an even larger amount of decimal digits, it will add zeroes to the end:

scala> f"$x%.3f"
res4: String = 12.340

What about the integer part? Let’s play with it:

scala> f"$x%0.3f"
java.util.MissingFormatWidthException: %0.3f
  at java.base/java.util.Formatter$FormatSpecifier.checkNumeric(Formatter.java:3291)
  at java.base/java.util.Formatter$FormatSpecifier.checkFloat(Formatter.java:3270)
  at java.base/java.util.Formatter$FormatSpecifier.<init>(Formatter.java:2994)
  at java.base/java.util.Formatter.parse(Formatter.java:2839)
  at java.base/java.util.Formatter.format(Formatter.java:2763)
  at java.base/java.util.Formatter.format(Formatter.java:2717)
  at java.base/java.lang.String.format(String.java:4150)
  at scala.collection.immutable.StringLike.format(StringLike.scala:354)
  at scala.collection.immutable.StringLike.format$(StringLike.scala:353)
  at scala.collection.immutable.StringOps.format(StringOps.scala:33)
  ... 26 elided

scala> f"$x%1.3f"
res5: String = 12.340

scala> f"$x%2.3f"
res6: String = 12.340

scala> f"$x%3.3f"
res7: String = 12.340

scala> f"$x%4.3f"
res8: String = 12.340

scala> f"$x%5.3f"
res9: String = 12.340

scala> f"$x%6.3f"
res10: String = 12.340

scala> f"$x%7.3f"
res11: String = " 12.340"

What can we see? We cannot specify ‘0’, as it will throw an exception. Nothing happens when we use 1, 2, 3, 4, 5, or even 6. Only with seven does it add a leading space. Why 7? Well, if we count the number of digits on our number, we have 5 + 1 if we include the dot. So, until 6, there’s nothing to add. On 7, we get a leading character.

If we want to replace the leading space by a zero, we can do that very easily:

scala> f"$x%07.3f"
res12: String = 012.340

scala> f"$x%08.3f"
res13: String = 0012.340

Simply by adding a ‘0’ after the % character.

5. Adding Commas to Numbers

Adding some commas for bigger numbers may be beneficial so that the different units can be read easily. To achieve that, we can once again make use of the Java interoperability with the NumberFormat.getIntegerInstance method:

scala> val formatter = java.text.NumberFormat.getIntegerInstance
formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc

scala> formatter.format(1000)
res0: String = 1,000

scala> formatter.format(1000000)
res1: String = 1,000,000

This makes it much easier to understand if there’s a missing zero, for instance.

One subtle detail is that some countries use commas to split the number, while others use dots or whitespaces. If this is a concern, we can make use of Locale:

scala> val locale = new java.util.Locale("pt", "PT")
locale: java.util.Locale = pt_PT

scala> val formatter = java.text.NumberFormat.getIntegerInstance(locale)
formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc

scala> formatter.format(1000000)
res69: String = 1 000 000

scala> val locale = new java.util.Locale("it", "IT")
locale: java.util.Locale = it_IT

scala> val formatter = java.text.NumberFormat.getIntegerInstance(locale)
formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc

scala> formatter.format(1000000)
res73: String = 1.000.000

6. Conclusion

In this article, we learned how to format a number using the Scala standard library. We also looked at using Java interoperability features to crop decimal digits, add leading spaces and zeroes, and use digit group separators depending on the Locale.