iOS конвертирует большие числа в меньший формат

Как я могу преобразовать все числа, которые состоят из более чем 3 цифр, в 4-значное или меньшее число?

Это именно то, что я имею в виду:

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

Округление не совсем важно, но дополнительный плюс.

Я просмотрел NSNumberFormatter, но не нашел правильного решения, и мне еще предстоит найти правильное решение здесь, на SO. Любая помощь приветствуется, спасибо!


person Kyle Begeman    schedule 16.08.2013    source источник
comment
Это может дать некоторое представление stackoverflow.com/questions/9345136/   -  person Pandiyan Cool    schedule 16.08.2013
comment
что такое К и М?? (я имею в виду, что это символ единицы) ??   -  person iPatel    schedule 16.08.2013
comment
iPatel: Как отображаются очки репутации SO   -  person Lithu T.V    schedule 16.08.2013
comment
@PandiyanCool Большое спасибо! Мне пришлось кое-что изменить, но эта ссылка дала мне базовый код для начала. Я опубликую ниже 2 функции, которые я создал, чтобы получить желаемый эффект   -  person Kyle Begeman    schedule 18.08.2013


Ответы (25)


-(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)]];
}

пример использования

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

[Обновить]

Быстрая версия ниже:

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])";
}

пример использования

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

Надеюсь, поможет

person Luca Iaco    schedule 25.04.2014
comment
Есть ли способ изменить этот метод, чтобы включить отрицательные числа? - person JH95; 13.06.2014
comment
@JakeHoskins Привет, вы можете сохранить знак числа с чем-то вроде: int sign = ( (n < 0) ? -1 : (n > 0) ? 1 : 0 );, затем num = labs(num), наконец, поставить символ знака - перед номером суффикса, проверяя значение знака - person Luca Iaco; 13.06.2014
comment
@JakeHoskins я отредактировал ответ с поддержкой отрицательных чисел;) - person Luca Iaco; 13.06.2014
comment
Можете ли вы преобразовать это в быстрый метод? Мне нужно показать как положительные, так и отрицательные значения, но пока я не могу заставить это работать с отрицательными значениями в быстром режиме. - person Sal Aldana; 18.09.2015
comment
разве это не 10 Base log10f(num) вместо log(num)? - person DareDevil; 07.05.2016
comment
@DareDevil да, это так. В данный момент я не могу проверить еще раз, но в то время функция log() была похожа на псевдоним log10() в Objective-C. Он может быть получен из используемого компилятора. Вместо этого в Swift мне пришлось указать функцию log10(), потому что log() была не 10-базовой, а e-базовой. - person Luca Iaco; 08.05.2016
comment
Это не работает в 999999 (которое становится 1000.0K). - person mishimay; 19.07.2016
comment
Числа ниже 100 представлены с .0. Я попытался перебрать некоторые числа: let numbers: [NSNumber] = [999999, -1, 0, 1, 2, 3, 9, 10, 29, 99, 1000, 125000, 125755, 1235690, 999999999, 124679] некоторые числа округляются вверх, некоторые имеют 0, а другие нет. - person zardon; 22.05.2020

Вот моя версия! Благодаря предыдущим ответам. Цели этой версии:

  • Иметь лучший пороговый контроль, потому что мелкие детали важнее, чем детали очень больших чисел.
  • Используйте как можно больше NSNumberFormatter, чтобы избежать проблем с местоположением (например, запятая вместо точки во французском языке).
  • Избегайте «.0» и хорошо округляемых чисел, которые можно настроить с помощью NSNumberFormatterRoundingMode.

Вы можете использовать все замечательные NSNumberFormatter опции для удовлетворения ваших потребностей, см. Справочник по классу NSNumberFormatter

Код (суть):

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

Результат :

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
person gbitaudeau    schedule 19.02.2016
comment
Отличный ответ, функция порогов очень удобна. Я также нашел это полезным: numFormatter.maximumFractionDigits = Int(value).description.characters.count > 2 ? 0 : 1 когда я поднял пороги, чтобы они соответствовали делителям во всех случаях. - person Jarrod Smith; 28.07.2016
comment
Некоторые числа, такие как Value : 125000 -> 0.1M и Value : 125755 -> 0.1M, неверны.; это должно читаться 125k и 126k соответственно (если округлить) - person zardon; 22.05.2020
comment
@zardon вам нужно только изменить порог, чтобы следовать вашим потребностям. Если вы хотите, чтобы 125000 использовало 125 КБ вместо 0,1 М, измените 3-й порог с 100_000,0 на 1_000_000,0 или значение, которое вы хотите... - person gbitaudeau; 02.06.2020
comment
Спасибо. Я выбрал другой метод подкласса NSNumberFormatter; но спасибо - person zardon; 02.06.2020

У меня была та же проблема, и в итоге я использовал подход Кайла, но, к сожалению, он ломается, когда используются такие числа, как 120000, показывая 12k вместо 120K, и я необходимо отображать небольшие числа, например: 1.1K вместо округления до 1K.

Итак, вот моя редакция оригинальной идеи Кайла:

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

Надеюсь, это поможет вам, ребята.

person Ricardo Corrêa de Souza    schedule 17.04.2014
comment
Приведите аббревиатуру к int в цикле for, чтобы избежать предупреждения: for (int i = (int)abbrev.count - 1; i >= 0; i--) { - person Rodrigo Pinto; 17.11.2016

Ответ Флавио Дж. Виейры Каэтано преобразован в 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)
    }
}
person AbdulMomen عبدالمؤمن    schedule 10.10.2016
comment
просто быстрое и потрясающее решение! - person Frank Eno; 14.11.2016
comment
Есть ли способ сделать это с помощью Measurement/NSMeasurement? - person user805981; 29.12.2016
comment
Простейшее решение, которое формирует самую чистую строку, т. е. удаляет десятичные знаки и округляет до наиболее подходящего сокращения. - person Dylan Moore; 07.03.2017
comment
как использовать это в основном сюжете @abdulMomen - person Marios; 26.07.2017
comment
@Мариос, я действительно не знаю - person AbdulMomen عبدالمؤمن; 26.07.2017
comment
Не работает с отрицательными числами и округляет числа; используя массив чисел, поскольку тест [-1000, -999999, 999999, 77777, 79578, 10088799] возвращает -1000, -999999, 1000.0K, 77.8K, 79.6K, 10.1M - person zardon; 02.08.2017
comment
@Mono.WTF Удаление .characters исправляет это. - person Vince; 13.03.2018

Я столкнулся с аналогичной проблемой, пытаясь отформатировать значения оси Y в диаграммах Шиноби. Это требовало использования NSNumberFormatter, поэтому я в конце концов придумал это

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

Чтобы получить отформатированное значение

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

Это решение не включает округление, но если вам (или кому-то еще) просто нужно что-то простое, это может сработать. Если вам нужна тысяча, а не миллион, вы меняете «M» на «K» в методе setPostiveFormat и меняете значение NSNumber в множителе на 0,001.

person Taidg Murphy    schedule 22.08.2013
comment
Это не локализуется. В некоторых локалях используются разные символы для разделителей тысяч или десятичных точек. В других локалях используется группировка, отличная от тысячи. - person Heath Borders; 02.09.2020

Вот два метода, которые я придумал, которые работают вместе, чтобы произвести желаемый эффект. Это также автоматически округлит. Это также укажет, сколько всего чисел будет видно, передав int dec.

Кроме того, в методе float to string вы можете изменить @"%.1f" на @"%.2f", @"%.3f" и т. д., чтобы указать, сколько видимых десятичных знаков следует отображать после запятой.

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

Надеюсь, это поможет всем, кому это нужно!

person Kyle Begeman    schedule 18.08.2013
comment
Это действительно здорово! Однако 39900, кажется, возвращает @"4K". Я думаю, что это как-то связано с методом floatToString:, но не могу это исправить. Логика перед вызовом floatToString: превращает number в 40.00000, но цикл while сокращает его до @"4". - person natenash203; 18.12.2013
comment
как написать это в быстром? - person Khodour.F; 28.05.2018
comment
Это не локализуется. В некоторых локалях используются разные символы для разделителей тысяч или десятичных точек. В других локалях используется группировка, отличная от тысячи. - person Heath Borders; 02.09.2020

Попробовав пару этих решений, Лука Лако, кажется, нашел его ближе всего, но я внес некоторые поправки в его метод, чтобы лучше контролировать количество отображаемых цифр (например, если вы хотите, чтобы 120,3 КБ было короче, вы можно ограничить до 120К). Кроме того, я добавил дополнительный шаг, который гарантирует, что число вроде 999 999 не будет отображаться как 1000,0K, а как 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;
}
person beebcon    schedule 03.06.2014
comment
После проверки/тестирования предыдущих ответов я использовал это. Работает как положено. Спасибо! Также я поставил onlyShowDecimalPlaceForNumbersUnder = 100, потому что хочу как 70669 - Result: 70.7K. - person KAMIKAZE; 17.09.2018

Я знаю, что уже есть много ответов и разных способов, но вот как я решил это с более функциональным подходом:

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)
    }
}
person Flávio J Vieira Caetano    schedule 30.09.2016
comment
Почему мы получаем 1000 как 1000 вместо 1К. Даже нам нужно сохранить Int64 вместо расширения Int - person Sri..; 04.11.2016

Swift-4 Doble extension – работает во всех случаях.

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

введите здесь описание изображения
введите здесь описание изображения
введите здесь описание изображения
введите здесь описание изображения< /а>

person Kiran Jasvanee    schedule 20.04.2018

Быстрая версия

