'iOS convert large numbers to smaller format
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!
Solution 1:[1]
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!
Solution 2:[2]
-(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
Solution 3:[3]
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
Solution 4:[4]
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.
Solution 5:[5]
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)
}
}
Solution 6:[6]
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 .
Solution 7:[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;
}
Solution 8:[8]
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)
}
}
Solution 9:[9]
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
}
}
Solution 10:[10]
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)
Solution 11:[11]
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
Solution 12:[12]
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])
}
Solution 13:[13]
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
}
}
Solution 14:[14]
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])";
}
Solution 15:[15]
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))
}
}
Solution 16:[16]
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))!
}
}
Solution 17:[17]
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
}
}
Solution 18:[18]
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 ...
Solution 19:[19]
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)];
Solution 20:[20]
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)
}
}
Solution 21:[21]
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)
Solution 22:[22]
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])"
}
}
}
Solution 23:[23]
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)
}
Solution 24:[24]
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.
Solution 25:[25]
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"
Solution 26:[26]
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;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow