66

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!

Rajesh
  • 850
  • 9
  • 18
Kyle Begeman
  • 7,169
  • 9
  • 40
  • 58

26 Answers26

62
-(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

saiday
  • 1,290
  • 12
  • 25
Luca Iaco
  • 3,387
  • 1
  • 19
  • 20
  • Is there a way of altering this method to include negative numbers? – JH95 Jun 13 '14 at 08:00
  • @JakeHoskins Hi, you could keep the number sign with something like: `int sign = ( (n < 0) ? -1 : (n > 0) ? 1 : 0 );` then `num = labs(num)`, finally put the `-` sign character before the suffixed number checking the sign value – Luca Iaco Jun 13 '14 at 14:42
  • 1
    @JakeHoskins i've edited the answer with negative number support ;) – Luca Iaco Jun 13 '14 at 16:52
  • 1
    Can you convert this to a swift method? I need to show both positive and negative values but so far I can't seem to get this to work with negative values in swift – Sal Aldana Sep 18 '15 at 19:10
  • is not it 10 Base log10f(num) instead of log(num)? – DareDevil May 06 '16 at 22:22
  • @DareDevil yes it is. In this moment I can't check again but, at that time, the log() function was like an alias of log10(), in Objective-C. It could be derived from the used compiler. In swift instead, I had to specify the log10() function because the log() one wasn't 10 base but e base. – Luca Iaco May 08 '16 at 12:29
  • This doesn't work in 999999 (which becomes "1000.0K"). – mishimay Jul 19 '16 at 09:53
  • Numbers below 100 are presented with .0. I tried looping through some numbers: `let numbers: [NSNumber] = [999999, -1, 0, 1, 2, 3, 9, 10, 29, 99, 1000, 125000, 125755, 1235690, 999999999, 124679]` some numbers round up, some have a .0, whilst others don't – zardon May 21 '20 at 21:13
46

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
gbitaudeau
  • 2,207
  • 1
  • 17
  • 13
  • Great answer, the thresholds feature is very handy. I also found this useful: `numFormatter.maximumFractionDigits = Int(value).description.characters.count > 2 ? 0 : 1` when I bumped the thresholds up to match the divisors in all cases. – sleep Jul 28 '16 at 01:46
  • Some numbers like Value : `125000 -> 0.1M` and `Value : 125755 -> 0.1M` are not right.; this should read 125k and 126k respectively (if rounding up) – zardon May 21 '20 at 21:19
  • 1
    @zardon you only need to change the threshold to follow your needs. If you want 125000 to use 125K instead of 0.1M, change the 3rd threshold from 100_000.0 to 1_000_000.0 or the value you want ... – gbitaudeau Jun 02 '20 at 08:30
  • Thanks. I chose a different method sub-classing NSNumberFormatter; but thanks – zardon Jun 02 '20 at 10:50
  • I'd suggest concatenating the `abbreviation.suffix` with the original suffix rather than replacing it (e.g. `numFormatter.positiveSuffix = abbreviation.suffix + " " + numFormatter.positiveSuffix`). Otherwise in currency formatting you will lose the currency symbol for currencies where the symbol is in the suffix (e.g. €). – Maciej Trybiło May 17 '23 at 19:26
35

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.

30

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)
    }
}
Community
  • 1
  • 1
12

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 .

Taidg Murphy
  • 129
  • 3
  • This doesn't localize. Some locales use different symbols for thousands separators or decimal points. Other locales use different grouping than thousands. – Heath Borders Sep 02 '20 at 16:16
9

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!

Kyle Begeman
  • 7,169
  • 9
  • 40
  • 58
  • 1
    This is really great! However, 39900 seems to return `@"4K"`. I think it has something to do with the `floatToString:` method but can't seem to fix it. The logic before the call to `floatToString:` turns `number` into 40.00000 but the while loop cuts it down to `@"4"`. – natenash203 Dec 18 '13 at 19:44
  • how to write this in swift ? – Kodr.F May 28 '18 at 11:19
  • This doesn't localize. Some locales use different symbols for thousands separators or decimal points. Other locales use different grouping than thousands. – Heath Borders Sep 02 '20 at 16:15
7

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;
}
beebcon
  • 6,893
  • 5
  • 25
  • 27
  • After checking/testing previous answers I've used this. Works as expected. Thank you! Also, I set `onlyShowDecimalPlaceForNumbersUnder = 100`, because I want like `70669 - Result: 70.7K`. – KAMIKAZE Sep 17 '18 at 15:29
7

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)
    }
}
  • 1
    Why we getting 1000 as 1000 instead of 1K. Even we need to Keep Int64 instead of Int extension – Sri.. Nov 04 '16 at 12:23
5

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
4

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)
coderek
  • 1,860
  • 14
  • 20
4

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
Linh
  • 57,942
  • 23
  • 262
  • 279
3

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])
}
Bassem Qoulta
  • 341
  • 1
  • 7
  • 17
2

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
    }
}
estemendoza
  • 3,023
  • 5
  • 31
  • 51
Aadi007
  • 247
  • 2
  • 5
2

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])";
}
Josh
  • 254
  • 3
  • 19
  • 1
    great answer. I had to figure out how to get rid of the **decimal and .0** before the **k**. If anyone else is wondering, in the return statement for the line if (num < 1000.0) return "\(sign)\(num)" just cast num as an int **"\(sign)\(Int(num))"** and for numbers over 1000 for the return statement cast (roundedNum) as an Int. So **"\(sign)\(Int(roundedNum)\(units[exp-1])"**. Be sure to use the backslashes >>\<< that are in the code. For some reason I couldn't include them in my comment but definitely add them. – Lance Samaria Nov 05 '17 at 22:06
2

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

}
kasyanov-ms
  • 431
  • 4
  • 12
2

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

}

Paul Lehn
  • 3,202
  • 1
  • 24
  • 29
1
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
    }
}
Community
  • 1
  • 1
Zoyt
  • 4,809
  • 6
  • 33
  • 45
1

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

verec
  • 5,224
  • 5
  • 33
  • 40
1

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)];
user726522
  • 276
  • 2
  • 4
1

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)
    }
}
Pankaj Gaikar
  • 2,357
  • 1
  • 23
  • 29
0

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)

Community
  • 1
  • 1
Rajesh
  • 850
  • 9
  • 18
0

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])"
    }
}
}
Bobj-C
  • 5,276
  • 9
  • 47
  • 83
  • public extension Int { func abbreviatedNumber() -> String { if abs(self) < 1000 { return String(self) } let sign = self < 0 ? "-" : "" let num = fabs(Double(self)) let exp = Int(log10(num) / 3.0 ) let units = ["K", "M", "G", "T", "P", "E"] let roundedNum: Double = round(10 * num / pow(1000.0, Double(exp))) / 10 return "\(sign)\(roundedNum)\(units[exp-1])" } }, – PhoneyDeveloper Apr 10 '17 at 22:05
0

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)
    }
Yunus T.
  • 569
  • 7
  • 13
0

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.

TruMan1
  • 33,665
  • 59
  • 184
  • 335
0

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"
dariowskii
  • 285
  • 2
  • 9
-1

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;
}
Leonardo Cavalcante
  • 1,274
  • 1
  • 16
  • 26