iOS convert large numbers to smaller format

31.6k views Asked by At

How can I convert all numbers that are more than 3 digits down to a 4 digit or less number?

This is exactly what I mean:

10345 = 10.3k
10012 = 10k
123546 = 123.5k
4384324 = 4.3m

Rounding is not entirely important, but an added plus.

I have looked into NSNumberFormatter but have not found the proper solution, and I have yet to find a proper solution here on SO. Any help is greatly appreciated, thanks!

28

There are 28 answers

3
The Human Bagel On BEST ANSWER

Here are two methods I have come up with that work together to produce the desired effect. This will also automatically round up. This will also specify how many numbers total will be visible by passing the int dec.

Also, in the float to string method, you can change the @"%.1f" to @"%.2f", @"%.3f", etc to tell it how many visible decimals to show after the decimal point.

For Example:

52935 --->  53K
52724 --->  53.7K





-(NSString *)abbreviateNumber:(int)num withDecimal:(int)dec {

    NSString *abbrevNum;
    float number = (float)num;

    NSArray *abbrev = @[@"K", @"M", @"B"];

    for (int i = abbrev.count - 1; i >= 0; i--) {

        // Convert array index to "1000", "1000000", etc
        int size = pow(10,(i+1)*3);

        if(size <= number) {
            // Here, we multiply by decPlaces, round, and then divide by decPlaces.
            // This gives us nice rounding to a particular decimal place.
            number = round(number*dec/size)/dec;

            NSString *numberString = [self floatToString:number];

            // Add the letter for the abbreviation
            abbrevNum = [NSString stringWithFormat:@"%@%@", numberString, [abbrev objectAtIndex:i]];

            NSLog(@"%@", abbrevNum);

        }

    }


    return abbrevNum;
}

- (NSString *) floatToString:(float) val {

    NSString *ret = [NSString stringWithFormat:@"%.1f", val];
    unichar c = [ret characterAtIndex:[ret length] - 1];

    while (c == 48 || c == 46) { // 0 or .
        ret = [ret substringToIndex:[ret length] - 1];
        c = [ret characterAtIndex:[ret length] - 1];
    }

    return ret;
}

Hope this helps anyone else out who needs it!

0
dariowskii On

Swift 5 - 2022

Based on @gbitaudeau answer, I've updated the syntax and improved the string formatting a bit, which is useful in most cases.

I also added the locale to handle comma etc..

extension Int {

    func formatUsingAbbrevation() -> String {
        let numFormatter = NumberFormatter()

        typealias Abbrevation = (threshold: Double, divisor: Double, suffix: String)
        let abbreviations: [Abbrevation] = [(0, 1, ""),
                                            (1000.0, 1000.0, "K"),
                                            (999_999.0, 1_000_000.0, "M"),
                                            (999_999_999.0, 1_000_000_000.0, "B")]

        let startValue = Double(abs(self))
        let abbreviation: Abbrevation = {
            var prevAbbreviation = abbreviations[0]
            for tmpAbbreviation in abbreviations {
                if (startValue < tmpAbbreviation.threshold) {
                    break
                }
                prevAbbreviation = tmpAbbreviation
            }
            return prevAbbreviation
        }()

        let value = Double(self) / abbreviation.divisor
        numFormatter.positiveSuffix = abbreviation.suffix
        numFormatter.negativeSuffix = abbreviation.suffix
        numFormatter.allowsFloats = true
        numFormatter.minimumIntegerDigits = 1
        numFormatter.minimumFractionDigits = 0
        numFormatter.maximumFractionDigits = 1
        numFormatter.locale = .current

        return numFormatter.string(from: NSNumber(value: value))!
    }
}

let testValue: [Int] = [598, -999, 999, 1000, -1284, 9940, 9980, 39900, 99880, 125325, 154789, 399880, 999898, 999999, 1456384, 12383474, 789456123, 8573657281]

testValue.forEach {
    print ("Value: \($0) -> \($0.formatUsingAbbrevation())")
}

Prints:

Value: 598 -> "598"
Value: -999 -> "-999"
Value: 999 -> "999"
Value: 1000 -> "1K"
Value: -1284 -> "-1.3K"
Value: 9940 -> "9.9K"
Value: 9980 -> "10K"
Value: 39900 -> "39.9K"
Value: 99880 -> "99.9K"
Value: 125325 -> "125.3K"
Value: 154789 -> "154.8K"
Value: 399880 -> "399.9K"
Value: 999898 -> "999.9K"
Value: 999999 -> "1M"
Value: 1456384 -> "1.5M"
Value: 12383474 -> "12.4M"
Value: 789456123 -> "789.5M"
Value: 8573657281 -> "8.6B"
1
Taidg Murphy On

I ran into a similar issue trying to format y-axis values in Shinobi Charts. It required using a NSNumberFormatter, so I eventually came up with this

NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
[numFormatter setPositiveFormat:@"0M"];
[numFormatter setMultiplier:[NSNumber numberWithDouble:0.000001]];

To get a formatted value

NSString *formattedNumber = [numFormatter stringFromNumber:[NSNumber numberWithInteger:4000000]]; //@"4M"

This solution does not having rounding included, but if you (or anyone else) just needs something simple, this could work. If you need by the thousand instead of by million, you change the "M" to a "K" in the setPostiveFormat method, and change the NSNumber value in the multiplier to 0.001 .

