Swift 2.0 Format 1000's into a friendly K's

13,816

Solution 1

func formatPoints(num: Double) ->String{
    let thousandNum = num/1000
    let millionNum = num/1000000
    if num >= 1000 && num < 1000000{
        if(floor(thousandNum) == thousandNum){
            return("\(Int(thousandNum))k")
        }
        return("\(thousandNum.roundToPlaces(1))k")
    }
    if num > 1000000{
        if(floor(millionNum) == millionNum){
            return("\(Int(thousandNum))k")
        }
        return ("\(millionNum.roundToPlaces(1))M")
    }
    else{
        if(floor(num) == num){
            return ("\(Int(num))")
        }
        return ("\(num)")
    }

}

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

The updated code should now not return a .0 if the number is whole. Should now output 1k instead of 1.0k for example. I just checked essentially if double and its floor were the same.

I found the double extension in this question: Rounding a double value to x number of decimal places in swift

Solution 2

extension Int {
    var roundedWithAbbreviations: String {
        let number = Double(self)
        let thousand = number / 1000
        let million = number / 1000000
        if million >= 1.0 {
            return "\(round(million*10)/10)M"
        }
        else if thousand >= 1.0 {
            return "\(round(thousand*10)/10)K"
        }
        else {
            return "\(self)"
        }
    }
}

print(11.roundedWithAbbreviations)          // "11"
print(11111.roundedWithAbbreviations)       // "11.1K"
print(11111111.roundedWithAbbreviations)    // "11.1 M"

Solution 3

The extension below does the following-

  1. Will display number 10456 as 10.5k and 10006 as 10k (will not show the .0 decimals).
  2. Will do the exact above for millions and format it i.e. 10.5M and 10M
  3. Will format thousands upto 9999 in currency format i.e. with a comma like 9,999

    extension Double {
        var kmFormatted: String {
    
            if self >= 10000, self <= 999999 {
                return String(format: "%.1fK", locale: Locale.current,self/1000).replacingOccurrences(of: ".0", with: "")
            }
    
            if self > 999999 {
                return String(format: "%.1fM", locale: Locale.current,self/1000000).replacingOccurrences(of: ".0", with: "")
            }
    
            return String(format: "%.0f", locale: Locale.current,self)
        }
    }
    

Usage:

let num: Double = 1000001.00 //this should be a Double since the extension is on Double
let millionStr = num.kmFormatted
print(millionStr)

Prints 1M

And here it is in action-

enter image description here

Solution 4

To add to the answers, here's a Swift 4.X version of this using a loop to easily add/remove units if necessary:

extension Double {
    var shortStringRepresentation: String {
        if self.isNaN {
            return "NaN"
        }
        if self.isInfinite {
            return "\(self < 0.0 ? "-" : "+")Infinity"
        }
        let units = ["", "k", "M"]
        var interval = self
        var i = 0
        while i < units.count - 1 {
            if abs(interval) < 1000.0 {
                break
            }
            i += 1
            interval /= 1000.0
        }
        // + 2 to have one digit after the comma, + 1 to not have any.
        // Remove the * and the number of digits argument to display all the digits after the comma.
        return "\(String(format: "%0.*g", Int(log10(abs(interval))) + 2, interval))\(units[i])"
    }
}

Examples:

$ [1.5, 15, 1000, 1470, 1000000, 1530000, 1791200000].map { $0.shortStringRepresentation }
[String] = 7 values {
  [0] = "1.5"
  [1] = "15"
  [2] = "1k"
  [3] = "1.5k"
  [4] = "1M"
  [5] = "1.5M"
  [6] = "1791.2M"
}

Solution 5

Some Change in Answer(For Int and correct for million):

func formatPoints(num: Int) ->String{
    let thousandNum = num/1000
    let millionNum = num/1000000
    if num >= 1000 && num < 1000000{
        if(thousandNum == thousandNum){
            return("\(thousandNum)k")
        }
        return("\(thousandNum)k")
    }
    if num > 1000000{
        if(millionNum == millionNum){
            return("\(millionNum)M")
        }
        return ("\(millionNum)M")
    }
    else{
        if(num == num){
            return ("\(num)")
        }
        return ("\(num)")
    }

}
Share:
13,816

