Fucking Format Style!
GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Numeric Styles

Number Style Xcode 13+

The many ways you can customize the display of numbers.

There are many ways to format Swift’s numerical types (Float, Double, Decimal, and Integer) for display to the user. Each of the following options can be used by the Percent Format Styles and the Currency Format Style.

The examples below show the individual options available to format your final string, the real power available is that you chain these options together to allow for a truly staggering amount of customization.

The easiest and best way to access this style is through the .number extension on FormatStyle. From there, you can use method chaining to customize the output.

Float(10).formatted(.number.scale(200.0).notation(.compactName).grouping(.automatic)) // "2K"

You can also initialize an instance of IntegerFormatStyle<Value: BinaryInteger>, FloatingPointFormatStyle<BinaryFloatingPoint> or Decimal.FormatStyle and use method chaining to customize the output.

FloatingPointFormatStyle<Double>().rounded(rule: .up, increment: 1).format(10.9) // "11"
IntegerFormatStyle<Int>().notation(.compactName).format(1_000) // "1K"
Decimal.FormatStyle().scale(10).format(1) // "10"

Available Properties

Property Description
Rounding Customize the rounding behaviour
Sign Do you want to show or hide the + or - sign?
Decimal Separator Do you want to show or hide the decimal separator
Grouping How do you want the thousands numbers to be grouped
Precision How many fractional or significant digits do you want to show
Notation Enable scientific or compact notation
Scale Scale the number up or down before display
Locale Set the Locale for one output
Compositing Mix and match any and all of the above
AttributedString output Output an AttributedString

Rounding

At its simplest, you can call the .formatted(.number.rounded()) method on any number type (Float, Double, Decimal, or Integer) in order to get the system’s default rounding behaviour.

Double(1.9999999).formatted(.number.rounded())  // "2"
Decimal(1.9999999).formatted(.number.rounded()) // "2"
Float(1.9999999).formatted(.number.rounded())   // "2"
Int(1.9999999).formatted(.number.rounded())     // "1"

Using the full instance method, you can access more granular settings: .number.rounded(rule:increment:).

Rounding Rule Description
.awayFromZero Round to the closest allowed value whose magnitude is greater than or equal to that of the source.
.down Round to the closest allowed value that is less than or equal to the source.
.toNearestOrAwayFromZero Round to the closest allowed value; if two values are equally close, the one with greater
.toNearestOrEven Round to the closest allowed value; if two values are equally close, the even one is chosen.
.towardZero Round to the closest allowed value whose magnitude is less than or equal to that of the source.
.up Round to the closest allowed value that is greater than or equal to the source.

The increment: parameter is a Double and tells the system under the hood what to round the value by.

Float(0.26575467567788).formatted(.number.rounded(rule: .awayFromZero))              // "0.265755"
Float(0.00900999876871).formatted(.number.rounded(rule: .awayFromZero))              // "0.00901"

Float(5.01).formatted(.number.rounded(rule: .awayFromZero, increment: 1))            // "6"
Float(5.01).formatted(.number.rounded(rule: .awayFromZero, increment: 10))           // "10"
Float(0.01).formatted(.number.rounded(rule: .down))                                  // "0.009999"
Float(0.01).formatted(.number.rounded(rule: .toNearestOrAwayFromZero))               // "0.01"
Float(0.01).formatted(.number.rounded(rule: .towardZero))                            // "0.009999"
Float(0.01).formatted(.number.rounded(rule: .up))                                    // "0.01"
Float(5.01).formatted(.number.rounded(rule: .down, increment: 1))                    // "5"
Float(5.01).formatted(.number.rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "5"
Float(5.01).formatted(.number.rounded(rule: .towardZero, increment: 1))              // "5"
Float(5.01).formatted(.number.rounded(rule: .up, increment: 1))                      // "5"

Double(0.26575467567788).formatted(.number.rounded(rule: .awayFromZero))              // "0.265755"
Double(0.00900999876871).formatted(.number.rounded(rule: .awayFromZero))              // "0.00901"
Double(5.01).formatted(.number.rounded(rule: .awayFromZero, increment: 1))            // "6"
Double(5.01).formatted(.number.rounded(rule: .awayFromZero, increment: 10))           // "10"
Double(0.01).formatted(.number.rounded(rule: .down))                                  // "0.01"
Double(0.01).formatted(.number.rounded(rule: .toNearestOrAwayFromZero))               // "0.01"
Double(0.01).formatted(.number.rounded(rule: .towardZero))                            // "0.01"
Double(0.01).formatted(.number.rounded(rule: .up))                                    // "0.01"
Double(5.01).formatted(.number.rounded(rule: .down, increment: 1))                    // "5"
Double(5.01).formatted(.number.rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "5"
Double(5.01).formatted(.number.rounded(rule: .towardZero, increment: 1))              // "5"
Double(5.01).formatted(.number.rounded(rule: .up, increment: 1))                      // "5"

Decimal(0.26575467567788).formatted(.number.rounded(rule: .awayFromZero))              // "0.265755"
Decimal(0.00900999876871).formatted(.number.rounded(rule: .awayFromZero))              // "0.00901"
Decimal(5.01).formatted(.number.rounded(rule: .awayFromZero, increment: 1))            // "6"
Decimal(5.01).formatted(.number.rounded(rule: .awayFromZero, increment: 10))           // "10"
Decimal(0.01).formatted(.number.rounded(rule: .down))                                  // "0.01"
Decimal(0.01).formatted(.number.rounded(rule: .toNearestOrAwayFromZero))               // "0.01"
Decimal(0.01).formatted(.number.rounded(rule: .towardZero))                            // "0.01"
Decimal(0.01).formatted(.number.rounded(rule: .up))                                    // "0.01"
Decimal(5.01).formatted(.number.rounded(rule: .down, increment: 1))                    // "5"
Decimal(5.01).formatted(.number.rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "5"
Decimal(5.01).formatted(.number.rounded(rule: .towardZero, increment: 1))              // "5"
Decimal(5.01).formatted(.number.rounded(rule: .up, increment: 1))                      // "5"

Sign

Controls the visibility of the negative and positive sign.

Sign Display Strategy Description
.automatic Displays the negative sign (-) when the number is negative. Positive sign isn’t shown
.never Never shows the positive (+) or negative (-) signs
.always(includingZero:) Passing in true will show the positive sign on a 0 value
Float(1.90).formatted(.number.sign(strategy: .never))                     // "1.9"
Float(-1.90).formatted(.number.sign(strategy: .never))                    // "1.9"
Float(1.90).formatted(.number.sign(strategy: .automatic))                 // "1.9"
Float(1.90).formatted(.number.sign(strategy: .always()))                  // "+1.9"
Float(0).formatted(.number.sign(strategy: .always(includingZero: true)))  // "+0"
Float(0).formatted(.number.sign(strategy: .always(includingZero: false))) // "0"

Decimal Separator

Controls the visibility of the decimal separator.

Decimal Separator Display Strategy Descriotion
.automatic Only shows the decimal separator on fractional values
.always Always shows the decimal separator
Float(10).formatted(.number.decimalSeparator(strategy: .automatic)) // "10"
Float(10).formatted(.number.decimalSeparator(strategy: .always))    // "10."

Grouping

Controls if the thousands units are grouped or not.

Grouping Descriotion
.never Never group thousands digits
.automatic Group the digits automatically based on the locale
Float(1000).formatted(.number.grouping(.automatic)) // "1,000"
Float(1000).formatted(.number.grouping(.never))     // "1000"

Precision

There are seven options to set the precision of the output.

Precision Option Description
.significantDigits(Int) Sets a fixed number of significant digits to show
.significantDigits(Range) Sets a range of significant digits to show
.fractionLength(Int) Sets the number digits after the decimal separator
.fractionLength(Range) Sets a range of digits to show after the decimal separator
.integerLength(Int) Sets the number of digits to show before the decimal separator
.integerLength(Range) Sets a range of digits to show before the decimal separator
.integerAndFractionLength(integer:fraction:) Sets both the integer and fractional digits to display
Decimal(10.1).formatted(.number.precision(.significantDigits(1))) // "10"
Decimal(10.1).formatted(.number.precision(.significantDigits(2))) // "10"
Decimal(10.1).formatted(.number.precision(.significantDigits(3))) // "10.1"
Decimal(10.1).formatted(.number.precision(.significantDigits(4))) // "10.10"
Decimal(10.1).formatted(.number.precision(.significantDigits(5))) // "10.100"
Decimal(1000000.1).formatted(.number.precision(.significantDigits(5))) // "10.100"

Decimal(1).formatted(.number.precision(.significantDigits(1 ... 3)))     // "1"
Decimal(10).formatted(.number.precision(.significantDigits(1 ... 3)))    // "10"
Decimal(10.1).formatted(.number.precision(.significantDigits(1 ... 3)))  // "10.1"
Decimal(10.01).formatted(.number.precision(.significantDigits(1 ... 3))) // "10"

Decimal(10.01).formatted(.number.precision(.fractionLength(1))) // 10.0
Decimal(10.01).formatted(.number.precision(.fractionLength(2))) // 10.01
Decimal(10.01).formatted(.number.precision(.fractionLength(3))) // 10.010

Decimal(10).formatted(.number.precision(.fractionLength(0...2)))        // 10
Decimal(10.1).formatted(.number.precision(.fractionLength(0...2)))      // 10.1
Decimal(10.11).formatted(.number.precision(.fractionLength(0...2)))     // 10.11
Decimal(10.111).formatted(.number.precision(.fractionLength(0...2)))    // 10.11

Decimal(10.111).formatted(.number.precision(.integerLength(1))) // 0.111
Decimal(10.111).formatted(.number.precision(.integerLength(2))) // 10.111

Decimal(10.111).formatted(.number.precision(.integerLength(0...1))) // .111
Decimal(10.111).formatted(.number.precision(.integerLength(0...2))) // 10.111
Decimal(10.111).formatted(.number.precision(.integerLength(0...3))) // 10.111

Decimal(10.111).formatted(.number.precision(.integerAndFractionLength(integer: 1, fraction: 1))) // 0.1
Decimal(10.111).formatted(.number.precision(.integerAndFractionLength(integer: 2, fraction: 1))) // 10.1
Decimal(10.111).formatted(.number.precision(.integerAndFractionLength(integer: 2, fraction: 2))) // 10.11
Decimal(10.111).formatted(.number.precision(.integerAndFractionLength(integer: 2, fraction: 3))) // 10.111

Notation

Controls the ability to use different notation styles.

Notation Setting Description
.automatic Used primarily when compositing other styles, will choose the best notation setting for the given settings.
.compactName Uses the compact notation for the thousands styles
.scientific Uses scientific notation
Float(1_000).formatted(.number.notation(.automatic))   // "1,000"
Float(1_000).formatted(.number.notation(.compactName)) // "1K"
Float(1_000).formatted(.number.notation(.scientific))  // "1E3"

Scale

Controls the scale of the number.

Float(10).formatted(.number.scale(1.0))  // "10"
Float(10).formatted(.number.scale(1.5))  // "15"
Float(10).formatted(.number.scale(2.0))  // "20"
Float(10).formatted(.number.scale(-2.0)) // "-20"

Setting the Locale

Controls the locale of the output.

Float(1_000).formatted(.number.notation(.automatic).locale(Locale(identifier: "fr_FR")))   // "1 000"
Float(1_000).formatted(.number.notation(.compactName).locale(Locale(identifier: "fr_FR"))) // "1 k"
Float(1_000).formatted(.number.notation(.scientific).locale(Locale(identifier: "fr_FR")))  // "1E3"

Float(1000).formatted(.number.grouping(.automatic).locale(Locale(identifier: "fr_FR"))) // "1 000"
Float(1000).formatted(.number.grouping(.never).locale(Locale(identifier: "fr_FR")))     // "1000"

Localizing Number Systems

In cases where a given Locale has multiple number systems available, numeric format styles will default to using the number system which matches the your system’s Locale.current value. You’re able to explicitly set the number system for the Format Style to use by initializing a new Locale with the number system set using the BCP-47 or ICU Identifiers:

let englishArabicBCP47 = "en-u-nu-arab"
let enArabBCP47 = Locale(identifier: englishArabicBCP47)
123456.formatted(.number.locale(enArabBCP47)) // "١٢٣٬٤٥٦"
Date.now.formatted(.dateTime.year().month().day().locale(enArabBCP47)) // "Sep ٢٣, ٢٠٢٣"

ICU

let englishArabicICU = "en@numbers=arab"
let enArabICU = Locale(identifier: "en@numbers=arab")
12345.formatted(.number.locale(enArabICU)) // "١٢٬٣٤٥"
Date.now.formatted(.dateTime.year().month().day().locale(enArabICU)) // "Sep ٢٣, ٢٠٢٣"

Compositing

Any of the above styles can be combined to fully customize the output.

Float(10).formatted(.number.scale(200.0).notation(.compactName).grouping(.automatic)) // "2K"

Attributed String Output

Outputs and AttributedString instead of a String.

Float(10).formatted(.number.scale(200.0).notation(.compactName).grouping(.automatic).attributed)

Parsing Numbers From Strings

Each of Swift’s build-in numeric types supports the parsing of numeric string into their respective types.

// MARK: Parsing Integers
try? Int("120", format: .number) // 120
try? Int("0.25", format: .number) // 0
try? Int("1E5", format: .number.notation(.scientific)) // 100000

// MARK: Parsing Floating Point Numbers
try? Double("0.0025", format: .number) // 0.0025
try? Double("95%", format: .number) // 95
try? Double("1E5", format: .number.notation(.scientific)) // 100000

try? Float("0.0025", format: .number) // 0.0025
try? Float("95%", format: .number) // 95
try? Float("1E5", format: .number.notation(.scientific)) // 100000

// MARK: - Parsing Decimals
try? Decimal("0.0025", format: .number) // 0.0025
try? Decimal("95%", format: .number) // 95
try? Decimal("1E5", format: .number.notation(.scientific)) // 100000

Percent Style Xcode 13+

Output number as a percentage.

There are many ways to format Swift’s numerical types (Float, Double, Decimal, and Integer) for display to the user. Each of the following options can be used by the Percent Format Styles and the Currency Format Style.

The examples below show the individual options available to format your final string, the real power available is that you chain these options together to allow for a truly staggering amount of customization.

Percentages are set by a range from 0.0 to 1.0, where 0.5 being 50%. This is consistent with the rest of Cocoa.

The easiest and best way to access this style is through the .percent extension on FormatStyle. From there, you can use method chaining to customize the output.

0.1.formatted(.percent) // "10%"

You can also initialize an instance of IntegerFormatStyle<Value: BinaryInteger>.Percent, FloatingPointFormatStyle<BinaryFloatingPoint>.Percent or Decimal.FormatStyle.Percent and use method chaining to customize the output.

FloatingPointFormatStyle<Double>.Percent().rounded(rule: .up, increment: 1).format(0.109) // "11%"
IntegerFormatStyle<Int>.Percent().notation(.compactName).format(1_000) // "1K%"
Decimal.FormatStyle.Percent().scale(12).format(0.1) // "1.2%"

Available Properties

Property Description
Rounding Customize the rounding behaviour
Sign Do you want to show or hide the + or - sign?
Decimal Separator Do you want to show or hide the decimal separator
Grouping How do you want the thousands numbers to be grouped
Precision How many fractional or significant digits do you want to show
Notation Enable scientific or compact notation
Scale Scale the number up or down before display
Locale Set the Locale for one output
Compositing Mix and match any and all of the above
AttributedString output Output an AttributedString

Rounding

At its simplest, you can call the .formatted(.number.rounded()) method on any number type (Float, Double, Decimal, or Integer) in order to get the system’s default rounding behaviour.

Double(1.9999999).formatted(.percent.rounded())  // "199.99999%"
Decimal(1.9999999).formatted(.percent.rounded()) // "199.99999%"
Float(1.9999999).formatted(.percent.rounded())   // "199.999998%"
Int(1.9999999).formatted(.percent.rounded())     // 1%

Using the full instance method, you can access more granular settings: .number.rounded(rule:increment:).

Rounding Rule Description
.awayFromZero Round to the closest allowed value whose magnitude is greater than or equal to that of the source.
.down Round to the closest allowed value that is less than or equal to the source.
.toNearestOrAwayFromZero Round to the closest allowed value; if two values are equally close, the one with greater
.toNearestOrEven Round to the closest allowed value; if two values are equally close, the even one is chosen.
.towardZero Round to the closest allowed value whose magnitude is less than or equal to that of the source.
.up Round to the closest allowed value that is greater than or equal to the source.

The increment: parameter is a Double and tells the system under the hood what to round the value by.

Float(0.26575467567788).formatted(.percent.rounded(rule: .awayFromZero))              // "26.575467%"
Float(0.00900999876871).formatted(.percent.rounded(rule: .awayFromZero))              // "0.901%"
Float(5.01).formatted(.percent.rounded(rule: .awayFromZero, increment: 1))            // "502%"
Float(5.01).formatted(.percent.rounded(rule: .awayFromZero, increment: 10))           // "510%"
Float(0.01).formatted(.percent.rounded(rule: .down))                                  // "0.999999%"
Float(0.01).formatted(.percent.rounded(rule: .toNearestOrAwayFromZero))               // "1%"
Float(0.01).formatted(.percent.rounded(rule: .towardZero))                            // "0.999999%"
Float(0.01).formatted(.percent.rounded(rule: .up))                                    // "1%"
Float(5.01).formatted(.percent.rounded(rule: .down, increment: 1))                    // "501%"
Float(5.01).formatted(.percent.rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "501%"
Float(5.01).formatted(.percent.rounded(rule: .towardZero, increment: 1))              // "501%"
Float(5.01).formatted(.percent.rounded(rule: .up, increment: 1))                      // "502%"

Double(0.26575467567788).formatted(.percent.rounded(rule: .awayFromZero))              // "26.575468%"
Double(0.00900999876871).formatted(.percent.rounded(rule: .awayFromZero))              // "0.901%"
Double(5.01).formatted(.percent.rounded(rule: .awayFromZero, increment: 1))            // "501%"
Double(5.01).formatted(.percent.rounded(rule: .awayFromZero, increment: 10))           // "510%"
Double(0.01).formatted(.percent.rounded(rule: .down))                                  // "1%"
Double(0.01).formatted(.percent.rounded(rule: .toNearestOrAwayFromZero))               // "1%"
Double(0.01).formatted(.percent.rounded(rule: .towardZero))                            // "1%"
Double(0.01).formatted(.percent.rounded(rule: .up))                                    // "1%"
Double(5.01).formatted(.percent.rounded(rule: .down, increment: 1))                    // "501%"
Double(5.01).formatted(.percent.rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "501%"
Double(5.01).formatted(.percent.rounded(rule: .towardZero, increment: 1))              // "501%"
Double(5.01).formatted(.percent.rounded(rule: .up, increment: 1))                      // "501%"

Decimal(0.26575467567788).formatted(.percent.rounded(rule: .awayFromZero))              // "26.575468%"
Decimal(0.00900999876871).formatted(.percent.rounded(rule: .awayFromZero))              // "0.901%"
Decimal(5.01).formatted(.percent.rounded(rule: .awayFromZero, increment: 1))            // "501%"
Decimal(5.01).formatted(.percent.rounded(rule: .awayFromZero, increment: 10))           // "510%"
Decimal(0.01).formatted(.percent.rounded(rule: .down))                                  // "1%"
Decimal(0.01).formatted(.percent.rounded(rule: .toNearestOrAwayFromZero))               // "1%"
Decimal(0.01).formatted(.percent.rounded(rule: .towardZero))                            // "1%"
Decimal(0.01).formatted(.percent.rounded(rule: .up))                                    // "1%"
Decimal(5.01).formatted(.percent.rounded(rule: .down, increment: 1))                    // "500%"
Decimal(5.01).formatted(.percent.rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "501%"
Decimal(5.01).formatted(.percent.rounded(rule: .towardZero, increment: 1))              // "500%"
Decimal(5.01).formatted(.percent.rounded(rule: .up, increment: 1))                      // "501%"

Sign

Controls the visibility of the negative and positive sign.

Sign Display Strategy Description
.automatic Displays the negative sign (-) when the number is negative. Positive sign isn’t shown
.never Never shows the positive (+) or negative (-) signs
.always(includingZero:) Passing in true will show the positive sign on a 0 value
Float(1.90).formatted(.percent.sign(strategy: .never))                      // "189.999998%"
Float(-1.90).formatted(.percent.sign(strategy: .never))                     // "189.999998%"
Float(1.90).formatted(.percent.sign(strategy: .automatic))                  // "189.999998%"
Float(1.90).formatted(.percent.sign(strategy: .always()))                   // "+189.999998%"
Float(0).formatted(.percent.sign(strategy: .always(includingZero: true)))   // "+0%"
Float(0).formatted(.percent.sign(strategy: .always(includingZero: false)))  // "0%"

Decimal Separator

Controls the visibility of the decimal separator.

Decimal Separator Display Strategy Descriotion
.automatic Only shows the decimal separator on fractional values
.always Always shows the decimal separator
Float(10).formatted(.percent.decimalSeparator(strategy: .automatic))    // "1,000%"
Float(10).formatted(.percent.decimalSeparator(strategy: .always))       // "1,000.%"

Grouping

Controls if the thousands units are grouped or not.

Grouping Descriotion
.never Never group thousands digits
.always Always group thousands digits
Float(1_000).formatted(.percent.grouping(.automatic))   // "100,000%"
Float(1_000).formatted(.percent.grouping(.never))       // "100000%"

Precision

There are seven options to set the precision of the output.

Precision Option Description
.significantDigits(Int) Sets a fixed number of significant digits to show
.significantDigits(Range) Sets a range of significant digits to show
.fractionLength(Int) Sets the number digits after the decimal separator
.fractionLength(Range) Sets a range of digits to show after the decimal separator
.integerLength(Int) Sets the number of digits to show before the decimal separator
.integerLength(Range) Sets a range of digits to show before the decimal separator
.integerAndFractionLength(integer:fraction:) Sets both the integer and fractional digits to display
Decimal(10.1).formatted(.percent.precision(.significantDigits(1))) // "1,000%"
Decimal(10.1).formatted(.percent.precision(.significantDigits(2))) // "1,000%"
Decimal(10.1).formatted(.percent.precision(.significantDigits(3))) // "1,010%"
Decimal(10.1).formatted(.percent.precision(.significantDigits(4))) // "1,010%"
Decimal(10.1).formatted(.percent.precision(.significantDigits(5))) // "1,010.0%"

Decimal(1).formatted(.percent.precision(.significantDigits(1 ... 3)))       // "100%"
Decimal(10).formatted(.percent.precision(.significantDigits(1 ... 3)))      // "1,000%"
Decimal(10.1).formatted(.percent.precision(.significantDigits(1 ... 3)))    // "1,010%"
Decimal(10.01).formatted(.percent.precision(.significantDigits(1 ... 3)))   // "1,000%"

Decimal(0.0001).formatted(.percent.precision(.fractionLength(1))) // 0.0%
Decimal(0.0001).formatted(.percent.precision(.fractionLength(2))) // 0.01%
Decimal(0.0001).formatted(.percent.precision(.fractionLength(3))) // 0.010%

Decimal(0.0001).formatted(.percent.precision(.fractionLength(0...1)))    // 0%
Decimal(0.0001).formatted(.percent.precision(.fractionLength(0...2)))    // 0.01%
Decimal(0.0001).formatted(.percent.precision(.fractionLength(0...3)))    // 0.01%
Decimal(0.0001).formatted(.percent.precision(.fractionLength(0...4)))    // 0.01%

Decimal(10.111).formatted(.percent.precision(.integerLength(1))) // 1.1%
Decimal(10.111).formatted(.percent.precision(.integerLength(2))) // 11.1%

Decimal(10.111).formatted(.percent.precision(.integerLength(0...1))) // 1.1%
Decimal(10.111).formatted(.percent.precision(.integerLength(0...2))) // 11.1%
Decimal(10.111).formatted(.percent.precision(.integerLength(0...3))) // 11.1%

Decimal(10.111).formatted(.percent.precision(.integerAndFractionLength(integer: 1, fraction: 1))) // 1.1%
Decimal(10.111).formatted(.percent.precision(.integerAndFractionLength(integer: 2, fraction: 1))) // 11.1%
Decimal(10.111).formatted(.percent.precision(.integerAndFractionLength(integer: 2, fraction: 2))) // 11.10%
Decimal(10.111).formatted(.percent.precision(.integerAndFractionLength(integer: 2, fraction: 3))) // 11.100%

Notation

Controls the ability to use different notation styles.

Notation Setting Description
.automatic Used primarily when compositing other styles, will choose the best notation setting for the given settings.
.compactName Uses the compact notation for the thousands styles
.scientific Uses scientific notation
Float(1_000).formatted(.percent.notation(.automatic))   // "100,000%"
Float(1_000).formatted(.percent.notation(.compactName)) // "100K%"
Float(1_000).formatted(.percent.notation(.scientific))  // "1E5%"

Scale

Controls the scale of the number.

Float(10).formatted(.percent.scale(1.0))    // "10%"
Float(10).formatted(.percent.scale(1.5))    // "15%"
Float(10).formatted(.percent.scale(2.0))    // "20%"
Float(10).formatted(.percent.scale(-2.0))   // "-20%"

Setting the Locale

Controls the locale of the output.

Float(1_000).formatted(.percent.grouping(.automatic).locale(Locale(identifier: "fr_FR")))   // "100 000 %"
Float(1_000).formatted(.percent.grouping(.never).locale(Locale(identifier: "fr_FR")))       // "100000 %"

Float(1_000).formatted(.percent.notation(.automatic).locale(Locale(identifier: "fr_FR")))   // "100 000 %"
Float(1_000).formatted(.percent.notation(.compactName).locale(Locale(identifier: "fr_FR"))) // "100 k %"
Float(1_000).formatted(.percent.notation(.scientific).locale(Locale(identifier: "fr_FR")))  // "1E5 %"

Localizing Number Systems

In cases where a given Locale has multiple number systems available, numeric format styles will default to using the number system which matches the your system’s Locale.current value. You’re able to explicitly set the number system for the Format Style to use by initializing a new Locale with the number system set using the BCP-47 or ICU Identifiers:

let englishArabicBCP47 = "en-u-nu-arab"
let enArabBCP47 = Locale(identifier: englishArabicBCP47)
123456.formatted(.number.locale(enArabBCP47)) // "١٢٣٬٤٥٦"
Date.now.formatted(.dateTime.year().month().day().locale(enArabBCP47)) // "Sep ٢٣, ٢٠٢٣"

ICU

let englishArabicICU = "en@numbers=arab"
let enArabICU = Locale(identifier: "en@numbers=arab")
12345.formatted(.number.locale(enArabICU)) // "١٢٬٣٤٥"
Date.now.formatted(.dateTime.year().month().day().locale(enArabICU)) // "Sep ٢٣, ٢٠٢٣"

Compositing

Any of the above styles can be combined to fully customize the output.

Float(10).formatted(.percent.scale(200.0).notation(.compactName).grouping(.automatic)) // "2K%"

Attributed String Output

Outputs and AttributedString instead of a String.

Float(10).formatted(.percent.scale(200.0).notation(.compactName).grouping(.automatic).attributed)

Parsing Percentages From Strings

Percentages parsed as Integers will be a value from 0 - 100, while percentages parsed as floating point or decimal values will be 0.0 - 1.0.

Percentage strings can be parsed into any of Swift’s built-in numeric types.

try? Int("98%", format: .percent) // 98
try? Float("95%", format: .percent) // 0.95
try? Decimal("95%", format: .percent) // 0.95

Currency Style Xcode 13+

Output number values in the local currency.

The currency format style is very similar to the Number and Percent format styles and works with Swift’s numerical types (Float, Double, Decimal, and Integer).

The key difference is that you will need to pass in the ISO 4217 country code for the currency you would like to display.

Because accuracy can’t be guaranteed, never use floating point numbers (Float and Double) to store and do calculations on important values like money. Either store cents as Integers, or use Decimal values.

The easiest and best way to access this style is through the .currency(code:) extension on FormatStyle. From there, you can use method chaining to customize the output.

10.formatted(.currency(code: "JPY")) // "10%"

You can also initialize an instance of IntegerFormatStyle<Value: BinaryInteger>.Percent, FloatingPointFormatStyle<BinaryFloatingPoint>.Percent or Decimal.FormatStyle.Percent and use method chaining to customize the output.

FloatingPointFormatStyle<Double>.Currency(code: "JPY").rounded(rule: .up, increment: 1).format(10.9) // ¥11"
IntegerFormatStyle<Int>.Currency(code: "GBP").presentation(.fullName).format(42) // "42.00 British pounds"
Decimal.FormatStyle.Currency(code: "USD").scale(12).format(0.1) // "$1.20"

Available Properties

Property Description
Rounding Customize the rounding behaviour
Sign Do you want to show or hide the + or - sign?
Decimal Separator Do you want to show or hide the decimal separator
Grouping How do you want the thousands numbers to be grouped
Precision How many fractional or significant digits do you want to show
Presentation Controls the style of the displayed currency
Scale Scale the number up or down before display
Locale Set the Locale for one output
Compositing Mix and match any and all of the above
AttributedString output Output an AttributedString

Rounding

At its simplest, you can call the .formatted(.number.rounded()) method on any number type (Float, Double, Decimal, or Integer) in order to get the system’s default rounding behaviour.

Decimal(0.59).formatted(.currency(code: "GBP").rounded())   // "£0.59"
Decimal(0.599).formatted(.currency(code: "GBP").rounded())  // "£0.60"
Decimal(0.5999).formatted(.currency(code: "GBP").rounded()) // "£0.60"

Using the full instance method, you can access more granular settings: .number.rounded(rule:increment:).

Rounding Rule Description
.awayFromZero Round to the closest allowed value whose magnitude is greater than or equal to that of the source.
.down Round to the closest allowed value that is less than or equal to the source.
.toNearestOrAwayFromZero Round to the closest allowed value; if two values are equally close, the one with greater
.toNearestOrEven Round to the closest allowed value; if two values are equally close, the even one is chosen.
.towardZero Round to the closest allowed value whose magnitude is less than or equal to that of the source.
.up Round to the closest allowed value that is greater than or equal to the source.

The increment: parameter is a Double and tells the system under the hood what to round the value by.

Decimal(0.59).formatted(.currency(code: "GBP").rounded())   // "£0.59"
Decimal(0.599).formatted(.currency(code: "GBP").rounded())  // "£0.60"
Decimal(0.5999).formatted(.currency(code: "GBP").rounded()) // "£0.60"

Decimal(5.001).formatted(.currency(code: "GBP").rounded(rule: .awayFromZero)) // "£5.01"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .awayFromZero))  // "£5.01"

Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .awayFromZero, increment: 1))  // "£6"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .awayFromZero, increment: 10)) // "£10"

Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .down))                    // "£5.00"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .toNearestOrAwayFromZero)) // "£5.01"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .towardZero))              // "£5.00"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .up))                      // "£5.01"

Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .down, increment: 1)) // "£5"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .toNearestOrAwayFromZero, increment: 1)) // "£5"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .towardZero, increment: 1)) // "£5"
Decimal(5.01).formatted(.currency(code: "GBP").rounded(rule: .up, increment: 1)) // "£5"