Прямой перевод с версии Objective-C

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)
person coderek    schedule 04.06.2015

Вы можете использовать эту простую функцию, идея проста для понимания

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

Контрольная работа

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
person Linh    schedule 18.08.2016
comment
Кто за быструю версию? Не могу... Спасибо - person user2206906; 28.11.2016

Версия Swift 4.0 из ответа Фан Ван Линя

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])
}
person Bassem Qoulta    schedule 22.05.2018

Обновленный ответ для быстрого преобразования

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
    }
}
person Aadi007    schedule 18.08.2015

Вот обновленная версия ответа Luca Iaco, который работает с 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])";
}
person Josh    schedule 18.10.2017
comment
отличный ответ. Мне нужно было выяснить, как избавиться от десятичной дроби и .0 перед k. Если кому-то еще интересно, в операторе return для строки if (num ‹ 1000.0) return (sign)(num) просто введите num как int (sign)(Int(num)) и для числа более 1000 для оператора возврата, приведенного (roundedNum) как Int. Итак, (sign)(Int(roundedNum)(units[exp-1]). Обязательно используйте обратную косую черту ››\‹‹, которая есть в коде. По какой-то причине я не смог включить их в моем комментарии, но обязательно добавьте их. - person Lance Samaria; 06.11.2017

ответ gbitaudeau в 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))!
}

}

person Paul Lehn    schedule 21.12.2020

Если вас интересует форматирование количества байтов, в этой статье Мэтта Томпсона показано, как использовать встроенный в iOS/OSX NSByteCountFormatter

Существуют также встроенные средства форматирования для энергии. , масса, length и множество других.

Суть в том, что для большинства распространенных устройств вам не нужно писать какой-либо собственный код, поскольку Apple уже предоставила вам утомительную работу. Проверьте их онлайн-справку для NS[SomeUnit]Formatter, например. MKDistanceFormatter, NSDateIntervalFormatter или NSDateFormatter и т. д. ...

person verec    schedule 02.03.2016

Я использовал ответ gbitaudeau, чтобы создать эту категорию Objective-C для NSNumberFormatter, которую я использую в нашем проекте (Vero.co). Здесь экземпляр NSNumberFormatter создается только один раз для всего проекта.

@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

Теперь вы можете называть это так

[NSNumberFormatter abbreviatedStringFromNumber:@(N)];
person user726522    schedule 29.07.2016

Решение, совместимое со Swift 4 и Swift 5

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)
    }
}
person Pankaj Gaikar    schedule 29.06.2019

Немного более чистое решение:

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

}
person kasyanov-ms    schedule 01.06.2020

Следующий метод обрабатывает как положительные, так и отрицательные числа, в отличие от большинства решений здесь.

Это работает даже для валюты.

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];

Выход, как показано ниже

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' 

Я создал вышеуказанный метод на основе оригинального вдохновения @Kyle Begeman из ссылки, которой поделился @Pandiyan Cool. Спасибо @Jeff B за исходный код на Javascript по следующей ссылке. Есть ли способ округления чисел в удобный для читателя формат? (например, 1,1 000 долларов США)

person Rajesh    schedule 02.03.2016

Swift 2.2 как двойное расширение:

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])"
    }
}
}
person Bobj-C    schedule 01.07.2016
comment
‹code›public extension Int { func abbreviatedNumber() -> String { if abs(self) ‹ 1000 { return String(self) } let sign = self ‹ 0 ? - : пусть num = fabs(Double(self)) let exp = Int(log10(num) / 3.0 ) let unit = [K, M, G, T, P, E] let roundedNum: Double = round(10 * num / pow(1000.0, Double(exp))) / 10 return (sign)(roundedNum)(units[exp-1]) } },‹/code› - person PhoneyDeveloper; 11.04.2017

Используйте более одного символа для турецкого или других языков в 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)
    }
person Yunus T.    schedule 30.09.2019

Это похоже на недосмотр со стороны Apple, поскольку существует множество относительных форматов времени, метрик, дат, списков, людей, байтов и т. д., и т. д., но это довольно распространенный случай, особенно с социальными сетями, графиками и прочим. Ладно конец ругани..

Вот моя версия ниже, обертывающая NumberFormatter и обрабатывающая все значения Int, включая отрицательные, а также с учетом локали:

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

И тестовые случаи:

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

Единственное, что мне в нем не нравится, так это то, что он не локализован относительно того, что K, M, B или T означает на других языках. Высоко ценим всеобщее вдохновение.

person TruMan1    schedule 30.05.2021

Ребята, почему вам так сложно?

это может быть так просто:

-(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;
}
person Leo Cavalcante    schedule 04.02.2016
comment
Не для отрицательных чисел это не так. - person PhoneyDeveloper; 11.04.2017

person    schedule
comment
Волшебные числа, везде волшебные числа... 1, 3, 46... Боюсь использовать этот код :) - person Martin Berger; 10.08.2020