0
TruMan1 On

This seems like an oversight by Apple since there are tons of relative times, metrics, dates, list, person, bytes, etc, etc, etc formatters but this is a pretty common case especially with social media, graphs, and others. Ok end rant..

Here's my version below wrapping the NumberFormatter and handles all Int values including negatives, as well as locale aware:

public struct AbbreviatedNumberFormatter {
    private let formatter: NumberFormatter

    public init(locale: Locale? = nil) {
        let formatter = NumberFormatter()
        formatter.allowsFloats = true
        formatter.minimumIntegerDigits = 1
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 1
        formatter.numberStyle = .decimal

        if let locale = locale {
            formatter.locale = locale
        }

        self.formatter = formatter
    }
}

public extension AbbreviatedNumberFormatter {
    /// Returns a string containing the formatted value of the provided `Int` value.
    func string(from value: Int) -> String {
        let divisor: Double
        let suffix: String

        switch abs(value) {
        case ..<1000:
            return "\(value)"
        case ..<1_000_000:
            divisor = 1000
            suffix = "K"
        case ..<1_000_000_000:
            divisor = 1_000_000
            suffix = "M"
        case ..<1_000_000_000_000:
            divisor = 1_000_000_000
            suffix = "B"
        default:
            divisor = 1_000_000_000_000
            suffix = "T"
        }

        let number = NSNumber(value: Double(value) / divisor)

        guard let formatted = formatter.string(from: number) else {
            return "\(value)"
        }

        return formatted + suffix
    }
}

And the test cases:

final class AbbreviatedNumberFormatterTests: XCTestCase {}

extension AbbreviatedNumberFormatterTests {
    func testFormatted() {
        let formatter = AbbreviatedNumberFormatter()

        XCTAssertEqual(formatter.string(from: 0), "0")
        XCTAssertEqual(formatter.string(from: -10), "-10")
        XCTAssertEqual(formatter.string(from: 500), "500")
        XCTAssertEqual(formatter.string(from: 999), "999")
        XCTAssertEqual(formatter.string(from: 1000), "1K")
        XCTAssertEqual(formatter.string(from: 1234), "1.2K")
        XCTAssertEqual(formatter.string(from: 9000), "9K")
        XCTAssertEqual(formatter.string(from: 10_000), "10K")
        XCTAssertEqual(formatter.string(from: -10_000), "-10K")
        XCTAssertEqual(formatter.string(from: 15_235), "15.2K")
        XCTAssertEqual(formatter.string(from: -15_235), "-15.2K")
        XCTAssertEqual(formatter.string(from: 99_500), "99.5K")
        XCTAssertEqual(formatter.string(from: -99_500), "-99.5K")
        XCTAssertEqual(formatter.string(from: 100_500), "100.5K")
        XCTAssertEqual(formatter.string(from: -100_500), "-100.5K")
        XCTAssertEqual(formatter.string(from: 105_000_000), "105M")
        XCTAssertEqual(formatter.string(from: -105_000_000), "-105M")
        XCTAssertEqual(formatter.string(from: 140_800_200_000), "140.8B")
        XCTAssertEqual(formatter.string(from: 170_400_800_000_000), "170.4T")
        XCTAssertEqual(formatter.string(from: -170_400_800_000_000), "-170.4T")
        XCTAssertEqual(formatter.string(from: -9_223_372_036_854_775_807), "-9,223,372T")
        XCTAssertEqual(formatter.string(from: Int.max), "9,223,372T")
    }
}

extension AbbreviatedNumberFormatterTests {
    func testFormattedLocale() {
        let formatter = AbbreviatedNumberFormatter(locale: Locale(identifier: "fr"))

        XCTAssertEqual(formatter.string(from: 0), "0")
        XCTAssertEqual(formatter.string(from: -10), "-10")
        XCTAssertEqual(formatter.string(from: 500), "500")
        XCTAssertEqual(formatter.string(from: 999), "999")
        XCTAssertEqual(formatter.string(from: 1000), "1K")
        XCTAssertEqual(formatter.string(from: 1234), "1,2K")
        XCTAssertEqual(formatter.string(from: 9000), "9K")
        XCTAssertEqual(formatter.string(from: 10_000), "10K")
        XCTAssertEqual(formatter.string(from: -10_000), "-10K")
        XCTAssertEqual(formatter.string(from: 15_235), "15,2K")
        XCTAssertEqual(formatter.string(from: -15_235), "-15,2K")
        XCTAssertEqual(formatter.string(from: 99_500), "99,5K")
        XCTAssertEqual(formatter.string(from: -99_500), "-99,5K")
        XCTAssertEqual(formatter.string(from: 100_500), "100,5K")
        XCTAssertEqual(formatter.string(from: -100_500), "-100,5K")
        XCTAssertEqual(formatter.string(from: 105_000_000), "105M")
        XCTAssertEqual(formatter.string(from: -105_000_000), "-105M")
        XCTAssertEqual(formatter.string(from: 140_800_200_000), "140,8B")
        XCTAssertEqual(formatter.string(from: -170_400_800_000_000), "-170,4T")
        XCTAssertEqual(formatter.string(from: -9_223_372_036_854_775_807), "-9 223 372T")
        XCTAssertEqual(formatter.string(from: Int.max), "9 223 372T")
    }
}

