33

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?

BenNov
  • 1,086
  • 2
  • 10
  • 18
  • 4
    You're doing integer division which just throws away the remainder. You might want to convert to Doubles to do your math. – vacawama Apr 02 '16 at 18:01
  • 5
    Use `NSByteCountFormatter` instead of your own code. – rmaddy Apr 02 '16 at 18:03
  • 1
    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. – pasine Apr 02 '16 at 18:05
  • 2
    Have a look at http://stackoverflow.com/questions/35854069/convert-high-numbers-to-lower-format (and the linked-to threads). – Martin R Apr 02 '16 at 18:33

14 Answers14

32
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"
Islam
  • 3,654
  • 3
  • 30
  • 40
mitja13
  • 791
  • 8
  • 6
25
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

Community
  • 1
  • 1
user3483203
  • 50,081
  • 9
  • 65
  • 94
21

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

Anjan Biswas
  • 7,746
  • 5
  • 47
  • 77
  • Please fix: your minimum thousand value should be >= 1000 instead of 10000 – LargeGlasses Jun 20 '18 at 20:37
  • 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. – Anjan Biswas Jun 20 '18 at 20:54
  • 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. – LargeGlasses Jun 21 '18 at 13:38
  • No Worries, glad you figured it out. – Anjan Biswas Jun 21 '18 at 15:39
  • 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 – zardon May 21 '20 at 11:51
  • 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 – SwiftStudier Sep 28 '20 at 19:57
11

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"
}
Stéphane Copin
  • 1,888
  • 18
  • 18
  • I like this answer the best. It was easy to add `billion` and `trillion` and passes all my test cases! – highboi Feb 07 '19 at 15:31
  • correction: almost all my tests cases. not getting it to work on negative numbers, throws `Double value cannot be converted to Int because it is either infinite or NaN` – highboi Feb 19 '19 at 16:09
  • 1
    @lizzy91 indeed! I've updated the code to account for this, it was happening as `log10` can't take negative arguments, and returns `NaN` if this was the case. – Stéphane Copin Feb 19 '19 at 22:10
  • at the risk of asking a stupid question, how is the negative maintained when using `abs()`? wont that convert it to a positive number? – highboi Feb 19 '19 at 22:25
  • nvm, i see it now – highboi Feb 19 '19 at 22:29
  • 1
    I know you've figured out, but if other stumble on it and are wondering: this is because the actual value is stored in `interval` (the _second_ one). `Int(log10(abs(interval))) + 2` is how many digits to show, not the displayed value – Stéphane Copin Feb 21 '19 at 21:15
  • 1
    Whilst this is good, and a bit more accurate; the problem is that you are relying on the `units` array to be (a) correct (b) in the right order. The function doesn't actually do any checking on the number. So lets say you omit "m" and put in "B", and you feed it 1billion it won't find it. So, the units array has to be exhaustive and in the right order – zardon May 21 '20 at 15:00
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)")
    }

}
Arun sharma
  • 642
  • 1
  • 7
  • 17
5

For swift 4.0. its work completely fine and answer based on @user3483203

Function for convert Double value to String

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

}

Make one Double extension

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

Usage of above function

UILABEL.text = formatPoints(num: Double(310940)!)

Output :

enter image description here

Himanshu Moradiya
  • 4,769
  • 4
  • 25
  • 49
4

Here is my approach to it.

extension Int {
func shorted() -> String {
    if self >= 1000 && self < 10000 {
        return String(format: "%.1fK", Double(self/100)/10).replacingOccurrences(of: ".0", with: "")
    }

    if self >= 10000 && self < 1000000 {
        return "\(self/1000)k"
    }

    if self >= 1000000 && self < 10000000 {
        return String(format: "%.1fM", Double(self/100000)/10).replacingOccurrences(of: ".0", with: "")
    }

    if self >= 10000000 {
        return "\(self/1000000)M"
    }

    return String(self)
}

Below are some examples:

print(913.shorted())
print(1001.shorted())
print(1699.shorted())
print(8900.shorted())
print(10500.shorted())
print(17500.shorted())
print(863500.shorted())
print(1200000.shorted())
print(3010000.shorted())
print(11800000.shorted())

913
1K
1.6K
8.9K
10k
17k
863k
1.2M
3M
11M
Cosmin U.
  • 49
  • 4
3

Solution above (from @qlear) in Swift 3:

func formatPoints(num: Double) -> String {
    var thousandNum = num / 1_000
    var millionNum = num / 1_000_000
    if  num >= 1_000 && num < 1_000_000 {
        if  floor(thousandNum) == thousandNum {
            return("\(Int(thousandNum))k")
        }
        return("\(thousandNum.roundToPlaces(1))k")
    }
    if  num > 1_000_000 {
        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
    mutating func roundToPlaces(_ places : Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self.rounded() * divisor) / divisor
    }
}
Rici
  • 1,014
  • 2
  • 12
  • 21
3

I have converted @AnBisw's answer to use switch (build time friendly):

   extension Double {
    var kmFormatted: String {
        switch self {
        case ..<1_000:
            return String(format: "%.0f", locale: Locale.current, self)
        case 1_000 ..< 999_999:
            return String(format: "%.1fK", locale: Locale.current, self / 1_000).replacingOccurrences(of: ".0", with: "")
        default:
            return String(format: "%.1fM", locale: Locale.current, self / 1_000_000).replacingOccurrences(of: ".0", with: "")
        }
    }
}
MohamMad Salah
  • 971
  • 2
  • 14
  • 31
1

Based on the solution from @qlear.
I've noticed that if the number was exactly 1000000, it would return 1000000 unformatted.
So I've added that into the function. I also included negative values.. since not everyone is always making a profit!

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

