You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将含Unicode普通分数的字符串转换为CGFloat

问题描述

我有一个可能包含任意内容的字符串,例如"1"、"1½"或"one."。如果该字符串是数字,我希望将其转换为CGFloat。对于"1"这类简单情况,用NumberFormatter就能实现:

if let newFloat = NumberFormatter().number(from: string) {
    let float = CGFloat(truncating: newFloat)
    print(float)
}

但这段代码处理不了"½"这类普通分数。我写了一个String扩展来把普通分数替换成小数,但有两个问题:一是实现不够优雅,二是没法正确处理"1⅓"这类带整数的分数,会输出"10.333"。以下是我的扩展代码,求更优实现方式:

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
        }
    }
}
优化实现方案

核心思路

  1. 优先识别整数+分数的组合(如1⅓),拆分后计算总和再替换
  2. 单独的分数直接映射为小数
  3. 用字典替代冗长的switch分支,简化维护
  4. 一次性批量替换所有匹配项,避免循环处理的低效

优化后的代码

extension String {
    // 普通分数与小数的映射字典,新增分数只需添加键值对
    private static let vulgarFractionMap: [Character: Double] = [
        "½": 0.5,
        "⅓": 1/3,
        "⅔": 2/3,
        "¼": 0.25,
        "¾": 0.75,
        "⅕": 0.2,
        "⅖": 0.4,
        "⅗": 0.6,
        "⅘": 0.8,
        "⅙": 1/6,
        "⅚": 5/6,
        "⅐": 1/7,
        "⅛": 1/8,
        "⅜": 3/8,
        "⅝": 5/8,
        "⅞": 7/8
    ]
    
    func convertVulgarFractionsToDecimal() -> String {
        var result = self
        
        // 处理带整数的分数:匹配「数字+分数」格式
        let integerFractionRegex = "(\\d+)([½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞])"
        result = result.replacingOccurrences(
            of: integerFractionRegex,
            with: { match in
                guard let integerPart = Double(match.1),
                      let fractionValue = String.vulgarFractionMap[Character(match.2)] else {
                    return match.0 // 匹配失败则返回原内容
                }
                let total = integerPart + fractionValue
                return String(format: "%.6f", total).trimmingTrailingZeros()
            },
            options: .regularExpression
        )
        
        // 处理单独的分数
        let singleFractionRegex = "([½⅓⅔¼¾⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞])"
        result = result.replacingOccurrences(
            of: singleFractionRegex,
            with: { match in
                guard let fractionValue = String.vulgarFractionMap[Character(match.1)] else {
                    return match.0
                }
                return String(format: "%.6f", fractionValue).trimmingTrailingZeros()
            },
            options: .regularExpression
        )
        
        return result
    }
    
    // 辅助方法:移除小数末尾多余的0和小数点
    private func trimmingTrailingZeros() -> String {
        let trimmed = self.replacingOccurrences(of: "\\.?0+$", with: "", options: .regularExpression)
        return trimmed.isEmpty ? "0" : trimmed
    }
}

// 使用示例
let testStrings = ["1", "½", "1⅓", "2¾", "⅝", "one."]
for str in testStrings {
    let converted = str.convertVulgarFractionsToDecimal()
    if let number = NumberFormatter().number(from: converted),
       let cgFloat = CGFloat(exactly: number) {
        print("\(str) -> \(cgFloat)")
    } else {
        print("\(str) 无法转换为CGFloat")
    }
}

优化点说明

  • 字典映射替代switch:用vulgarFractionMap存储分数与对应值,代码更简洁,新增分数只需添加键值对即可
  • 正则匹配整数+分数:通过正则(\\d+)([分数字符])精准匹配带整数的分数,计算总和后替换,彻底解决1⅓变成10.333的问题
  • 批量替换提升效率:利用replacingOccurrences的闭包版本一次性处理所有匹配项,无需循环遍历字符串
  • 美化小数格式:trimmingTrailingZeros方法自动移除末尾多余的0和小数点,输出更简洁的小数格式

内容的提问来源于stack exchange,提问作者Moses Harding

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:30:03