Only thing I don't like about it is that it's not localized as to what K, M, B, or T means in other languages. Much appreciated to everyone's inspiration.

5
gbitaudeau On

Here my version ! Thanks to previous answers. The goals of this version is :

  • Have better threshold control because small number details are more important that very big number details
  • Use as much as possible NSNumberFormatter to avoid location problems (like comma instead of dot in french)
  • Avoid ".0" and well rounding numbers, which can be customize using NSNumberFormatterRoundingMode

You can use all wonderful NSNumberFormatter options to fulfill your needs, see NSNumberFormatter Class Reference

The code (gist):

extension Int {

    func formatUsingAbbrevation () -> String {
        let numFormatter = NSNumberFormatter()

        typealias Abbrevation = (threshold:Double, divisor:Double, suffix:String)
        let abbreviations:[Abbrevation] = [(0, 1, ""),
                                           (1000.0, 1000.0, "K"),
                                           (100_000.0, 1_000_000.0, "M"),
                                           (100_000_000.0, 1_000_000_000.0, "B")]
                                           // you can add more !

        let startValue = Double (abs(self))
        let abbreviation:Abbrevation = {
            var prevAbbreviation = abbreviations[0]
            for tmpAbbreviation in abbreviations {
                if (startValue < tmpAbbreviation.threshold) {
                    break
                }
                prevAbbreviation = tmpAbbreviation
            }
            return prevAbbreviation
        } ()

        let value = Double(self) / abbreviation.divisor
        numFormatter.positiveSuffix = abbreviation.suffix
        numFormatter.negativeSuffix = abbreviation.suffix
        numFormatter.allowsFloats = true
        numFormatter.minimumIntegerDigits = 1
        numFormatter.minimumFractionDigits = 0
        numFormatter.maximumFractionDigits = 1

        return numFormatter.stringFromNumber(NSNumber (double:value))!
    }

}


let testValue:[Int] = [598, -999, 1000, -1284, 9940, 9980, 39900, 99880, 399880, 999898, 999999, 1456384, 12383474]

testValue.forEach() {
    print ("Value : \($0) -> \($0.formatUsingAbbrevation ())")
}

Result :

Value : 598 -> 598
Value : -999 -> -999
Value : 1000 -> 1K
Value : -1284 -> -1.3K
Value : 9940 -> 9.9K
Value : 9980 -> 10K
Value : 39900 -> 39.9K
Value : 99880 -> 99.9K
Value : 399880 -> 0.4M
Value : 999898 -> 1M
Value : 999999 -> 1M
Value : 1456384 -> 1.5M
Value : 12383474 -> 12.4M
1
beebcon On

After trying a couple of these solutions, Luca laco appears to have it closest, but I've made some amendments to his method in order to have more control over how many digits will appear (i.e. if you want 120.3K to be shorter, you can limit it to 120K). Additionally, I've added an extra step that ensures a number like 999,999 doesn't appear as 1000.0K, rather 1.0M.

/*
 With "onlyShowDecimalPlaceForNumbersUnder" = 10:
 Original number: 598 - Result: 598
 Original number: 1000 - Result: 1.0K
 Original number: 1284 - Result: 1.3K
 Original number: 9980 - Result: 10K
 Original number: 39900 - Result: 40K
 Original number: 99880 - Result: 100K
 Original number: 999898 - Result: 1.0M
 Original number: 999999 - Result: 1.0M
 Original number: 1456384 - Result: 1.5M
 Original number: 12383474 - Result: 12M
 */

- (NSString *)suffixNumber:(NSNumber *)number
{
    if (!number)
        return @"";

    long long num = [number longLongValue];
    if (num < 1000)
        return [NSString stringWithFormat:@"%lld",num];

    int exp = (int) (log(num) / log(1000));
    NSArray * units = @[@"K",@"M",@"G",@"T",@"P",@"E"];

    int onlyShowDecimalPlaceForNumbersUnder = 10; // Either 10, 100, or 1000 (i.e. 10 means 12.2K would change to 12K, 100 means 120.3K would change to 120K, 1000 means 120.3K stays as is)
    NSString *roundedNumStr = [NSString stringWithFormat:@"%.1f", (num / pow(1000, exp))];
    int roundedNum = [roundedNumStr integerValue];
    if (roundedNum >= onlyShowDecimalPlaceForNumbersUnder) {
        roundedNumStr = [NSString stringWithFormat:@"%.0f", (num / pow(1000, exp))];
        roundedNum = [roundedNumStr integerValue];
    }

    if (roundedNum >= 1000) { // This fixes a number like 999,999 from displaying as 1000K by changing it to 1.0M
        exp++;
        roundedNumStr = [NSString stringWithFormat:@"%.1f", (num / pow(1000, exp))];
    }

    NSString *result = [NSString stringWithFormat:@"%@%@", roundedNumStr, [units objectAtIndex:(exp-1)]];

    NSLog(@"Original number: %@ - Result: %@", number, result);
    return result;
}
0
Aadi007 On

Updated answer for swift conversion