        if num <= -1000 && num > -1000000{
            if(floor(thousandNum) == thousandNum){
                return("\(Int(thousandNum))k")
            }
            return("\(round1(thousandNum, toNearest: 0.01))k")
        }
        if num < -1000000{
            if(floor(millionNum) == millionNum){
                return("\(Int(thousandNum))k")
            }
            return ("\(round1(millionNum, toNearest: 0.01))M")
        }
        else if num == -1000000
        {
            return ("\(round1(millionNum, toNearest: 0.01))M")
        }
        else{
            if(floor(num) == num){
                return ("\(Int(num))")
            }
            return ("\(round1(num, toNearest: 0.01))")
        }
    }

}

And the number extension of course:

extension Double {
    /// Rounds the double to decimal places value
    func round1(_ value: Double, toNearest: Double) -> Double {
        return Darwin.round(value / toNearest) * toNearest
    }

}
Egghead
  • 6,837
  • 5
  • 20
  • 38
1

if you want in lacs :

 extension Int {
func shorted() -> String {
    if self >= 1000 && self < 10000 {
        return String(format: "%.1fK", Double(self/100)/10).replacingOccurrences(of: ".0", with: "")
    }

    if self >= 10000 && self < 100000 {
        return "\(self/1000)k"
    }

    if self >= 100000 && self < 1000000 {
        return String(format: "%.1fL", Double(self/10000)/10).replacingOccurrences(of: ".0", with: "")
    }

    if self >= 1000000 && self < 10000000 {
        return String(format: "%.1fM", Double(self/100000)/10).replacingOccurrences(of: ".0", with: "")
    }

    if self >= 10000000 {
        return "\(self/1000000)M"
    }

    return String(self)
}
}
Siddhant Nigam
  • 462
  • 3
  • 13
1

This solution utilises ByteCountFormatter, but for any big number abbreviation of any number type. Why this formatter written by Apple for bytes only remains unknown.

extension Numeric {
    
    var abbreviated: String {
        let bytesString = ByteCountFormatter.string(fromByteCount: (self as! NSNumber).int64Value, countStyle: .decimal)
        let numericString = bytesString
            .replacingOccurrences(of: "bytes", with: "")
            .replacingOccurrences(of: "B", with: "") // removes B (bytes) in 'KB'/'MB'/'GB'
            .replacingOccurrences(of: "G", with: "B") // replace G (Giga) to just B (billions)
        return numericString.replacingOccurrences(of: " ", with: "")
    }
}
m8labs
  • 3,671
  • 2
  • 30
  • 32
0

Minor improvement to @chrisz answer, Swift-4 Doble extension - This works fine in all cases.

extension Double {

  // Formatting double value to k and M
  // 1000 = 1k
  // 1100 = 1.1k
  // 15000 = 15k
  // 115000 = 115k
  // 1000000 = 1m
  func formatPoints() -> String{
        let thousandNum = self/1000
        let millionNum = self/1000000
        if self >= 1000 && self < 1000000{
            if(floor(thousandNum) == thousandNum){
                return ("\(Int(thousandNum))k").replacingOccurrences(of: ".0", with: "")
            }
            return("\(thousandNum.roundTo(places: 1))k").replacingOccurrences(of: ".0", with: "")
        }
        if self > 1000000{
            if(floor(millionNum) == millionNum){
                return("\(Int(thousandNum))k").replacingOccurrences(of: ".0", with: "")
            }
            return ("\(millionNum.roundTo(places: 1))M").replacingOccurrences(of: ".0", with: "")
        }
        else{
            if(floor(self) == self){
                return ("\(Int(self))")
            }
            return ("\(self)")
        }
    }

    /// Returns rounded value for passed places
    ///
    /// - parameter places: Pass number of digit for rounded value off after decimal
    ///
    /// - returns: Returns rounded value with passed places
    func roundTo(places:Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
}

enter image description here
enter image description here
enter image description here
enter image description here

Kiran Jasvanee
  • 6,362
  • 1
  • 36
  • 52
  • @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. – Kiran Jasvanee Jun 12 '18 at 07:31
  • Dude @Janky something is wrong either with your data or your logic. This answer works and so does the one I posted. – Anjan Biswas Jun 14 '18 at 21:37
0

Since we all more or less disagree

func FormatFriendly(num: Double) ->String {
    var thousandNum = num/1000
    var millionNum = num/1000000

    if num >= 1000 && num < 1000000{
        if(floor(thousandNum) == thousandNum){
            return("\(Int(thousandNum))K").replacingOccurrences(of: ".0", with: "")
        }
        return("\(thousandNum.roundToPlaces(places: 1))K").replacingOccurrences(of: ".0", with: "")
    }

    if num >= 1000000{
        //if(floor(millionNum) == millionNum){
            //return("\(Int(thousandNum))K").replacingOccurrences(of: ".0", with: "")
        //}
    return ("\(millionNum.roundToPlaces(places: 1))M").replacingOccurrences(of: ".0", with: "")
    }else {
        if(floor(num) == num){
            return ("\(Int(num))")
        }
        return ("\(num)")
    }
}

extension Double {
    /// Rounds the double to decimal places value
    mutating func roundToPlaces(places: Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return Darwin.round(self * divisor) / divisor
    }
}
iOS Flow
  • 69
  • 1
  • 8