How to truncate decimals to x places in Swift

前端 未结 9 1956
Happy的楠姐
Happy的楠姐 2020-12-04 01:26

In my swift program, I have a really long decimal number (say 17.9384693864596069567) and I want to truncate the decimal to a few decimal places (so I want the

9条回答
  •  被撕碎了的回忆
    2020-12-04 02:03

    Answer for Swift 5.2

    I have looked a lot of answers and I always had conversion issues when truncating. According to my maths knowledge, by truncating I understand that if I have 3.1239 and I want 3 decimals then I will have 3.123 with no rounding (!= 3.1234).

    Maybe, because of the nature of the process, I was always successful with Doubles but always had issues with Floats.

    My approach is to create an extension of BinaryFloatingPoint so I can reuse it for Float, CGFLoat, Double ...

    The following extension gets a BinaryFloatingPoint and can return the String or the BinaryFloatingPoint values with a numberOfDecimals given and it handles different type of cases:

    extension Numeric where Self: BinaryFloatingPoint {
        
        /// Retruns the string value of the BinaryFloatingPoint. The initiaiser
        var toString: String {
            return String(describing: self)
        }
        
        /// Returns the number of decimals. It will be always greater than 0
        var numberOfDecimals: Int {
            return toString.count - String(Int(self)).count - 1
        }
       
        /// Returns a Number with a certain number of decimals
        /// - Parameters:
        ///   - Parameter numberOfDecimals: Number of decimals to return
        /// - Returns: BinaryFloatingPoint with number of decimals especified
        func with(numberOfDecimals: Int) -> Self {
            let stringValue = string(numberOfDecimals: numberOfDecimals)
            if self is Double {
                return Double(stringValue) as! Self
            } else {
                return Float(stringValue) as! Self
            }
        }
        
        /// Returns a string representation with a number of decimals
        /// - Parameters:
        ///   - Parameter numberOfDecimals: Number of decimals to return
        /// - Returns: String with number of decimals especified
        func string(numberOfDecimals: Int) -> String {
            let selfString = toString
            let selfStringComponents = selfString.components(separatedBy: ".")
            let selfStringIntegerPart = selfStringComponents[0]
            let selfStringDecimalPart = selfStringComponents[1]
            
            if numberOfDecimals == 0 {
                return selfStringIntegerPart
            } else {
                if selfStringDecimalPart.count == numberOfDecimals {
                    return [selfStringIntegerPart,
                            selfStringDecimalPart].joined(separator: ".")
                } else {
                    if selfStringDecimalPart.count > numberOfDecimals {
                        return [selfStringIntegerPart,
                                String(selfStringDecimalPart.prefix(numberOfDecimals))].joined(separator: ".")
                    } else {
                        let difference = numberOfDecimals - selfStringDecimalPart.count
                        let addedCharacters = [Character].init(repeating: "0", count: difference)
                        
                        return [selfStringIntegerPart,
                                selfStringDecimalPart+addedCharacters].joined(separator: ".")
                    }
                }
            }
        }
            
    }
    

    It might look old school but all my tests are passing:

    func test_GivenADecimalNumber_ThenAssertNumberOfDecimalsWanted() {
       //No decimals
        XCTAssertEqual(Float(3).with(numberOfDecimals: 0), 3)
        XCTAssertEqual(Float(3.09).with(numberOfDecimals: 0), 3)
        XCTAssertEqual(Float(3.999).with(numberOfDecimals: 0), 3)
        
        XCTAssertEqual(Double(3).with(numberOfDecimals: 0), 3)
        XCTAssertEqual(Double(3.09).with(numberOfDecimals: 0), 3)
        XCTAssertEqual(Double(3.999).with(numberOfDecimals: 0), 3)
        
        
        //numberOfDecimals == totalNumberOfDecimals
        XCTAssertEqual(Float(3.00).with(numberOfDecimals: 2), 3.00)
        XCTAssertEqual(Float(3.09).with(numberOfDecimals: 2), 3.09)
        XCTAssertEqual(Float(3.01).with(numberOfDecimals: 2), 3.01)
        XCTAssertEqual(Float(3.999).with(numberOfDecimals: 3), 3.999)
        XCTAssertEqual(Float(3.991).with(numberOfDecimals: 3), 3.991)
        
        XCTAssertEqual(Double(3.00).with(numberOfDecimals: 2), 3.00)
        XCTAssertEqual(Double(3.09).with(numberOfDecimals: 2), 3.09)
        XCTAssertEqual(Double(3.01).with(numberOfDecimals: 2), 3.01)
        XCTAssertEqual(Double(3.999).with(numberOfDecimals: 3), 3.999)
        XCTAssertEqual(Double(3.991).with(numberOfDecimals: 3), 3.991)
        
        
        //numberOfDecimals < totalNumberOfDecimals
        XCTAssertEqual(Float(3.00).with(numberOfDecimals: 1), 3.0)
        XCTAssertEqual(Float(3.09).with(numberOfDecimals: 1), 3.0)
        XCTAssertEqual(Float(3.01).with(numberOfDecimals: 1), 3.0)
        XCTAssertEqual(Float(3.999).with(numberOfDecimals: 2), 3.99)
        XCTAssertEqual(Float(3.991).with(numberOfDecimals: 2), 3.99)
        
        XCTAssertEqual(Double(3.00).with(numberOfDecimals: 1), 3.0)
        XCTAssertEqual(Double(3.09).with(numberOfDecimals: 1), 3.0)
        XCTAssertEqual(Double(3.01).with(numberOfDecimals: 1), 3.0)
        XCTAssertEqual(Double(3.999).with(numberOfDecimals: 2), 3.99)
        XCTAssertEqual(Double(3.991).with(numberOfDecimals: 2), 3.99)
        
        
        //numberOfDecimals > totalNumberOfDecimals
        XCTAssertEqual(Float(3.00).with(numberOfDecimals: 3), 3.000)
        XCTAssertEqual(Float(3.09).with(numberOfDecimals: 3), 3.090)
        XCTAssertEqual(Float(3.01).with(numberOfDecimals: 3), 3.010)
        XCTAssertEqual(Float(3.999).with(numberOfDecimals: 4), 3.9990)
        XCTAssertEqual(Float(3.991).with(numberOfDecimals: 4), 3.9910)
        
        XCTAssertEqual(Double(3.00).with(numberOfDecimals: 3), 3.000)
        XCTAssertEqual(Double(3.09).with(numberOfDecimals: 3), 3.090)
        XCTAssertEqual(Double(3.01).with(numberOfDecimals: 3), 3.010)
        XCTAssertEqual(Double(3.999).with(numberOfDecimals: 4), 3.9990)
        XCTAssertEqual(Double(3.991).with(numberOfDecimals: 4), 3.9910)
    }
    
    func test_GivenADecimal_ThenAssertStringValueWithDecimalsWanted() {
        //No decimals
        XCTAssertEqual(Float(3).string(numberOfDecimals: 0), "3")
        XCTAssertEqual(Float(3.09).string(numberOfDecimals: 0), "3")
        XCTAssertEqual(Float(3.999).string(numberOfDecimals: 0), "3")
        
        XCTAssertEqual(Double(3).string(numberOfDecimals: 0), "3")
        XCTAssertEqual(Double(3.09).string(numberOfDecimals: 0), "3")
        XCTAssertEqual(Double(3.999).string(numberOfDecimals: 0), "3")
        
        
        //numberOfDecimals == totalNumberOfDecimals
        XCTAssertEqual(Float(3.00).string(numberOfDecimals: 2), "3.00")
        XCTAssertEqual(Float(3.09).string(numberOfDecimals: 2), "3.09")
        XCTAssertEqual(Float(3.01).string(numberOfDecimals: 2), "3.01")
        XCTAssertEqual(Float(3.999).string(numberOfDecimals: 3), "3.999")
        XCTAssertEqual(Float(3.991).string(numberOfDecimals: 3), "3.991")
        
        XCTAssertEqual(Double(3.00).string(numberOfDecimals: 2), "3.00")
        XCTAssertEqual(Double(3.09).string(numberOfDecimals: 2), "3.09")
        XCTAssertEqual(Double(3.01).string(numberOfDecimals: 2), "3.01")
        XCTAssertEqual(Double(3.999).string(numberOfDecimals: 3), "3.999")
        XCTAssertEqual(Double(3.991).string(numberOfDecimals: 3), "3.991")
        
        
        //numberOfDecimals < totalNumberOfDecimals
        XCTAssertEqual(Float(3.00).string(numberOfDecimals: 1), "3.0")
        XCTAssertEqual(Float(3.09).string(numberOfDecimals: 1), "3.0")
        XCTAssertEqual(Float(3.01).string(numberOfDecimals: 1), "3.0")
        XCTAssertEqual(Float(3.999).string(numberOfDecimals: 2), "3.99")
        XCTAssertEqual(Float(3.991).string(numberOfDecimals: 2), "3.99")
        
        XCTAssertEqual(Double(3.00).string(numberOfDecimals: 1), "3.0")
        XCTAssertEqual(Double(3.09).string(numberOfDecimals: 1), "3.0")
        XCTAssertEqual(Double(3.01).string(numberOfDecimals: 1), "3.0")
        XCTAssertEqual(Double(3.999).string(numberOfDecimals: 2), "3.99")
        XCTAssertEqual(Double(3.991).string(numberOfDecimals: 2), "3.99")
        
        
        //numberOfDecimals > totalNumberOfDecimals
        XCTAssertEqual(Float(3.00).string(numberOfDecimals: 3), "3.000")
        XCTAssertEqual(Float(3.09).string(numberOfDecimals: 3), "3.090")
        XCTAssertEqual(Float(3.01).string(numberOfDecimals: 3), "3.010")
        XCTAssertEqual(Float(3.999).string(numberOfDecimals: 4), "3.9990")
        XCTAssertEqual(Float(3.991).string(numberOfDecimals: 4), "3.9910")
        
        XCTAssertEqual(Double(3.00).string(numberOfDecimals: 3), "3.000")
        XCTAssertEqual(Double(3.09).string(numberOfDecimals: 3), "3.090")
        XCTAssertEqual(Double(3.01).string(numberOfDecimals: 3), "3.010")
        XCTAssertEqual(Double(3.999).string(numberOfDecimals: 4), "3.9990")
        XCTAssertEqual(Double(3.991).string(numberOfDecimals: 4), "3.9910")
    }
    

提交回复
热议问题