7

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

Thanks!

2 Answers2

22

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 just packaged as an extension so it can be used anywhere (not just in the function).

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

func formatNumber(_ n: Int) -> String {
    let num = abs(Double(n))
    let sign = (n < 0) ? "-" : ""

    switch num {
    case 1_000_000_000...:
        var formatted = num / 1_000_000_000
        formatted = formatted.reduceScale(to: 1)
        return "\(sign)\(formatted)B"

    case 1_000_000...:
        var formatted = num / 1_000_000
        formatted = formatted.reduceScale(to: 1)
        return "\(sign)\(formatted)M"

    case 1_000...:
        var formatted = num / 1_000
        formatted = formatted.reduceScale(to: 1)
        return "\(sign)\(formatted)K"

    case 0...:
        return "\(n)"

    default:
        return "\(sign)\(n)"
    }
}

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.

print(formatNumber(1515)) // 1.5K
print(formatNumber(999999)) // 999.9K
print(formatNumber(1000999)) // 1.0M
fake girlfriends
  • 10,377
  • 2
  • 38
  • 42
  • 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
6

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 thousand = number / 1000
    let million = number / 1000000
    let billion = number / 1000000000
    
    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
  • 36,357
  • 13
  • 107
  • 175
  • [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