extension Int {
    func abbreviateNumber() -> String {
        func floatToString(val: Float) -> String {
            var ret: NSString = NSString(format: "%.1f", val)

            let c = ret.characterAtIndex(ret.length - 1)

            if c == 46 {
                ret = ret.substringToIndex(ret.length - 1)
            }

            return ret as String
        }

        var abbrevNum = ""
        var num: Float = Float(self)

        if num >= 1000 {
            var abbrev = ["K","M","B"]

            for var i = abbrev.count-1; i >= 0; i-- {
                let sizeInt = pow(Double(10), Double((i+1)*3))
                let size = Float(sizeInt)

                if size <= num {
                    num = num/size
                    var numStr: String = floatToString(num)
                    if numStr.hasSuffix(".0") {
                        let startIndex = numStr.startIndex.advancedBy(0)
                        let endIndex = numStr.endIndex.advancedBy(-2)
                        let range = startIndex..<endIndex
                        numStr = numStr.substringWithRange( range )
                    }

                    let suffix = abbrev[i]
                    abbrevNum = numStr+suffix
                }
            }
        } else {
            abbrevNum = "\(num)"
            let startIndex = abbrevNum.startIndex.advancedBy(0)
            let endIndex = abbrevNum.endIndex.advancedBy(-2)
            let range = startIndex..<endIndex
            abbrevNum = abbrevNum.substringWithRange( range )
        }

        return abbrevNum
    }
}
0
Bassem Qoulta On

Swift 4.0 version from Phan Van Linh's answer

private static let suffix = ["", "K", "M", "B", "T", "P", "E"]

public static func formatNumber(_ number: Double) -> String{
   var index = 0
   var value = number
   while((value / 1000) >= 1){
       value = value / 1000
       index += 1
   }
   return String(format: "%.1f%@", value, suffix[index])
}
1
Leonardo Cavalcante On

Why do you guys get it so difficult?

it can be as simple as this:

-(NSString *)friendlyNumber:(long long)num{

    NSString *stringNumber;

    if (num < 1000) {
        stringNumber = [NSString stringWithFormat:@"%lld", num];

    }else if(num < 1000000){
        float newNumber = floor(num / 100) / 10.0;
        stringNumber = [NSString stringWithFormat:@"%.1fK", newNumber];

    }else{
        float newNumber = floor(num / 100000) / 10.0;
        stringNumber = [NSString stringWithFormat:@"%.1fM", newNumber];
    }
    return stringNumber;
}
0
coderek On

Swift version

Direct translation from Objective-C version

func abbreviateNumber(num: NSNumber) -> NSString {
    var ret: NSString = ""
    let abbrve: [String] = ["K", "M", "B"]

    var floatNum = num.floatValue

    if floatNum > 1000 {

        for i in 0..<abbrve.count {
            let size = pow(10.0, (Float(i) + 1.0) * 3.0)
            println("\(size)   \(floatNum)")
            if (size <= floatNum) {
                let num = floatNum / size
                let str = floatToString(num)
                ret = NSString(format: "%@%@", str, abbrve[i])
            }
        }
    } else {
        ret = NSString(format: "%d", Int(floatNum))
    }

    return ret
}

func floatToString(val: Float) -> NSString {
    var ret = NSString(format: "%.1f", val)
    var c = ret.characterAtIndex(ret.length - 1)

    while c == 48 {
        ret = ret.substringToIndex(ret.length - 1)
        c = ret.characterAtIndex(ret.length - 1)


        if (c == 46) {
            ret = ret.substringToIndex(ret.length - 1)
        }
    }
    return ret
}


abbreviateNumber(123)
abbreviateNumber(12503)
abbreviateNumber(12934203)
abbreviateNumber(12234200003)
abbreviateNumber(92234203)
abbreviateNumber(9223.3)
1
Zoyt On
extension Int {
    func abbreviateNumber() -> String {
        func floatToString(val: Float) -> String {
            var ret: NSString = NSString(format: "%.1f", val)

            var c = ret.characterAtIndex(ret.length - 1)

            if c == 46 {
                ret = ret.substringToIndex(ret.length - 1)
            }

            return ret as String
        }

        var abbrevNum = ""
        var num: Float = Float(self)

        if num >= 1000 {
            var abbrev = ["K","M","B"]

            for var i = abbrev.count-1; i >= 0; i-- {
                var sizeInt = pow(Double(10), Double((i+1)*3))
                var size = Float(sizeInt)

                if size <= num {
                    num = num/size
                    var numStr: String = floatToString(num)
                    if numStr.hasSuffix(".0") {
                        numStr = numStr.substringToIndex(advance(numStr.startIndex,count(numStr)-2))
                    }

                    var suffix = abbrev[i]
                    abbrevNum = numStr+suffix
                }
            }
        } else {
            abbrevNum = "\(num)"
            if abbrevNum.hasSuffix(".0") {
                abbrevNum = abbrevNum.substringToIndex(advance(abbrevNum.startIndex, count(abbrevNum)-2))
            }
        }

        return abbrevNum
    }
}
1
Linh On

You can use this simple function, the idea is easy to understand

