How to truncate decimals to x places in Swift

Viewed 29287

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 output to be 17.9384). I do not want to round the number to 17.9385.

How can I do this?

10 Answers

The code for specific digits after decimals is:

let number = 17.9384693864596069567;
let merichle = Float(String(format: "%.1f", (number * 10000)).dropLast(2))!/10000

//merichle = 17.9384

And ultimately, your number gets truncate without round...

You can keep it simple with:

String(format: "%.0f", ratio*100)

Where 0 is the amount of decimals you want to allow. In this case zero. Ratio is a double like: 0.5556633. Hope it helps.

extension Double {
    /// Rounds the double to decimal places value
    func roundToPlaces(_ places:Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
    func cutOffDecimalsAfter(_ places:Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self*divisor).rounded(.towardZero) / divisor
    }
}

let a:Double = 1.228923598

print(a.roundToPlaces(2)) // 1.23
print(a.cutOffDecimalsAfter(2)) // 1.22

In swift 5 truncation to decimal can also be done by creating extension to decimal

extension Decimal {
  func truncation(by digit: Int) -> Decimal {
    var initialDecimal = self
    var roundedDecimal = Decimal()
    NSDecimalRound(&roundedDecimal, &initialDecimal, digit, .plain)
    return roundedDecimal
  }

use case for decimal value

value = Decimal(2.56430).truncation(by:2)

value = 2.560000 (after truncation)

Drag/Drop Solution, IOS, Swift 4

Copy this code into your application...

import Foundation

func truncateDigitsAfterDecimal(number: Double, afterDecimalDigits: Int) -> Double {
   if afterDecimalDigits < 1 || afterDecimalDigits > 512 {return 0.0}
   return Double(String(format: "%.\(afterDecimalDigits)f", number))!
}

Then you can call this function like:

truncateDigitsAfterDecimal(number: 45.123456789, afterDecimalDigits: 3)

Will produce the following:

45.123

SwiftUI: If you are truncating to format output in a view, but not computation, SwiftUI contains a convenient way to use C format specifiers as part of the signature for Text().

import SwiftUI

let myDouble = 17.93846938645960695
Text("\(myDouble, specifier: "%.2f")")
///Device display: 17.94

Above code will output the contents of Double directly to the View, rounded correctly to the hundredth place, but preserve the value of the Double for further calculations.

If as a user of SwiftUI you are unfamiliar with the C language format specifiers, this link contains helpful information: https://en.wikipedia.org/wiki/Printf_format_string

Way 1: If you don't want to create any new function for this, you can do it this way for getting the rounded value directly.

var roundedValue = (decimalValue * pow(10.0, Double(numberOfPlaces))).rounded())/pow(10.0, Double(numberOfPlaces)

Example:

var numberOfPlaces = 2
var decimalValue = 13312.2423423523523

print("\(((decimalValue * pow(10.0, Double(numberOfPlaces))).rounded())/pow(10.0, Double(numberOfPlaces)))")

Result: 13312.24



Way 2: if you just want to print, you may use:

print(String(format: "%.\(numberOfPlaces)f",decimalValue))

Example

var numberOfPlaces = 4
var decimalValue = 13312.2423423523523

print(String(format: "%.\(numberOfPlaces)f",decimalValue))

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")
}
Related