This code was inspired by this question: Number to Words
import Foundation
extension Int {
func plainEnglish (negativeSign: String = "negative") -> String {
func singleNumberName (number: Int) -> String {
switch (number) {
case 1: return "one"
case 2: return "two"
case 3: return "three"
case 4: return "four"
case 5: return "five"
case 6: return "six"
case 7: return "seven"
case 8: return "eight"
case 9: return "nine"
default:return ""
}
}
func tensNumberName (number: Int) -> String {
switch (number) {
case 2: return "twenty"
case 3: return "thirty"
case 4: return "fourty"
case 5: return "fifty"
case 6: return "sixty"
case 7: return "seventy"
case 8: return "eighty"
case 9: return "ninety"
default:return ""
}
}
func teensNumberName (number: Int) -> String {
switch (number) {
case 0: return "ten"
case 1: return "eleven"
case 2: return "twelve"
case 3: return "thirteen"
case 5: return "fifteen"
default:return singleNumberName(number) + "teen"
}
}
var number = self
var digits: [Int] = []
while number != 0 {
digits.append(abs(number%10))
number /= 10
}
var plainEnglish: [String] = []
var isTeen = false
let totalPlaces = digits.count
var currentPlace = 0
for (index, digit) in enumerate(digits.reverse()) {
currentPlace = totalPlaces - index
if currentPlace % 3 == 0 && digit > 0 {
plainEnglish.append(singleNumberName(digit) + " hundred")
} else if (currentPlace + 1) % 3 == 0 {
if digit == 1 {
isTeen = true
continue;
} else {
isTeen = false
plainEnglish.append(tensNumberName(digit))
}
} else {
if isTeen {
plainEnglish.append(teensNumberName(digit))
} else {
plainEnglish.append(singleNumberName(digit))
}
}
if currentPlace == 4 {
plainEnglish.append("thousand")
} else if currentPlace == 7 {
plainEnglish.append("million")
} else if currentPlace == 10 {
plainEnglish.append("billion")
}
}
let initial = (self < 0) ? negativeSign : ""
func combine (first: String, second: String) -> String {
return first + " " + second
}
let finalString = plainEnglish.reduce(initial, combine: combine)
return finalString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
}
}
This is implemented as an extension on the Int
data type, so you can use it via dot notation on any integer (including literals). Example usage:
let payamount = 98327
let dollars = payamount / 100
let cents = payamount % 100
let checkString = dollars.plainEnglish() + " dollars and " + cents.plainEnglish() + " cents"
Which results in a string that reads:
"nine hundred eighty three dollars and twenty seven cents"
4 Answers 4
So Apple already has an implementation for this, using NSNumberFormatter
. A bit easier to implement than what you have now ;)
.
var numberFormatter:NSNumberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.SpellOutStyle
var string = numberFormatter.stringFromNumber(100)
-
2\$\begingroup\$ You can drop the
:NSNumberFormatter
from line 1 and dropNSNumberFormatterStyle
from line 2. \$\endgroup\$vacawama– vacawama2014年08月09日 21:17:56 +00:00Commented Aug 9, 2014 at 21:17 -
2\$\begingroup\$ If you only need to translate one, you can use this one-liner:
var string = NSNumberFormatter.localizedStringFromNumber(100, numberStyle: .SpellOutStyle)
\$\endgroup\$vacawama– vacawama2014年08月09日 21:28:22 +00:00Commented Aug 9, 2014 at 21:28
if currentPlace == 4 {
plainEnglish.append("thousand")
} else if currentPlace == 7 {
plainEnglish.append("million")
} else if currentPlace == 10 {
plainEnglish.append("billion")
}
First of all, this doesn't cover all of the cases. Swift Int
s are 64 bit, so you need to cover everything up to "quintillion".
Second of all, why aren't we using a function for this like we used for other names?
Let's make a function that returns an optional string:
func bigNumberName (place: Int) -> String? {
switch (place) {
case 4: return "thousand"
case 7: return "million"
case 10:return "billion"
case 13:return "trillion"
case 16:return "quadrillion"
case 19:return "quintillion"
default:return nil
}
}
And now let's replace that if-else
structure that needed 3 more branches to it with a single if
:
if let bigName = bigNumberName(currentPlace) {
plainEnglish.append(bigName)
}
The same logic could and probably should be applied to the singleNumberName
and tensNumberName
functions, where they return nil
rather than empty strings for the default case. Doing this will probably help clean up the forin
loop a little bit.
Seems fine to me. Here's some thoughts:
- You will need to put some sort of limit on the int, or provide all the words (thousand, million, billion, etc) up to the int's natural limit. Or return "A really big number greater than a quadrillion" or something like that if there's no limit.
- With very similar code, you can add an ordinal number output: "First, second, three-hundred and twenty-first"
- You can easily add "negative" or "minus" to the test suite
-
\$\begingroup\$ I don't understand your third bullet point. As for your first one, the original code posted would've covered everything that fits in a 32-bit integer, which I assumed was what Swift ints are, but they are actually 64-bit integers, so we need to cover up to quintillion. \$\endgroup\$nhgrif– nhgrif2014年08月03日 14:10:11 +00:00Commented Aug 3, 2014 at 14:10
-
\$\begingroup\$ Oh sorry, I meant add it to the test suite \$\endgroup\$Carlos– Carlos2014年08月03日 14:12:26 +00:00Commented Aug 3, 2014 at 14:12
-
\$\begingroup\$ You mean in my "example usage" I provided below? \$\endgroup\$nhgrif– nhgrif2014年08月03日 14:14:05 +00:00Commented Aug 3, 2014 at 14:14
-
\$\begingroup\$ Yeah, that's right. I like to make sure the tests/examples cover any possibilities. \$\endgroup\$Carlos– Carlos2014年08月03日 14:15:07 +00:00Commented Aug 3, 2014 at 14:15
-
1\$\begingroup\$ Ah okay. I tested these things--just didn't include examples. \$\endgroup\$nhgrif– nhgrif2014年08月03日 14:16:04 +00:00Commented Aug 3, 2014 at 14:16
return finalString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
This is ugly. And it only exists because if the Int is not negative, you end up with a space at the front of the string because of the way your combine
works.
What's more, because of the way combine
works, if you end up with a 0 in the hundreds place (for example, 1,034
or 2,044,562
), you end up with a double space there and stringByTrimming...
doesn't fix the embedded double space. So let's improve the combine
function.
func combine (first: String, second: String) -> String {
let joiner = (first.utf16Count > 0 && second.utf16Count > 0) ? " " : ""
return first + joiner + second
}
Now the last few lines look like this and never produce and unnecessary spaces:
let initial = (self < 0) ? negativeSign : ""
func combine (first: String, second: String) -> String {
let joiner = (first.utf16Count > 0 && second.utf16Count > 0) ? " " : ""
return first + joiner + second
}
let finalString = plainEnglish.reduce(initial, combine: combine)
return finalString
Explore related questions
See similar questions with these tags.
negativeSign
argument for anything. Near the bottom of the function (let initial = ...
), you just have a hard-coded"negative"
. (By the waynegativePrefix
might be a more precise name for the argument) \$\endgroup\$let finalString ...
line. I messed it up when I moved it out of there. \$\endgroup\$