Swift 2.0 Format 1000's into a friendly K's
Asked Answered
A

15

36

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?

Apulia answered 2/4, 2016 at 17:58 Comment(5)
You're doing integer division which just throws away the remainder. You might want to convert to Doubles to do your math.Armlet
Use NSByteCountFormatter instead of your own code.Pontus
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.Sandglass
Have a look at #35854569 (and the linked-to threads).Cambrian
It's now built-in 1000.formatted(.number.notation(.compactName)) (See developer.apple.com/documentation/foundation/…).Finisterre
G
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

Grefer answered 2/4, 2016 at 18:17 Comment(5)
Tried it, but when the number is below 1000 it returns it with .0 so 916 would become 916.0Apulia
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.Grefer
I just updated my answer to something that works for numbers under 1000 as well.Grefer
Code has some bug in swift 4.0.Nam
@Rajesh instead of round(self * divisor) / divisor, in Swift 4 do (self * divisor).rounded() / divisorStephanus
E
34
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.1M"
Ekaterinburg answered 4/8, 2017 at 13:34 Comment(0)
G
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

Grefer answered 2/4, 2016 at 18:17 Comment(5)
Tried it, but when the number is below 1000 it returns it with .0 so 916 would become 916.0Apulia
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.Grefer
I just updated my answer to something that works for numbers under 1000 as well.Grefer
Code has some bug in swift 4.0.Nam
@Rajesh instead of round(self * divisor) / divisor, in Swift 4 do (self * divisor).rounded() / divisorStephanus
V
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

Voltmer answered 25/10, 2017 at 7:35 Comment(6)
Please fix: your minimum thousand value should be >= 1000 instead of 10000Augustina
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.Voltmer
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.Augustina
No Worries, glad you figured it out.Voltmer
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 largerPulitzer
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 extensionTurnsole
B
12

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"
}
Berger answered 30/7, 2018 at 21:25 Comment(7)
I like this answer the best. It was easy to add billion and trillion and passes all my test cases!Nappy
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 NaNNappy
@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.Nianiabi
at the risk of asking a stupid question, how is the negative maintained when using abs()? wont that convert it to a positive number?Nappy
nvm, i see it nowNappy
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 valueNianiabi
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 orderPulitzer
F
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)")
    }

}
Fey answered 25/5, 2018 at 7:15 Comment(0)
E
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

Ellsworth answered 14/8, 2018 at 6:17 Comment(0)
T
5

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
Theaterintheround answered 30/8, 2019 at 7:26 Comment(0)
W
4

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: "")
        }
    }
}
Wyant answered 30/11, 2020 at 13:16 Comment(0)
J
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
    }
}
Jacquelinjacqueline answered 8/3, 2017 at 18:29 Comment(0)
Q
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
    }

}
Quickie answered 23/12, 2017 at 10:24 Comment(0)
C
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)
}
}
Coldshoulder answered 6/2, 2020 at 8:29 Comment(0)
E
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: "")
    }
}
Elah answered 13/1, 2021 at 19:39 Comment(0)
T
1

I just created a custom struct for this goal. I use IntegerFormatStyle. It is available from @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)

struct AppNumberCompactFormatter {
    static func string(for value: Any?) -> String? {
        guard let number = value as? NSNumber else { return nil }
        return string(from: number)
    }

    static func string(from number: NSNumber) -> String {
        number.intValue.formatted(.number.notation(.compactName)).lowercased()
    }
}

Results:

  • AppNumberFormatter.string(from: -54050) the result is "-54k"
  • AppNumberFormatter.string(for: 7400) the result is "7.4k"
  • AppNumberFormatter.string(for: 7700) the result is "7.7k"
  • AppNumberFormatter.string(for: 4) the result is "4"
  • AppNumberFormatter.string(for: 5_530_432) the result is "5.5m"
Tallbot answered 29/12, 2023 at 8:43 Comment(1)
Why not use ByteCountFormatter? Also note that M (mega) is the proper symbol for 10^6. m (milli) is for 10^-3.Aerophobia
B
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

Baron answered 20/4, 2018 at 5:40 Comment(2)
@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.Baron
Dude @Janky something is wrong either with your data or your logic. This answer works and so does the one I posted.Voltmer
A
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
    }
}
Aberration answered 4/10, 2020 at 6:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.