-(NSString*) suffixNumber:(NSNumber*)number
    double value = [number doubleValue];
    NSUInteger index = 0;
    NSArray *suffixArray = @[@"", @"K", @"M", @"B", @"T", @"P", @"E"];

    while ((value/1000) >= 1){
       value = value/1000;
       index++;
    }

    //3 line of code below for round doubles to 1 digit
    NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init];
    [fmt setMaximumFractionDigits:1];
    NSString *valueWith1Digit = [fmt stringFromNumber:[NSNumber numberWithFloat:value]];

    NSString *svalue = [NSString stringWithFormat:@"%@%@",valueWith1Digit, [suffixArray objectAtIndex:index]];
    return svalue;
}

Test

NSLog(@"%@",[self suffixNumber:@100]);     //  100
NSLog(@"%@",[self suffixNumber:@1000]);    // 1K
NSLog(@"%@",[self suffixNumber:@10345]);   // 10.3K
NSLog(@"%@",[self suffixNumber:@10012]);   // 10K
NSLog(@"%@",[self suffixNumber:@123456]);  // 123.5K
NSLog(@"%@",[self suffixNumber:@4384324]); // 4.4M
NSLog(@"%@",[self suffixNumber:@10000000]) // 10M
0
Kiran Jasvanee On

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

0
kasyanov-ms On

A little bit cleaner solution:

struct Shortener {

    func string(from value: String) -> String? {
        guard let value = Int(value) else { return nil }

        if value < 1000 {
            return "\(value)"
        }
        if value < 100_000 {
            return string(from: value, divisor: 1000, suffix: "K")
        }
        if value < 100_000_000 {
            return string(from: value, divisor: 1_000_000, suffix: "M")
        }

        return string(from: value, divisor: 1_000_000_000, suffix: "B")
    }

    private func string(from value: Int, divisor: Double, suffix: String) -> String? {
        let formatter = NumberFormatter()
        let dividedValue = Double(value) / divisor

        formatter.positiveSuffix = suffix
        formatter.negativeSuffix = suffix
        formatter.allowsFloats = true
        formatter.minimumIntegerDigits = 1
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 1

        return formatter.string(from: NSNumber(value: dividedValue))
    }

}
0
Pankaj Gaikar On

A swift 4 and swift 5 compatible solution

extension Int {
    func formatUsingAbbrevation () -> String {
        let abbrev = "KMBTPE"
        return abbrev.enumerated().reversed().reduce(nil as String?) { accum, tuple in
            let factor = Double(self) / pow(10, Double(tuple.0 + 1) * 3)
            let format = (factor.truncatingRemainder(dividingBy: 1)  == 0 ? "%.0f%@" : "%.1f%@")
            return accum ?? (factor > 1 ? String(format: format, factor, String(tuple.1)) : nil)
        } ?? String(self)
    }
}
0
Rajesh On

The following method handles both positive and negative numbers unlike most of the solutions here.

It even works for currency as well.

BOOL isCurrency = YES; // Make it YES / NO depending upon weather your input value belongs to a revenue figure or a normal value.
double value = XXX ; // where 'XXX' is your input value

NSString *formattedValue = @"";

int decimalPlaces = 1; // number of decimal places (precision) that you want.
float multiplier;

// Enumerate number abbreviations
NSArray *abbrevations = @[@"", @"k", @"m", @"b", @"t" ];

// Go through the array backwards, so we do the largest first
int index;
for (index = abbrevations.count-1; index >= 0; index--) {

    multiplier = pow(10, decimalPlaces);

    // Convert array index to "1000", "1000000", etc
    double size = pow(10, index*3); 

    // If the number is bigger or equal do the abbreviation
    if(size <= fabs(round(value)))
    {
        // Here, we multiply by multiplier, round, and then divide by multiplier.
        // This gives us nice rounding to a particular decimal place.
        value = round(value * multiplier / size) / multiplier;


        // We are done... stop
        break;
    }
}

if (index<0)
{
    // Note: - To handle special case where x is our input number,  -0.5 > x < 0.5
    value = 0;
    index++;
}

NSString *stringFormat = nil;
// Add the letter for the abbreviation
if (isCurrency) 
{
    if (value >=0)
    {
        stringFormat = [NSString stringWithFormat:@"$%%.%0df%@", decimalPlaces, abbrevations[index]];
    }
    else
    {
        // Note: - To take care of extra logic where '$' symbol comes after '-' symbol for negative currency.
        stringFormat = [NSString stringWithFormat:@"-$%%.%df%@", decimalPlaces, abbrevations[index]];
        value = -value;
    }
}
else
{
    stringFormat = [NSString stringWithFormat:@"%%.%0df%@", decimalPlaces, abbrevations[index]];
}

formattedValue = [NSString stringWithFormat:stringFormat, value];

Output is as below

In Currency mode

'999' ---- '$999.0' 
'999.9' ---- '$1.0k' 
'999999.9' ---- '$1.0m' 
'-1000.1' ---- '-$1.0k' 
'-0.9' ---- '-$0.9' 

In Number mode

'999' ---- '999.0' 
'999.9' ---- '1.0k' 
'1' ---- '1.0' 
'9999' ---- '10.0k' 
'99999.89999999999' ---- '100.0k' 
'999999.9' ---- '1.0m' 
'-1' ---- '-1.0' 
'-1000.1' ---- '-1.0k' 
'5109999' ---- '5.1m' 
'-5109999' ---- '-5.1m' 
'999999999.9' ---- '1.0b' 
'0.1' ---- '0.0' 
'0' ---- '0.0' 
'-0.1' ---- '0.0' 
'-0.9' ---- '-0.9' 