Sign

Controls the visibility of the negative and positive sign.

Sign Display Strategy Description
.automatic Automatically desides which strategy to use
.never Never shows the positive (+) or negative (-) signs
.always() Always shows the positive (+) or negative (-) signs
.always(showsZero:) Accepts a Bool, and controls if a 0 value gets a positive (+) sign
.accountingAlways() Uses the standardized account style for numbers
.accountingAlways(showsZero) Accepts a Bool, and controls if a 0 value gets a positive (+) sign
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .automatic))                         // "£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .never))                             // "£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .accounting))                        // "£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .accountingAlways()))                // "+£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .accountingAlways(showZero: true)))  // "+£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .accountingAlways(showZero: false))) // "+£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .always()))                          // "+£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .always(showZero: true)))            // "+£7.00"
Decimal(7).formatted(.currency(code: "GBP").sign(strategy: .always(showZero: false)))           // "+£7.00"

Decimal Separator

Controls the visibility of the decimal separator.

Decimal Separator Display Strategy Descriotion
.automatic Only shows the decimal separator on fractional values
.always Always shows the decimal separator
Decimal(3000).formatted(.currency(code: "GBP").decimalSeparator(strategy: .automatic)) // "£3,000.00"
Decimal(3000).formatted(.currency(code: "GBP").decimalSeparator(strategy: .always))    // "£3,000.00"

