I have a string that can contain arbitrary values; e.g. it might be "1", "1½", or it might be "one."
If it's a number, I want to convert it to a float. This is simple if it's just "1"
if let newFloat = NumberFormatter().number(from: string) {
let float = CGFloat(truncating: newFloat)
print(float)
}
But if it's "½," that code fails.
I made an extension to String that will find the 'vulgar fractions' and replace them with a decimal. It does work, but 1. it feels hacky, and 2. it doesn't account for partial fractions, meaning that the string "1⅓" outputs to "10.333." I've pasted it below - is there a better way to do this?
extension String {
func replaceVulgarFractions() -> String {
var modifiedText = self
let vulgarFractionRegex = "[½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]"
while let matchRange = modifiedText.range(of: vulgarFractionRegex, options: .regularExpression) {
let matchedFraction = String(modifiedText[matchRange])
if let decimalValue = matchedFraction.vulgarFractionToDecimal() {
modifiedText = modifiedText.replacingCharacters(in: matchRange, with: String(format: "%.3f", decimalValue))
}
}
return modifiedText
}
func vulgarFractionToDecimal() -> Double? {
switch self {
case "½": return 0.5
case "⅓": return 1.0 / 3.0
case "⅔": return 2.0 / 3.0
case "¼": return 0.25
case "¾": return 0.75
case "⅕": return 0.2
case "⅖": return 0.4
case "⅗": return 0.6
case "⅘": return 0.8
case "⅙": return 1.0 / 6.0
case "⅚": return 5.0 / 6.0
case "⅐": return 1.0 / 7.0
case "⅛": return 1.0 / 8.0
case "⅜": return 3.0 / 8.0
case "⅝": return 5.0 / 8.0
case "⅞": return 7.0 / 8.0
default: return nil
}
}
}
I don't see any built-in way to translate the vulgar fraction characters to decimals. One solution would be to fix your code to adjust for the leading 0
from the string format.
Update your while
loop with the following code:
while let matchRange = modifiedText.range(of: vulgarFractionRegex, options: .regularExpression) {
let matchedFraction = String(modifiedText[matchRange])
if let decimalValue = matchedFraction.vulgarFractionToDecimal() {
var fracStr = String(format: "%3.3f", decimalValue) // Your original format
// Remove the leading zero unless this is the first character
if fracStr.hasPrefix("0.") && matchRange.lowerBound != modifiedText.startIndex {
fracStr = String(fracStr.dropFirst())
}
modifiedText = modifiedText.replacingCharacters(in: matchRange, with: fracStr)
}
}
This code removes any leading zero from the result of the string format unless this is from the first character of the original string.
These changes will give you "1.5" for "1½" and "0.5" for "½".