How to truncate decimals to x places in Swift
Asked Answered
M

10

23

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?

Moth answered 11/3, 2016 at 17:37 Comment(3)
Possible duplicate of #24051814Cairo
Either solution below is wholly effective.Moth
Sure, was just showing you another one :)Cairo
S
52

You can tidy this up even more by making it an extension of Double:

extension Double {
    func truncate(places : Int)-> Double {
        return Double(floor(pow(10.0, Double(places)) * self)/pow(10.0, Double(places)))
    }
}

You use it like this:

var num = 1.23456789
// return the number truncated to 2 places
print(num.truncate(places: 2))

// return the number truncated to 6 places
print(num.truncate(places: 6))
Scrod answered 11/3, 2016 at 18:2 Comment(6)
I like it! So organized and it actually makes sense when you use it :).Moth
glad you like it - it's the first extension I have ever written, so we have both learned something today :-)Scrod
This doesn't work on numbers below 1 :( If you use 0.23456789.truncate(2) you'll get 0.23000000000000001Regeniaregensburg
What about this value : "2315385.01" , it gives 2315385, which is wrongBosson
Jeroen Bakker - I don't know why you think that, because it does return 0.23 @Samarth Kejriwal - that is odd. It doesn't do that for numbers on either side of your magic numberScrod
@Scrod I want the result of "2315385.01" as "2315385.01" after truncating, but the results come out to be 2315385. Any edit in the answer?Bosson
F
13

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.

Fauch answered 28/4, 2020 at 23:23 Comment(2)
This is the right answer if you want to consume the double as a string.Periderm
Though I would simply go like so: String(format: "%.2f", 0.123 to generate a string like so: "0.12"Truant
M
10

I figured this one out.

Just floor (round down) the number, with some fancy tricks.

let x = 1.23556789
let y = Double(floor(10000*x)/10000) // leaves on first four decimal places
let z = Double(floor(1000*x)/1000) // leaves on first three decimal places
print(y) // 1.2355
print(z) // 1.235

So, multiply by 1 and the number of 0s being the decimal places you want, floor that, and divide it by what you multiplied it by. And voila.

Moth answered 11/3, 2016 at 17:42 Comment(0)
K
6
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
Kleenex answered 27/11, 2019 at 8:50 Comment(0)
M
5

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...

Marileemarilin answered 10/2, 2018 at 9:18 Comment(6)
This looks like it'll work just fine if you're looking for a String as output instead of a Double.Moth
@Moth if you want double number, you may use this code: Double(String(format: "%.2f", b))Marileemarilin
Good call, that way looks fine to me :)Moth
@RamazanKarami String(format: "%.2f", b) will round a double, not truncate it.Lavin
@Lavin Yes, you're right. The answer was corrected by a new edit without roundthe number. Thanks for your follow upMarileemarilin
let number = 1.99999999 and your code will return merichle = 2.Alkahest
L
4

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)

Lucia answered 22/7, 2020 at 14:32 Comment(2)
I like the idea behind this, but there is a bug. This actually rounds, it does not truncate. The .plain causes NSDecimalRound to round up or down. Replace it with .down to always round down, which will give it the behavior of truncating.Brinkema
@Brinkema this is relative. If the number is negative you would have to use .up. Check this postUmbra
A
3

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
Apodosis answered 17/4, 2019 at 15:42 Comment(1)
this will round it, not truncateConjoint
M
3

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

Mangle answered 12/3, 2020 at 23:37 Comment(1)
Your solution is rounding decimal number... Read the question one more time.Tabithatablature
M
1

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))
Macegan answered 16/5, 2021 at 4:39 Comment(0)
B
0

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")
}
Boff answered 3/8, 2020 at 9:57 Comment(6)
extending Numeric protocol and constraining it to BinaryFloatingPoint is pointlessUmbra
Double(stringValue) as! Self ??? Btw if it is not Double what guarantee do you have that it is a Float. Try Float80(123.456789).with(numberOfDecimals: 2) // BOOMUmbra
@LeoDabus Have you seen that the logic is around the protocol BinaryFloatingPoint ??? The idea is that you don't care if it is Float, Double, Float80 ... as! Self is just to help the compiler ... In addition, try to see what happens when you cast floats and doubles on edge casesBoff
This is wrong. What you need is to return a generic type using a generic method. If you want to return Self you should return the same type which is calling the method. You are not helping the compiler you are affirming that it is a Float but there is no guarantee.Umbra
Btw I am not endorsing the conversion to string to manipulate your floating point but if you want to convert a string to binary floating point you need to constrain Self to LosslessStringConvertible. Note that it will not cover CGFloat because it does NOT conform to it.Umbra
And it would return an optional Self?Umbra

© 2022 - 2024 — McMap. All rights reserved.