Grouping

Controls if the thousands units are grouped or not.

Grouping Descriotion
.never Never group thousands digits
.always Always group thousands digits
Int(3_000).formatted(.currency(code: "GBP").grouping(.never))     // "£3000.00"
Int(3_000).formatted(.currency(code: "GBP").grouping(.automatic)) // "£3,000.00"

Precision

There are seven options to set the precision of the output.

Precision Option Description
.significantDigits(Int) Sets a fixed number of significant digits to show
.significantDigits(Range) Sets a range of significant digits to show
.fractionLength(Int) Sets the number digits after the decimal separator
.fractionLength(Range) Sets a range of digits to show after the decimal separator
.integerLength(Int) Sets the number of digits to show before the decimal separator
.integerLength(Range) Sets a range of digits to show before the decimal separator
.integerAndFractionLength(integer:fraction:) Sets both the integer and fractional digits to display
// Please don't use Floating point numbers to store currency. Please.
Float(3_000.003).formatted(.currency(code: "GBP").precision(.fractionLength(4))) // "£3,000.0029" <- This is why
Float(3_000.003).formatted(.currency(code: "GBP").precision(.fractionLength(1 ... 4))) // "£3,000.0029"

Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.fractionLength(4)))       // "£3,000.0029"
Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.fractionLength(1 ... 4))) // "£3,000.0029"

Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.integerLength(3))) // "£000.00"
Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.integerLength(4))) // "£3,000.00"
Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.integerLength(5))) // "£03,000.00"

Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.integerLength(0...3))) // "£.00"
Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.integerLength(0...4))) // "£3,000.00"
Decimal(3_000.003).formatted(.currency(code: "GBP").precision(.integerLength(0...5))) // "£03,000.00"

