Precision String Format Specifier In Swift

前端 未结 30 1978
面向向阳花
面向向阳花 2020-11-22 05:58

Below is how I would have previously truncated a float to two decimal places

NSLog(@\" %.02f %.02f %.02f\", r, g, b);

I checked the docs an

相关标签:
30条回答
  • 2020-11-22 06:10

    You can also create an operator in this way

    operator infix <- {}
    
    func <- (format: String, args:[CVarArg]) -> String {
        return String(format: format, arguments: args)
    }
    
    let str = "%d %.1f" <- [1453, 1.123]
    
    0 讨论(0)
  • 2020-11-22 06:10

    Swift2 example: Screen width of iOS device formatting the Float removing the decimal

    print(NSString(format: "Screen width = %.0f pixels", CGRectGetWidth(self.view.frame)))
    
    0 讨论(0)
  • 2020-11-22 06:12

    Details

    • Xcode 9.3, Swift 4.1
    • Xcode 10.2.1 (10E1001), Swift 5

    Solution 1

    func rounded() -> Double

    (5.2).rounded()
    // 5.0
    (5.5).rounded()
    // 6.0
    (-5.2).rounded()
    // -5.0
    (-5.5).rounded()
    // -6.0
    

    func rounded(_ rule: FloatingPointRoundingRule) -> Double

    let x = 6.5
    
    // Equivalent to the C 'round' function:
    print(x.rounded(.toNearestOrAwayFromZero))
    // Prints "7.0"
    
    // Equivalent to the C 'trunc' function:
    print(x.rounded(.towardZero))
    // Prints "6.0"
    
    // Equivalent to the C 'ceil' function:
    print(x.rounded(.up))
    // Prints "7.0"
    
    // Equivalent to the C 'floor' function:
    print(x.rounded(.down))
    // Prints "6.0"
    

    mutating func round()

    var x = 5.2
    x.round()
    // x == 5.0
    var y = 5.5
    y.round()
    // y == 6.0
    var z = -5.5
    z.round()
    // z == -6.0
    

    mutating func round(_ rule: FloatingPointRoundingRule)

    // Equivalent to the C 'round' function:
    var w = 6.5
    w.round(.toNearestOrAwayFromZero)
    // w == 7.0
    
    // Equivalent to the C 'trunc' function:
    var x = 6.5
    x.round(.towardZero)
    // x == 6.0
    
    // Equivalent to the C 'ceil' function:
    var y = 6.5
    y.round(.up)
    // y == 7.0
    
    // Equivalent to the C 'floor' function:
    var z = 6.5
    z.round(.down)
    // z == 6.0
    

    Solution 2

    extension Numeric {
    
        private func _precision(number: NSNumber, formatter: NumberFormatter) -> Self? {
            if  let formatedNumString = formatter.string(from: number),
                let formatedNum = formatter.number(from: formatedNumString) {
                    return formatedNum as? Self
            }
            return nil
        }
    
        private func toNSNumber() -> NSNumber? {
            if let num = self as? NSNumber { return num }
            guard let string = self as? String, let double = Double(string) else { return nil }
            return NSNumber(value: double)
        }
    
        func precision(_ minimumFractionDigits: Int,
                       roundingMode: NumberFormatter.RoundingMode = NumberFormatter.RoundingMode.halfUp) -> Self? {
            guard let number = toNSNumber() else { return nil }
            let formatter = NumberFormatter()
            formatter.minimumFractionDigits = minimumFractionDigits
            formatter.roundingMode = roundingMode
            return _precision(number: number, formatter: formatter)
        }
    
        func precision(with numberFormatter: NumberFormatter) -> String? {
            guard let number = toNSNumber() else { return nil }
            return numberFormatter.string(from: number)
        }
    }
    

    Usage

    _ = 123.44.precision(2)
    _ = 123.44.precision(3, roundingMode: .up)
    
    let numberFormatter = NumberFormatter()
    numberFormatter.minimumFractionDigits = 1
    numberFormatter.groupingSeparator = " "
    let num = 222.3333
    _ = num.precision(2)
    

    Full sample

    func option1<T: Numeric>(value: T, numerFormatter: NumberFormatter? = nil) {
        print("Type: \(type(of: value))")
        print("Original Value: \(value)")
        let value1 = value.precision(2)
        print("value1 = \(value1 != nil ? "\(value1!)" : "nil")")
        let value2 = value.precision(5)
        print("value2 = \(value2 != nil ? "\(value2!)" : "nil")")
        if let value1 = value1, let value2 = value2 {
            print("value1 + value2 = \(value1 + value2)")
        }
        print("")
    }
    
    func option2<T: Numeric>(value: T, numberFormatter: NumberFormatter) {
        print("Type: \(type(of: value))")
        print("Original Value: \(value)")
        let value1 = value.precision(with: numberFormatter)
        print("formated value = \(value1 != nil ? "\(value1!)" : "nil")\n")
    }
    
    func test(with double: Double) {
        print("===========================\nTest with: \(double)\n")
        let float = Float(double)
        let float32 = Float32(double)
        let float64 = Float64(double)
        let float80 = Float80(double)
        let cgfloat = CGFloat(double)
    
        // Exapmle 1
        print("-- Option1\n")
        option1(value: double)
        option1(value: float)
        option1(value: float32)
        option1(value: float64)
        option1(value: float80)
        option1(value: cgfloat)
    
        // Exapmle 2
    
        let numberFormatter = NumberFormatter()
        numberFormatter.formatterBehavior = .behavior10_4
        numberFormatter.minimumIntegerDigits = 1
        numberFormatter.minimumFractionDigits = 4
        numberFormatter.maximumFractionDigits = 9
        numberFormatter.usesGroupingSeparator = true
        numberFormatter.groupingSeparator = " "
        numberFormatter.groupingSize = 3
    
        print("-- Option 2\n")
        option2(value: double, numberFormatter: numberFormatter)
        option2(value: float, numberFormatter: numberFormatter)
        option2(value: float32, numberFormatter: numberFormatter)
        option2(value: float64, numberFormatter: numberFormatter)
        option2(value: float80, numberFormatter: numberFormatter)
        option2(value: cgfloat, numberFormatter: numberFormatter)
    }
    
    test(with: 123.22)
    test(with: 1234567890987654321.0987654321)
    

    Output

    ===========================
    Test with: 123.22
    
    -- Option1
    
    Type: Double
    Original Value: 123.22
    value1 = 123.22
    value2 = 123.22
    value1 + value2 = 246.44
    
    Type: Float
    Original Value: 123.22
    value1 = nil
    value2 = nil
    
    Type: Float
    Original Value: 123.22
    value1 = nil
    value2 = nil
    
    Type: Double
    Original Value: 123.22
    value1 = 123.22
    value2 = 123.22
    value1 + value2 = 246.44
    
    Type: Float80
    Original Value: 123.21999999999999886
    value1 = nil
    value2 = nil
    
    Type: CGFloat
    Original Value: 123.22
    value1 = 123.22
    value2 = 123.22
    value1 + value2 = 246.44
    
    -- Option 2
    
    Type: Double
    Original Value: 123.22
    formatted value = 123.2200
    
    Type: Float
    Original Value: 123.22
    formatted value = 123.220001221
    
    Type: Float
    Original Value: 123.22
    formatted value = 123.220001221
    
    Type: Double
    Original Value: 123.22
    formatted value = 123.2200
    
    Type: Float80
    Original Value: 123.21999999999999886
    formatted value = nil
    
    Type: CGFloat
    Original Value: 123.22
    formatted value = 123.2200
    
    ===========================
    Test with: 1.2345678909876544e+18
    
    -- Option1
    
    Type: Double
    Original Value: 1.2345678909876544e+18
    value1 = 1.23456789098765e+18
    value2 = 1.23456789098765e+18
    value1 + value2 = 2.4691357819753e+18
    
    Type: Float
    Original Value: 1.234568e+18
    value1 = nil
    value2 = nil
    
    Type: Float
    Original Value: 1.234568e+18
    value1 = nil
    value2 = nil
    
    Type: Double
    Original Value: 1.2345678909876544e+18
    value1 = 1.23456789098765e+18
    value2 = 1.23456789098765e+18
    value1 + value2 = 2.4691357819753e+18
    
    Type: Float80
    Original Value: 1234567890987654400.0
    value1 = nil
    value2 = nil
    
    Type: CGFloat
    Original Value: 1.2345678909876544e+18
    value1 = 1.23456789098765e+18
    value2 = 1.23456789098765e+18
    value1 + value2 = 2.4691357819753e+18
    
    -- Option 2
    
    Type: Double
    Original Value: 1.2345678909876544e+18
    formatted value = 1 234 567 890 987 650 000.0000
    
    Type: Float
    Original Value: 1.234568e+18
    formatted value = 1 234 567 939 550 610 000.0000
    
    Type: Float
    Original Value: 1.234568e+18
    formatted value = 1 234 567 939 550 610 000.0000
    
    Type: Double
    Original Value: 1.2345678909876544e+18
    formatted value = 1 234 567 890 987 650 000.0000
    
    Type: Float80
    Original Value: 1234567890987654400.0
    formatted value = nil
    
    Type: CGFloat
    Original Value: 1.2345678909876544e+18
    formatted value = 1 234 567 890 987 650 000.0000
    
    0 讨论(0)
  • 2020-11-22 06:13

    Plenty of good answers above, but sometimes a pattern is more appropriate than the "%.3f" sort of gobbledygook. Here's my take using a NumberFormatter in Swift 3.

    extension Double {
      func format(_ pattern: String) -> String {
        let formatter = NumberFormatter()
        formatter.format = pattern
        return formatter.string(from: NSNumber(value: self))!
      }    
    }
    
    let n1 = 0.350, n2 = 0.355
    print(n1.format("0.00#")) // 0.35
    print(n2.format("0.00#")) // 0.355
    

    Here I wanted 2 decimals to be always shown, but the third only if it wasn't zero.

    0 讨论(0)
  • 2020-11-22 06:13

    @Christian Dietrich:

    instead of:

    var k = 1.0
        for i in 1...right+1 {
            k = 10.0 * k
        }
    let n = Double(Int(left*k)) / Double(k)
    return "\(n)"
    

    it could also be:

    let k = pow(10.0, Double(right))
    let n = Double(Int(left*k)) / k
    return "\(n)"
    

    [correction:] Sorry for confusion* - Of course this works with Doubles. I think, most practical (if you want digits to be rounded, not cut off) it would be something like that:

    infix operator ~> {}
    func ~> (left: Double, right: Int) -> Double {
        if right <= 0 {
            return round(left)
        }
        let k = pow(10.0, Double(right))
        return round(left*k) / k
    }
    

    For Float only, simply replace Double with Float, pow with powf and round with roundf.
    Update: I found that it is most practical to use return type Double instead of String. It works the same for String output, i.e.:

    println("Pi is roughly \(3.1415926 ~> 3)")
    

    prints: Pi is roughly 3.142
    So you can use it the same way for Strings (you can even still write: println(d ~> 2)), but additionally you can also use it to round values directly, i.e.:

    d = Double(slider.value) ~> 2
    

    or whatever you need …

    0 讨论(0)
  • 2020-11-22 06:16

    Swift 4 Xcode 10 Update

    extension Double {
        var asNumber:String {
            if self >= 0 {
                let formatter = NumberFormatter()
                formatter.numberStyle = .none
                formatter.percentSymbol = ""
                formatter.maximumFractionDigits = 2
                return "\(formatter.string(from: NSNumber(value: self)) ?? "")"
            }
            return ""
        }
    }
    
    0 讨论(0)
提交回复
热议问题