This is an excerpt from the Scala Cookbook, 2nd Edition. This is Recipe 3.8, Formatting Numbers and Currency.
Scala 3 Problem
You want to format numbers or currency to control decimal places and separators (commas and decimals), typically for printed output.
Solution
For basic number formatting, use the f
string interpolator. For other needs, such as adding commas and working with locales and currency, use instances of the java.text.NumberFormat class:
NumberFormat.getInstance // general-purpose numbers (floating-point)
NumberFormat.getIntegerInstance // integers
NumberFormat.getCurrencyInstance // currency
NumberFormat.getPercentInstance // percentages
The NumberFormat
instances can also be customized for locales.
The f
string interpolator
The f
string interpolator, which is discussed in detail in Substituting Variables Into Strings, provides simple number formatting capabilities:
val pi = scala.math.Pi // Double = 3.141592653589793
println(f"${pi}%1.5f") // 3.14159
A few more examples demonstrate the technique:
// floating-point
f"${pi}%1.2f" // String = 3.14
f"${pi}%1.3f" // String = 3.142
f"${pi}%1.5f" // String = 3.14159
f"${pi}%6.2f" // String = " 3.14"
f"${pi}%06.2f" // String = 003.14
// whole numbers
val x = 10_000
f"${x}%d" // 10000
f"${x}%2d" // 10000
f"${x}%8d" // " 10000"
f"${x}%-8d" // "10000 "
If you prefer the explicit use of the format
method that’s available to strings, write the code like this instead:
"%06.2f".format(pi) // String = 003.14
Commas, locales, and integers
When you want to format integer values, such as by adding commas in a locale like the United States, use NumberFormat
’s getIntegerInstance
method:
import java.text.NumberFormat
val formatter = NumberFormat.getIntegerInstance
formatter.format(10_000) // String = 10,000
formatter.format(1_000_000) // String = 1,000,000
That result shows commas because of my locale (near Denver, Colorado), but you can set a locale with getIntegerInstance
and the Locale class:
import java.text.NumberFormat
import java.util.Locale
val formatter = NumberFormat.getIntegerInstance(Locale.GERMANY)
formatter.format(1_000) // 1.000
formatter.format(10_000) // 10.000
formatter.format(1_000_000) // 1.000.000
Commas, locales, and floating-point values
You can handle floating-point values with a formatter returned by getInstance
:
val formatter = NumberFormat.getInstance
formatter.format(12.34) // 12.34
formatter.format(1_234.56) // 1,234.56
formatter.format(1_234_567.89) // 1,234,567.89
You can also set a locale with getInstance
:
val formatter = NumberFormat.getInstance(Locale.GERMANY)
formatter.format(12.34) // 12,34
formatter.format(1_234.56) // 1.234,56
formatter.format(1_234_567.89) // 1.234.567,89
Currency
For currency output, use the getCurrencyInstance
formatter. This is the default output in the United States:
val formatter = NumberFormat.getCurrencyInstance
formatter.format(123.456789) // $123.46
formatter.format(12_345.6789) // $12,345.68
formatter.format(1_234_567.89) // $1,234,567.89
Use a Locale
to format international currency:
import java.util.{Currency, Locale}
val deCurrency = Currency.getInstance(Locale.GERMANY)
val deFormatter = java.text.NumberFormat.getCurrencyInstance
deFormatter.setCurrency(deCurrency)
deFormatter.format(123.456789) // €123.46
deFormatter.format(12_345.6789) // €12,345.68
deFormatter.format(1_234_567.89) // €1,234,567.89
If you don’t use a currency library you’ll probably want to use BigDecimal
, which also works with getCurrencyInstance
. Here’s the default output in the United States:
import java.text.NumberFormat
import scala.math.BigDecimal.RoundingMode
val a = BigDecimal("10000.995") // BigDecimal = 10000.995
val b = a.setScale(2, RoundingMode.DOWN) // BigDecimal = 10000.99
val formatter = NumberFormat.getCurrencyInstance
formatter.format(b) // String = $10,000.99
Here are two examples of BigDecimal
values that uses a locale:
import java.text.NumberFormat
import java.util.Locale
import scala.math.BigDecimal.RoundingMode
val b = BigDecimal("1234567.891").setScale(2, RoundingMode.DOWN)
// result: BigDecimal = 1234567.89
val deFormatter = NumberFormat.getCurrencyInstance(Locale.GERMANY)
deFormatter.format(b) // String = 1.234.567,89 €
val ukFormatter = NumberFormat.getCurrencyInstance(Locale.UK)
ukFormatter.format(b) // String = £1,234,567.89
Custom formatting patterns
You can also create your own formatting patterns with the DecimalFormat
class. Just create the pattern you want, then apply the pattern to a number using the format
method, as shown in these examples:
import java.text.DecimalFormat
val df = DecimalFormat("0.##")
df.format(123.45) // 123.45 (type = String)
df.format(123.4567890) // 123.46
df.format(.1234567890) // 0.12
df.format(1_234_567_890) // 1234567890
val df = DecimalFormat("0.####")
df.format(.1234567890) // 0.1235
df.format(1_234.567890) // 1234.5679
df.format(1_234_567_890) // 1234567890
val df = DecimalFormat("#,###,##0.00")
df.format(123) // 123.00
df.format(123.4567890) // 123.46
df.format(1_234.567890) // 1,234.57
df.format(1_234_567_890) // 1,234,567,890.00
See the Java DecimalFormat class for more formatting pattern characters (and a warning that, in general, you shouldn’t create a direct instance of DecimalFormat
).
Locales
The java.util.Locale class has three constructors:
Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String data)
It also includes more than a dozen static instances for locales like CANADA
, CHINA
, FRANCE
, GERMAN
, JAPAN
, UK
, US
, and more. For countries and languages that don’t have Locale
constants, you can still specify them using a language or a pair of language/country strings. For example, per Oracle’s JDK 10 and JRE 10 Supported Locales page, locales in India can be specified like this:
Locale("hi-IN", "IN")
Locale("en-IN", "IN")
Here are a few other examples:
Locale("en-AU", "AU") // Australia
Locale("pt-BR", "BR") // Brazil
Locale("es-ES", "ES") // Spain
These examples demonstrate how the first India locale is used:
// India
import java.util.{Currency, Locale}
val indiaLocale = Currency.getInstance(Locale("hi-IN", "IN"))
val formatter = java.text.NumberFormat.getCurrencyInstance
formatter.setCurrency(indiaLocale)
formatter.format(123.456789) // ₹123.46
formatter.format(1_234.56789) // ₹1,234.57
With all of the get*Instance
methods of NumberFormat
you can also set a default locale:
import java.text.NumberFormat
import java.util.Locale
val default = Locale.getDefault
val formatter = NumberFormat.getInstance(default)
formatter.format(12.34) // 12.34
formatter.format(1_234.56) // 1,234.56
formatter.format(1_234_567.89) // 1,234,567.89
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Discussion
This recipe falls back to the Java approach for printing currency and other formatted numeric fields, though of course the currency solution depends on how you handle currency in your applications. In my work as a consultant, I’ve seen most companies handle currency using the Java BigDecimal
class, and others create their own custom currency classes, which are typically wrappers around BigDecimal
.
JSR-354, Money and Currency API defines a proposed API that hasn’t made it into the Java SDK at the time of this writing. You can also use libraries like Joda Money.