Decimal(3).formatted(.currency(code: "GBP").precision(.integerAndFractionLength(integer: 4, fraction: 4))) // "£0,003.0000"
Decimal(3).formatted(
    .currency(code: "GBP")
    .precision(.integerAndFractionLength(integerLimits: 1 ... 5, fractionLimits: 1 ... 5))
) // "£3.0"
Decimal(3.00004).formatted(
    .currency(code: "GBP")
    .precision(.integerAndFractionLength(integerLimits: 1 ... 5, fractionLimits: 1 ... 5))
) // "£3.00004"
Decimal(3.000000004).formatted(
    .currency(code: "GBP")
    .precision(.integerAndFractionLength(integerLimits: 1 ... 5, fractionLimits: 1 ... 5))
)
Decimal(30000.01).formatted(
    .currency(code: "GBP")
    .precision(.integerAndFractionLength(integerLimits: 1 ... 5, fractionLimits: 1 ... 5))
) // "£30,000.01"
Decimal(3000000.000001).formatted(
    .currency(code: "GBP")
    .precision(.integerAndFractionLength(integerLimits: 1 ... 5, fractionLimits: 1 ... 5))
) // "£0.0"

Decimal(10.1).formatted(.currency(code: "GBP").precision(.significantDigits(1))) // "£10"
Decimal(10.1).formatted(.currency(code: "GBP").precision(.significantDigits(2))) // "£10"
Decimal(10.1).formatted(.currency(code: "GBP").precision(.significantDigits(3))) // "£10.1"
Decimal(10.1).formatted(.currency(code: "GBP").precision(.significantDigits(4))) // "£10.10"
Decimal(10.1).formatted(.currency(code: "GBP").precision(.significantDigits(5))) // "£10.100"

