Blog

# Numeric string representations

In this post, we’ll create string representations of numeric types in Swift and extend these types with convenience functionality to make them easier to use.

## The basics

When you create a string from a serializable type, you can use `String(format:,)` to provide rules for how you want the string to be formatted. Different formats apply to different value types.

For instance, to create a string of a `Double` and use two decimals, you would have to write something like:

``````let value = 1.2345
let result = String(format: "%0.2f")    // => 1.23
``````

While this is easy, it’s pretty hard to remember certain rules. In my opinion, it’s also nasty to scatter magic formatting strings all over the code base.

## Extending numeric types

To make this specific task of serializing a decimal value with two decimals easier, we could create extensions for the numeric types that we want to support:

``````public extension CGFloat {

func string(withDecimals decimals: Int) -> String {
String(format: .decimals(decimals), self)
}
}

public extension Double {

func string(withDecimals decimals: Int) -> String {
String(format: .decimals(decimals), self)
}
}

public extension Float {

func string(withDecimals decimals: Int) -> String {
String(format: .decimals(decimals), self)
}
}

private extension String {

static func decimals(_ decimals: Int) -> String { "%0.\(decimals)f" }
}

``````

While this works, it’s repeating the same code over and over. We can do better.

## Creating a shared extension

If we look at `String(format:,)`, we can see that it takes a list of `CVarArg` arguments. It turns out that this is a protocol that is implemented by all the types above.

We could thus make the extension above more general:

``````public extension CVarArg {

func string(withDecimals decimals: Int) -> String {
String(format: "%0.\(decimals)f", self)
}
}
``````

This extension will apply to all the types above, and thus only require us to have a single extension for all types. Nice…

…or not that nice, because `CVarArg` is implemented by a bunch of types, where “decimals” doesn’t make sense. For instance, with the exstension above, we could do this:

``````let string = "Hello, world!"
let result = string.string(withDecimals: 2)
``````

While this is exciting and wild, it just doesn’t make sense. We need to restrict this somehow.

We can do this by introducing a new protocol

``````public protocol NumericStringRepresentable: CVarArg {}
``````

then let the numeric types we want to support implement this protocol

``````extension CGFloat: NumericStringRepresentable {}
extension Double: NumericStringRepresentable {}
extension Float: NumericStringRepresentable {}
``````

and then apply the extension to this protocol instead of `CVarArg`

``````public extension NumericStringRepresentable {

func string(withDecimals decimals: Int) -> String {
String(format: "%0.\(decimals)f", self)
}
}
``````

We have now isolated the extension to the types that explictly implement `NumericStringRepresentable`.

## Source code

I have added this extension to my SwiftKit library. You can find the source code here and the unit tests here.