Last active
April 18, 2019 02:52
-
-
Save fjolnir/cd72ea39be1476023adf to your computer and use it in GitHub Desktop.
Abbreviates numbers like: 1234567 to 1m 234k 567, 1.23m, 123万4千5百67, 123.4万 etc..
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import Foundation; | |
typedef NS_ENUM(NSUInteger, LENumberFormatterAbbreviationStyle) { | |
kLEAbbreviateShort, // 2.5m | |
kLEAbbreviateNormal // 2m 5k | |
}; | |
@interface LENumberFormatter : NSNumberFormatter | |
@property(nonatomic) BOOL abbreviateLargeNumbers; | |
@property(nonatomic) LENumberFormatterAbbreviationStyle abbreviationStyle; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import ObjectiveC.message; | |
#import "LENumberFormatter.h" | |
@implementation LENumberFormatter | |
- (instancetype)init | |
{ | |
if((self = [super init])) | |
self.abbreviationStyle = [self _usingKanjiNumbers] | |
? kLEAbbreviateNormal | |
: kLEAbbreviateShort; | |
return self; | |
} | |
- (NSString *)stringForObjectValue:(id const)aObj | |
{ | |
if(!_abbreviateLargeNumbers || ![aObj isKindOfClass:[NSNumber class]]) | |
return [super stringForObjectValue:aObj]; | |
// Copy ourselves to format the partial digits using the settings on self (minus the currency symbol) | |
LENumberFormatter * const partialFormatter = [self copy]; | |
partialFormatter.currencySymbol = @""; | |
if(_abbreviationStyle == kLEAbbreviateNormal) | |
partialFormatter.maximumFractionDigits = 0; | |
NSString *(^const partialFormat)(NSNumber*) = ^(NSNumber *num) { | |
NSString *(*superImp)(struct objc_super*,SEL,NSNumber*) = (void*)&objc_msgSendSuper; | |
return superImp(&(struct objc_super) { partialFormatter, self.superclass }, _cmd, num); | |
}; | |
BOOL const usingShortFormat = _abbreviationStyle == kLEAbbreviateShort; | |
double n = fabs([aObj doubleValue]); | |
NSDictionary * const separators = [self _localizedGroupingSeparators]; | |
NSArray * const separatorExponents = [separators.allKeys sortedArrayUsingSelector:@selector(compare:)]; | |
NSMutableString * const result = [NSMutableString new]; | |
NSUInteger significantDigits = 0; | |
NSNumber *lastExp = nil; | |
for(NSNumber *exp in separatorExponents.reverseObjectEnumerator) { | |
double divisor = pow(10, exp.shortValue); | |
if(divisor > n) | |
continue; | |
if(lastExp) | |
significantDigits += lastExp.doubleValue - exp.doubleValue; | |
lastExp = exp; | |
if(self.usesSignificantDigits && significantDigits >= self.maximumSignificantDigits) | |
break; | |
double const partialNum = usingShortFormat | |
? n/divisor | |
: floor(n/divisor); | |
NSString * const digits = [self _groupRecursively] && ![exp isEqual:@0] | |
? [partialFormatter stringFromNumber:@(partialNum)] | |
: partialFormat(@(partialNum)); | |
[result appendFormat:@"%@%@", digits, separators[exp]]; | |
n = fmod(n, divisor); | |
if(usingShortFormat) | |
break; // Just use a float+first hit | |
// If we make it here, partialNum is integral and we can use log10 to find the number of digits | |
significantDigits += log10(partialNum) + 1; | |
partialFormatter.maximumSignificantDigits -= digits.length; | |
} | |
if(n > 0 | |
&& !usingShortFormat | |
&& (!self.usesSignificantDigits || significantDigits < self.maximumSignificantDigits)) | |
{ | |
partialFormatter.maximumFractionDigits = self.maximumFractionDigits; | |
[result appendString:partialFormat(@(n))]; | |
} | |
if(result.length > 0) { | |
// If our grouping separators contain spaces, we need to trim any trailing ones | |
[result replaceOccurrencesOfString:@"\\s+$" withString:@"" | |
options:NSRegularExpressionSearch | |
range:(NSRange) { 0, result.length }]; | |
if([aObj compare:@0] == NSOrderedAscending) { | |
if(self.negativePrefix) [result insertString:self.negativePrefix atIndex:0]; | |
if(self.negativeSuffix) [result appendString:self.negativeSuffix]; | |
} else { | |
if(self.positivePrefix) [result insertString:self.positivePrefix atIndex:0]; | |
if(self.positiveSuffix) [result appendString:self.positiveSuffix]; | |
} | |
return result; | |
} else | |
return [super stringForObjectValue:aObj]; | |
} | |
- (NSDictionary *)_localizedGroupingSeparators | |
{ | |
if(self._usingKanjiNumbers) | |
return @{ @2: @"百", @3: @"千", @4: @"万", @8: @"億" }; | |
else { | |
NSBundle * const bundle = [NSBundle bundleForClass:self.class]; | |
return @{ | |
@3: [bundle localizedStringForKey:@"thousandSuffix" value:@"k " table:nil], | |
@6: [bundle localizedStringForKey:@"millionSuffix" value:@"m " table:nil] | |
}; | |
} | |
} | |
- (BOOL)_usingKanjiNumbers | |
{ | |
return [self.locale.localeIdentifier rangeOfString:@"^(ja|zh)_" | |
options:NSRegularExpressionSearch].location != NSNotFound; | |
} | |
- (BOOL)_groupRecursively | |
{ | |
// Return _usingKanjiNumbers if you want: | |
// 12億3千4百56万7千8百90 | |
// Rather than: | |
// 1億2,3456万7千8百90 | |
return NO; | |
} | |
- (instancetype)copyWithZone:(NSZone * const)aZone | |
{ | |
LENumberFormatter * const copy = [super copyWithZone:aZone]; | |
copy.abbreviateLargeNumbers = _abbreviateLargeNumbers; | |
copy.abbreviationStyle = _abbreviationStyle; | |
return copy; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment