8

Currently working on a simple function which does a great job for me.

For example: If I have 1000, It'll print out 1.0K, or 1,000,000 it'll be 1M. Everything works fine until here.

What if I wanted to turn 1,000,000,000 into 1B?

I tried the following:

func formatPoints(from: Int) -> String {
     let number = Double(from)
     let thousand = number / 1000
     let million = number / 1000000
     let billion = number / 1000000000
     
     if million >= 1.0 { 
     return "\(round(million*10)/10)M" 
} else if thousand >= 1.0 { 
     return "\(round(thousand*10)/10)K"
} else if billion >= 1.0 {
        return ("\(round(billion*10/10))B")
} else {
    return "\(Int(number))"}
}

print(formatPoints(from: 1000000000))

But it returns 1000.0M, not 1B.

HangarRash
  • 7,314
  • 5
  • 5
  • 32

2 Answers2

30

This answer formats by truncating (versus rounding). 1,515 rounded would generate 2k whereas truncated would generate 1.5k. The function requires reducing a number's scale (removing digits to the right of the decimal) which I've packaged as an extension.

extension Double {
    func reduceScale(to places: Int) -> Double {
        let multiplier = pow(10, Double(places))
        let newDecimal = multiplier * self // move the decimal right
        let truncated = Double(Int(newDecimal)) // drop the fraction
        let originalDecimal = truncated / multiplier // move the decimal back
        return originalDecimal
    }
}

// As an extension
extension Int {
    var asFormattedString: String {
        let num = abs(Double(self))
        let sign = self < 0 ? "-" : ""

        switch num {
        case 1_000_000_000...:
            return "\(sign)\((num / 1_000_000_000).reduceScale(to: 1))B"
        case 1_000_000...:
            return "\(sign)\((num / 1_000_000).reduceScale(to: 1))M"
        case 1_000...:
            return "\(sign)\((num / 1_000).reduceScale(to: 1))K"
        case 0...:
            return "\(self)"
        default:
            return "\(sign)\(self)"
        }
    }
}

// As a standalone function
func getFormattedString(from n: Int) -> String {
    let num = abs(Double(n))
    let sign = (n < 0) ? "-" : ""

    switch num {
    case 1_000_000_000...:
        return "\(sign)\((num / 1_000_000_000).reduceScale(to: 1))B"
    case 1_000_000...:
        return "\(sign)\((num / 1_000_000).reduceScale(to: 1))M"
    case 1_000...:
        return "\(sign)\((num / 1_000).reduceScale(to: 1))K"
    case 0...:
        return "\(n)"
    default:
        return "\(sign)\(n)"
    }
}

let count = 349382093
let string1 = count.asFormattedString // 349.3M
let string2 = getFormattedString(from: count) // 349.3M

You can fine tune this method for specific cases, such as returning 100k instead of 100.5k or 1M instead of 1.1M. This method handles negatives as well.

trndjc
  • 11,654
  • 3
  • 38
  • 51
  • Great answer, i also prefer this way – excitedmicrobe Jan 21 '18 at 21:13
  • 1
    If you put the cases in descending order, you would only have to wrtie `case 1_000_000_000...`, `case 1_000_000...`, `case 1_000...`. Also, you just extract the sign: `let sign = (n < 0) ? "-" : ""`, then return `return "\(sign)\(formatted)X"` – Alexander Jan 21 '18 at 22:20
  • @iabuseservers You should put brackets around the initial definition of `formatted` (which isn't actually formatted, at that time!), and call truncate directly on it. `return "\(sign)\((num / 1_000).truncate(places: 1))K"` – Alexander Jan 21 '18 at 22:41
9

The following logic of if-else statements shows you what goes first and what last:

import Foundation

func formatPoints(from: Int) -> String {

    let number = Double(from)
    let billion = number / 1_000_000_000
    let million = number / 1_000_000
    let thousand = number / 1000
    
    if billion >= 1.0 {
        return "\(round(billion * 10) / 10)B"
    } else if million >= 1.0 {
        return "\(round(million * 10) / 10)M"
    } else if thousand >= 1.0 {
        return "\(round(thousand * 10) / 10)K"
    } else {
        return "\(Int(number))"
    }
}

print(formatPoints(from: 1000))             /*  1.0 K  */
print(formatPoints(from: 1000000))          /*  1.0 M  */
print(formatPoints(from: 1000000000))       /*  1.0 B  */

Billion must go first.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • [round()](https://developer.apple.com/documentation/swift/double/2884722-round) is an instance method inside Foundation module (or in Swift Standard Library). – Andy Jazz Aug 05 '21 at 08:09