I have created the above method based on @Kyle Begeman's original inspiration from the link shared by @Pandiyan Cool. Thanks to @Jeff B for the initial code in Javascript from the following link. Is there a way to round numbers into a reader friendly format? (e.g. $1.1k)

0
verec On

If you are interested in formatting bytes count, this article by Mattt Thompson shows how to use iOS/OSX builtin NSByteCountFormatter

There are also builtin formatters for energy, mass, length and a bunch of others.

The crux of it is that for most common units you do not need to write any custom code as Apple has already provided the tedious work for you. Check their online reference for NS[SomeUnit]Formatter, e.g. MKDistanceFormatter, NSDateIntervalFormatter or NSDateFormatter, etc ...

0
Paul Lehn On

gbitaudeau's answer in Swift 4

extension Int {

func formatUsingAbbrevation () -> String {
    let numFormatter = NumberFormatter()

    typealias Abbrevation = (threshold:Double, divisor:Double, suffix:String)
    let abbreviations:[Abbrevation] = [(0, 1, ""),
                                       (1000.0, 1000.0, "K"),
                                       (100_000.0, 1_000_000.0, "M"),
                                       (100_000_000.0, 1_000_000_000.0, "B")]
                                       // you can add more !

    let startValue = Double (abs(self))
    let abbreviation:Abbrevation = {
        var prevAbbreviation = abbreviations[0]
        for tmpAbbreviation in abbreviations {
            if (startValue < tmpAbbreviation.threshold) {
                break
            }
            prevAbbreviation = tmpAbbreviation
        }
        return prevAbbreviation
    } ()

    let value = Double(self) / abbreviation.divisor
    numFormatter.positiveSuffix = abbreviation.suffix
    numFormatter.negativeSuffix = abbreviation.suffix
    numFormatter.allowsFloats = true
    numFormatter.minimumIntegerDigits = 1
    numFormatter.minimumFractionDigits = 0
    numFormatter.maximumFractionDigits = 1

    return numFormatter.string(from: NSNumber (value:value))!
}

}

1
Josh On

Here is an updated version of Luca Iaco's answer that works with Swift 4

func suffixNumber(number: NSNumber) -> String {
    var num:Double = number.doubleValue
    let sign = ((num < 0) ? "-" : "" )
    num = fabs(num)
    if (num < 1000.0) {
        return "\(sign)\(num)"
    }

    let exp: Int = Int(log10(num) / 3.0)
    let units: [String] = ["K","M","G","T","P","E"]
    let roundedNum: Double = round(10 * num / pow(1000.0,Double(exp))) / 10

    return "\(sign)\(roundedNum)\(units[exp-1])";
}
1
Ricardo Corrêa de Souza On

I had the same issue and ended up using Kyle's approach but unfortunately it breaks when numbers like 120000 are used, showing 12k instead of 120K and I needed to show small numbers like: 1.1K instead of rounding down to 1K.

So here's my edit from Kyle's original idea:

Results:
[self abbreviateNumber:987] ---> 987
[self abbreviateNumber:1200] ---> 1.2K
[self abbreviateNumber:12000] ----> 12K
[self abbreviateNumber:120000] ----> 120K
[self abbreviateNumber:1200000] ---> 1.2M
[self abbreviateNumber:1340] ---> 1.3K
[self abbreviateNumber:132456] ----> 132.5K

-(NSString *)abbreviateNumber:(int)num {

NSString *abbrevNum;
float number = (float)num;

//Prevent numbers smaller than 1000 to return NULL
if (num >= 1000) {
    NSArray *abbrev = @[@"K", @"M", @"B"];

    for (int i = abbrev.count - 1; i >= 0; i--) {

        // Convert array index to "1000", "1000000", etc
        int size = pow(10,(i+1)*3);

        if(size <= number) {
            // Removed the round and dec to make sure small numbers are included like: 1.1K instead of 1K
            number = number/size;
            NSString *numberString = [self floatToString:number];

            // Add the letter for the abbreviation
            abbrevNum = [NSString stringWithFormat:@"%@%@", numberString, [abbrev objectAtIndex:i]];
        }

    }
} else {

    // Numbers like: 999 returns 999 instead of NULL
    abbrevNum = [NSString stringWithFormat:@"%d", (int)number];
}

return abbrevNum;
}

- (NSString *) floatToString:(float) val {
NSString *ret = [NSString stringWithFormat:@"%.1f", val];
unichar c = [ret characterAtIndex:[ret length] - 1];

while (c == 48) { // 0
    ret = [ret substringToIndex:[ret length] - 1];
    c = [ret characterAtIndex:[ret length] - 1];

    //After finding the "." we know that everything left is the decimal number, so get a substring excluding the "."
    if(c == 46) { // .
        ret = [ret substringToIndex:[ret length] - 1];
    }
}

return ret;
}

I Hope this can help you guys.

