Convert numbers to Roman Numerials in Swift

Roman numerals, from ancient Rome are a way of writing numbers using these letters from the latin alphabet [I, V, X, L, C, D and M]. Each letter has a fixed integer value and can be combined to represent any number from 1 to 3999. This article demonstrates how to convert a integer to the Roman Numeral representation.

Roman numerals are composed of combinations of the following seven letters that are combined to represent any number from 1 to 3999.


Roman numeral building blocks

Letter Value Name
I 1 unus
V 5 quinque
X 10 decem
L 50 quinquaginta
C 100 centum
D 500 quingenti
M 1000 mille


Repeating characters

Roman numerals are composed of repeating the base 10 characters up to 3 times. Such as 1, 2, 3 are repeats of the unus symbol 1, 2 and 3 times respectively. The repeating parameter is used to create a string with the appropriate number of the character.

1String(repeating: "I", count:1)
2// I
3
4String(repeating: "I", count:2)
5// II
6
7String(repeating: "I", count:3)
8// III


Treat 4 and 9 as their own character set

The key to programatically converting a number to Roman Numerals is to treat the 4, 9 and equivalent characters as their own digit. Then start with integer division from the highest to the lowest number, when the division result is greater than zero, that result is the number of the equivalent character in Roman numerals. The remainder from the division is fed into the next round of division by the next highest number.

Programatic building blocks for Roman numerals

Letter Value
I 1
IV 4
V 5
IX 9
X 10
XL 40
L 50
XC 90
C 100
CD 400
D 500
CM 900
M 1000

Take an example of the number 7

Programatic building blocks for Roman numerals

Letter Value 6 division remainder
X 10 0 -
IX 9 0 -
V 5 1 2

Roman Numeral is composed of 1 V. Remainder 2 is used with the next highest number (4).

Programatic building blocks for Roman numerals

Letter Value 2 division remainder
IV 4 0 2
I 1 2 0

Roman Numeral is composed of 2 I
Result is VII



Swift Code

Create a Converter struct with a static function to convert numeric values to Roman Numerals. A guard statement is used to ensure the value is between 1 and 3999. It is important that these are ordered from highest to lowest so the highest numbers are converted first and then removed. This process is repeated with the remainder and the next highest number and so on down to the number 1 and the letter "I". The zip function is used to create a sequence of tuples from a list of Arabic numbers and a list of Roman numeral symbols.

 1struct Converter {
 2    
 3    static func romanNumeralFor(_ n:Int) -> String {
 4        guard n > 0 && n < 4000 else {
 5            return "Number must be between 1 and 3999"
 6        }
 7        
 8        var returnString = ""
 9        let arabicNumbers = [1000,  900, 500,  400, 100,   90,  50,   40,  10,    9,   5,    4,  1]
10        let romanLetters  = [ "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
11
12        var num = n
13        for (ar, rome) in zip(arabicNumbers, romanLetters) {
14            let repeats = num / ar
15            returnString += String(repeating: rome, count: repeats)
16            num = num % ar
17        }
18        return returnString
19    }
20}


App ViewModel

Create a ConverterViewModel to call the Converter and set variables for the Roman and Arabic numbers as well as an error message.

 1class ConverterViewModel: ObservableObject {
 2    @Published var roman: String
 3    @Published var arabic: String
 4    @Published var error: String
 5
 6    init() {
 7        roman = "I"
 8        arabic = "1"
 9        error = ""
10    }
11    
12    func convertNumber() {
13        error = ""
14        if let n = Int(arabic) {
15            guard n > 0 && n < 4000 else {
16                roman = ""
17                error = "Number must be between 1 and 3999"
18                return
19            }
20            roman = Converter.romanNumeralFor(n)
21        } else {
22            roman = ""
23            error = "'\(arabic)' is not a valid number"
24        }
25    }
26}


SwiftUI View

Create a SwiftUI view consisting of a form and an action button to call the conversion. A TextField is used to allow the user to enter a number to convert and the Roman Numerals are displayed in a Text view.

 1struct RomanNumeralsView: View {
 2    @StateObject private var vm = ConverterViewModel()
 3
 4    var body: some View {
 5        VStack {
 6            Text("Roman Numerals")
 7                .font(.title)
 8                .padding()
 9            
10            Form {
11                Section(header: Text("Numver to convert"),
12                        footer: Text("\(vm.error)").foregroundColor(.red)) {
13                    TextField("Number", text: $vm.arabic)
14                }
15
16                Section(header: Text("Roman Numerals")) {
17                    Text("\(vm.roman)")
18                        .font(.title)
19                }
20            }
21            .frame(width: 350, height: 250, alignment: .center)
22            .padding()
23            
24            Button("Convert") {
25                vm.convertNumber()
26            }
27            .buttonStyle(ActionButtonStyle())
28
29            Spacer()
30        }
31    }
32}

Roman Numerals for year 2021 in SwiftUI app
Roman Numerals for year 2021 in SwiftUI app


Basic error handling is added to inform the user when invalid data has been entered, such as non-numeric data or numbers outside of the range 1 to 3999.

Error handling for invalid number or number out of range
Error handling for invalid number or number out of range




Conclusion

This article how to convert a number to Roman Numerals. The solution is to loop through a set of Roman Numeral symbols from the highest to lowest. For each symbol in the list; divide by the number and add this amount of the symbol to the result, continue the loop with the remainder. There are 7 symbols used [I, V, X, L, C, D and M], but it is easier to use additional symbol-pairs for the 4,9,40, etc. numbers [I, IV, V, IX, X, XL. L, XC, C, CD, D, CM and M], then each symbol can be treated the same.