Related videos on Youtube

BenNov
Author by

BenNov

Updated on October 16, 2022

Comments

  • BenNov
    BenNov over 1 year

    I'm trying to write a function to present thousands and millions into K's and M's For instance:

    1000 = 1k
    1100 = 1.1k
    15000 = 15k
    115000 = 115k
    1000000 = 1m
    

    Here is where I got so far:

    func formatPoints(num: Int) -> String {
        let newNum = String(num / 1000)
        var newNumString = "\(num)"
        if num > 1000 && num < 1000000 {
            newNumString = "\(newNum)k"
        } else if num > 1000000 {
            newNumString = "\(newNum)m"
        }
    
        return newNumString
    }
    
    formatPoints(51100) // THIS RETURNS 51K instead of 51.1K
    

    How do I get this function to work, what am I missing?

    • vacawama
      vacawama about 8 years
      You're doing integer division which just throws away the remainder. You might want to convert to Doubles to do your math.
    • rmaddy
      rmaddy about 8 years
      Use NSByteCountFormatter instead of your own code.
    • pasine
      pasine about 8 years
      I don't do swift, but talking from an Obj-C prospective, I would say that you are using an Int value as input, so num / 1000 is probably not returning any decimals.
    • Martin R
      Martin R about 8 years
      Have a look at stackoverflow.com/questions/35854069/… (and the linked-to threads).
  • BenNov
    BenNov about 8 years
    Tried it, but when the number is below 1000 it returns it with .0 so 916 would become 916.0
  • user3483203
    user3483203 about 8 years
    All you would have to do is add one more if statement if the number is less than 1000, and run the same check I ran.
  • user3483203
    user3483203 about 8 years
    I just updated my answer to something that works for numbers under 1000 as well.
  • Kiran Jasvanee
    Kiran Jasvanee almost 6 years
    @Janky I think you are making some mistake, You can check the results in answer of mine here. I have tested it properly from my side and worked well. If you have any case where it misses you can discuss here rather than directly down voting the answer.
  • AnBisw
    AnBisw almost 6 years
    Dude @Janky something is wrong either with your data or your logic. This answer works and so does the one I posted.
  • LargeGlasses
    LargeGlasses almost 6 years
    Please fix: your minimum thousand value should be >= 1000 instead of 10000
  • AnBisw
    AnBisw almost 6 years
    No it should not. This extension will show thousands upto 9999 in a format of 9,999 (with a comma i.e. currency format) example 1000 will be 1,000 ; 2000 will be 2,000. Anything equal to or above 10000 or less than or equal to less than 999999 will show with a 'K' format and so on.
  • LargeGlasses
    LargeGlasses almost 6 years
    Ah I see what you did there. Interesting. Well, for my use case, I ended up changing it to 1000. My apologies for assuming your use case was the same as mine.
  • AnBisw
    AnBisw almost 6 years
    No Worries, glad you figured it out.
  • Rajesh Maurya
    Rajesh Maurya almost 6 years
    Code has some bug in swift 4.0.
  • Bassel
    Bassel over 5 years
    @Rajesh instead of round(self * divisor) / divisor, in Swift 4 do (self * divisor).rounded() / divisor
  • zardon
    zardon almost 4 years
    Test case: let values = [100, 999, 1000, 1250, 1750, 2000, 2741, 641239, -719409, 247001,999998, 10000000, 100000000, 1e10] Doesn't seem to handle 1250, 1750, 2000, 2741, 1e10 or larger
  • SwiftStudier
    SwiftStudier over 3 years
    For double 528010 this would generate "528,0K" string for countries which have different decimal separator rather than ".". It would be better to use Locale.current.decimalSeparator before zero in replacingOccurences for each value in this extension