8
Luca Iaco On
-(NSString*) suffixNumber:(NSNumber*)number
{
    if (!number)
        return @"";

    long long num = [number longLongValue];

    int s = ( (num < 0) ? -1 : (num > 0) ? 1 : 0 );
    NSString* sign = (s == -1 ? @"-" : @"" );

    num = llabs(num);

    if (num < 1000)
        return [NSString stringWithFormat:@"%@%lld",sign,num];

    int exp = (int) (log10l(num) / 3.f); //log10l(1000));

    NSArray* units = @[@"K",@"M",@"G",@"T",@"P",@"E"];

    return [NSString stringWithFormat:@"%@%.1f%@",sign, (num / pow(1000, exp)), [units objectAtIndex:(exp-1)]];
}

sample usage

NSLog(@"%@",[self suffixNumber:@100]); // 100
NSLog(@"%@",[self suffixNumber:@1000]); // 1.0K
NSLog(@"%@",[self suffixNumber:@1500]); // 1.5K
NSLog(@"%@",[self suffixNumber:@24000]); // 24.0K
NSLog(@"%@",[self suffixNumber:@99900]); // 99.9K
NSLog(@"%@",[self suffixNumber:@99999]); // 100.0K
NSLog(@"%@",[self suffixNumber:@109999]); // 110.0K
NSLog(@"%@",[self suffixNumber:@5109999]); // 5.1M
NSLog(@"%@",[self suffixNumber:@8465445223]); // 8.5G
NSLog(@"%@",[self suffixNumber:[NSNumber numberWithInt:-120]]); // -120
NSLog(@"%@",[self suffixNumber:[NSNumber numberWithLong:-5000000]]); // -5.0M
NSLog(@"%@",[self suffixNumber:[NSNumber numberWithDouble:-3.5f]]); // -3
NSLog(@"%@",[self suffixNumber:[NSNumber numberWithDouble:-4000.63f]]); // -4.0K

[Update]

Swift version below:

func suffixNumber(number:NSNumber) -> NSString {

    var num:Double = number.doubleValue;
    let sign = ((num < 0) ? "-" : "" );

    num = fabs(num);

    if (num < 1000.0){
        return "\(sign)\(num)";
    }

    let exp:Int = Int(log10(num) / 3.0 ); //log10(1000));

    let units:[String] = ["K","M","G","T","P","E"];

    let roundedNum:Double = round(10 * num / pow(1000.0,Double(exp))) / 10;

    return "\(sign)\(roundedNum)\(units[exp-1])";
}

sample usage

print(self.suffixNumber(NSNumber(long: 100))); // 100.0
print(self.suffixNumber(NSNumber(long: 1000))); // 1.0K
print(self.suffixNumber(NSNumber(long: 1500))); // 1.5K
print(self.suffixNumber(NSNumber(long: 24000))); // 24.0K
print(self.suffixNumber(NSNumber(longLong: 99900))); // 99.9K
print(self.suffixNumber(NSNumber(longLong: 99999))); // 100.0K
print(self.suffixNumber(NSNumber(longLong: 109999))); // 110.0K
print(self.suffixNumber(NSNumber(longLong: 5109999))); // 5.1K
print(self.suffixNumber(NSNumber(longLong: 8465445223))); // 8.5G
print(self.suffixNumber(NSNumber(long: -120))); // -120.0
print(self.suffixNumber(NSNumber(longLong: -5000000))); // -5.0M
print(self.suffixNumber(NSNumber(float: -3.5))); // -3.5
print(self.suffixNumber(NSNumber(float: -4000.63))); // -4.0K

Hope it helps

1
Mojtaba Hosseini On

Use a formatter:

You can use NumberAbbreviationFormatter:

var formatter = NumberAbbreviationFormatter()
formatter.maximumFractionDigits = 2 //  Any `NumberFormatter` attribute can be applied like this.

formatter.format(10_201) // -> 10.2K

Here is the implementation of this simple custom formatter:

import Foundation

@dynamicMemberLookup
class NumberAbbreviationFormatter {
    var formatter = NumberFormatter()

    private let units = "KMBTPE"
        .enumerated()
        .map { (symbol: $0.element, value: pow(10, ($0.offset + 1) * 3)) }

    func format(_ value: Int) -> String {
        guard let unit = units.last(where: { $0.value.isLessThanOrEqualTo(Decimal(abs(value))) }) else {
            return formatter.string(from: NSNumber(value: value)) ?? value.description
        }

        let factor = Decimal(value) / unit.value
        let number = NSDecimalNumber(decimal: factor) as NSNumber
        let formattedNumber = formatter.string(from: number) ?? number.description
        return formattedNumber + String(unit.symbol)
    }
}

/// Access to the internal formatter
extension NumberAbbreviationFormatter {
    subscript<T>(dynamicMember keyPath: KeyPath<NumberFormatter, T>) -> T {
        formatter[keyPath: keyPath]
    }

    subscript<T>(dynamicMember keyPath: WritableKeyPath<NumberFormatter, T>) -> T {
        get { formatter[keyPath: keyPath] }
        set { formatter[keyPath: keyPath] = newValue }
    }
}

Pros against other answers:

  • Uses the native Foundation's NumberFormatter internally to minimize bugs.
  • All NumberFormatter's attributes can be customized through the dynamicMemberLookup.
    • Automatic rounding and changing the rounding mode
    • minimum and maximum fraction digits
    • DecimalSeparator and etc.
  • Uses Decimal instead of Double to prevent breaking the floating point.
  • No unnecessary iterations for better performance.
  • No manual hand-written numbers.
  • Two way conversion (both 10_000 -> 10K and 10K -> 10_000).
  • Uses the Apple way to use a formatter just like any other formatters.