Decimal(1).formatted(.currency(code: "GBP").precision(.significantDigits(1 ... 3)))     // "£1"
Decimal(10).formatted(.currency(code: "GBP").precision(.significantDigits(1 ... 3)))    // "£10"
Decimal(10.1).formatted(.currency(code: "GBP").precision(.significantDigits(1 ... 3)))  // "£10.1"
Decimal(10.01).formatted(.currency(code: "GBP").precision(.significantDigits(1 ... 3))) // "£10"

Presentation

Controls how verbose the currency display is when being presented

Presentation Setting Description
.fullName Writes out the currency value in full
.isoCode Uses the ISO 4217 currency code for display
.narrow Fits the string in the smallest horizontal space possible
.standard The default output style
Decimal(10).formatted(.currency(code: "GBP").presentation(.fullName)) // "10.00 British pounds"
Decimal(10).formatted(.currency(code: "GBP").presentation(.isoCode))  // "GBP 10.00"
Decimal(10).formatted(.currency(code: "GBP").presentation(.narrow))   // "£10.00"
Decimal(10).formatted(.currency(code: "GBP").presentation(.standard)) // "£10.00"

Scale

Controls the scale of the number.

Decimal(10).formatted(.currency(code: "GBP").scale(1))    // "£10.00"
Decimal(10).formatted(.currency(code: "GBP").scale(1.5))  // "£15.00"
Decimal(10).formatted(.currency(code: "GBP").scale(-1.5)) // "-£15.00"
Decimal(10).formatted(.currency(code: "GBP").scale(10))   // "£100.00"

Setting the Locale

Controls the locale of the output.

Decimal(10).formatted(.currency(code: "GBP").presentation(.fullName).locale(Locale(identifier: "fr_FR"))) // "10,00 livres sterling"
Decimal(10000000).formatted(.currency(code: "GBP").locale(Locale(identifier: "hi_IN"))) // "£1,00,00,000.00

Compositing

Any of the above styles can be combined to fully customize the output.

Decimal(10).formatted(.currency(code: "GBP").scale(200.0).sign(strategy: .always()).presentation(.fullName)) // "+2,000.00 British pounds"

Attributed String Output

Outputs and AttributedString instead of a String.

Decimal(10).formatted(.currency(code: "GBP").scale(200.0).sign(strategy: .always()).presentation(.fullName).attributed)

Parsing Currencies From Strings

Due to rounding issues, you should never use floating point types (Double, Float) to store currency values. Use Decimal instead.
try? Decimal("$100.25", format: .currency(code: "USD")) // 100.25
try? Decimal("100.25 British Points", format: .currency(code: "GBP")) // 100.25