0
Mojtaba Hosseini On

✅ iOS 15 / macOS 12

You can natively get the compact version of any number like:

1600.formatted(.number.notation(.compactName)) // 1.6K

You can also chain configurations (precision, rounding, etc.) like:

1631.formatted(
  .number
  .notation(.compactName)
  .precision(.fractionLength(3))
) // 1.631K

For older platforms, you can define your own formatter.

0
user726522 On

I used gbitaudeau's answer to make this Objective-C category of NSNumberFormatter, which I use in our project (Vero.co). The NSNumberFormatter instance here created only once for the entire project.

@implementation NSNumberFormatter (Abbreviation)
+ (NSString*) abbreviatedStringFromNumber:(NSNumber*) number
{
    static dispatch_once_t pred;
    static NSNumberFormatter* __abbrFormatter = nil;
    static NSArray<NSDictionary*> * __abbreviations = nil;

    dispatch_once(&pred, ^{
        __abbrFormatter = [[NSNumberFormatter alloc] init];
        __abbrFormatter.numberStyle = NSNumberFormatterDecimalStyle;
        __abbrFormatter.usesGroupingSeparator = YES;
        __abbrFormatter.allowsFloats = YES;
        __abbrFormatter.minimumIntegerDigits = 1;
        __abbrFormatter.minimumFractionDigits = 0;
        __abbrFormatter.maximumFractionDigits = 2;

        __abbreviations = @[@{@"threshold":@(0.0), @"divisor":@(1.0), @"suffix":@""},
                        @{@"threshold":@(1000.0), @"divisor":@(1000.0), @"suffix":@"K"},
                        @{@"threshold":@(1000000.0), @"divisor":@(1000000.0), @"suffix":@"M"}];
    });

    double startValue = ABS([number doubleValue]);
    NSDictionary* abbreviation = __abbreviations[0];
    for (NSDictionary* tmpAbbr in __abbreviations)
    {
        if (startValue < [tmpAbbr[@"threshold"] doubleValue])
        {
            break;
        }
        abbreviation = tmpAbbr;
    }

    double value = [number doubleValue] / [abbreviation[@"divisor"] doubleValue];
    [__abbrFormatter setLocale:[NSLocale currentLocale]]; //user might change locale while the app is sleeping
    [__abbrFormatter setPositiveSuffix:abbreviation[@"suffix"]];
    [__abbrFormatter setNegativeSuffix:abbreviation[@"suffix"]];

    return [__abbrFormatter stringFromNumber:@(value)];
}
@end

You now can call it like that

[NSNumberFormatter abbreviatedStringFromNumber:@(N)];
1
Bobj-C On

Swift 2.2 as Double extension:

extension Double {

var suffixNumber : String {

    get {

        var num = self

        let sign = ((num < 0) ? "-" : "" )

        num = fabs(num)

        if (num < 1000.0){
            return "\(sign)\(num)"
        }

        let exp:Int = Int(log10(num) / 3.0 )

        let units:[String] = ["K","M","G","T","P","E"]

        let roundedNum = round(10 * num / pow(1000.0,Double(exp))) / 10

        return "\(sign)\(roundedNum)\(units[exp-1])"
    }
}
}
0
Yunus Tek On

Use more than from 1 character for Turkish or other languages in Swift 5:

extension Int {
var abbreviated: String {
    let trSuffix = "B,Mn,Mr,T,Kt,Kn"
    let abbrev = trSuffix.split(separator: ",")
    return abbrev.enumerated().reversed().reduce(nil as String?) { accum, tuple in
        let factor = Double(self) / pow(10, Double(tuple.0 + 1) * 3)
        let format = (factor.truncatingRemainder(dividingBy: 1)  == 0 ? "%.0f%@" : "%.1f%@")
        return accum ?? (factor > 1 ? String(format: format, factor, String(tuple.1)) : nil)
            } ?? String(self)
    }
1
Flávio J Vieira Caetano On

I know there are already lots of answers and different ways, but this is how I solved it with a more functional approach:

extension Int {
    var abbreviated: String {
        let abbrev = "KMBTPE"
        return abbrev.characters
            .enumerated()
            .reversed()
            .reduce(nil as String?) { accum, tuple in
                let factor = Double(self) / pow(10, Double(tuple.0 + 1) * 3)
                let format = (factor - floor(factor) == 0 ? "%.0f%@" : "%.1f%@")
                return accum ?? (factor >= 1 ? String(format: format, factor, String(tuple.1)) : nil)
            } ?? String(self)
    }
}
7
AbdulMomen عبدالمؤمن On

Flávio J Vieira Caetano's answer converted to Swift 3.0

extension Int {
    var abbreviated: String {
        let abbrev = "KMBTPE"
        return abbrev.characters.enumerated().reversed().reduce(nil as String?) { accum, tuple in
            let factor = Double(self) / pow(10, Double(tuple.0 + 1) * 3)
            let format = (factor.truncatingRemainder(dividingBy: 1)  == 0 ? "%.0f%@" : "%.1f%@")
            return accum ?? (factor > 1 ? String(format: format, factor, String(tuple.1)) : nil)
            } ?? String(self)
    }
}