So this was originally inspired by @nhgrif's Int extension for translating integer to plain English, however the code is mainly a port of @rolfl's Converting a number to the text representation; taking into consideration the reviews on both answers as well.
NumberToText.h
:
#import <Foundation/Foundation.h>
@interface NSString (NumberToText)
- (instancetype)initWithInteger:(NSInteger)number;
- (instancetype)initWithInteger:(NSInteger)number negativeSign:(NSString *)negativeSign;
+ (instancetype)stringWithInteger:(NSInteger)number;
+ (instancetype)stringWithInteger:(NSInteger)number negativeSign:(NSString *)negativeSign;
+ (NSString *)tripleAsText:(NSInteger)value;
@end
NumberToText.m
:
#import "NumberToText.h"
#define DIGIT_LENGTH 19
#define TENS_LENGTH 9
#define MODIFIER_LENGTH 7
NSString * digitName(NSInteger number) {
switch (number) {
case 1: return @"one";
case 2: return @"two";
case 3: return @"three";
case 4: return @"four";
case 5: return @"five";
case 6: return @"six";
case 7: return @"seven";
case 8: return @"eight";
case 9: return @"nine";
case 10: return @"ten";
case 11: return @"eleven";
case 12: return @"twelve";
case 13: return @"thirteen";
case 14: return @"fourteen";
case 15: return @"fifteen";
case 16: return @"sixteen";
case 17: return @"seventeen";
case 18: return @"eighteen";
case DIGIT_LENGTH: return @"nineteen";
default: return @"";
}
}
NSString * tensName(NSInteger number) {
switch (number) {
case 0: return @"";
case 1: return @"ten";
case 2: return @"twenty";
case 3: return @"thirty";
case 4: return @"fourty";
case 5: return @"fifty";
case 6: return @"sixty";
case 7: return @"seventy";
case 8: return @"eighty";
case TENS_LENGTH: return @"ninety";
default: return @"";
}
}
NSString * modifierName(NSInteger number) {
switch (number) {
case 1: return @"thousand";
case 2: return @"million";
case 3: return @"billion";
case 4: return @"trillion";
case 5: return @"quadrillion";
case 6: return @"quintillion";
case MODIFIER_LENGTH: return @"sextillion";
default: return @"";
}
}
@implementation NSString (NumberToText)
- (instancetype)initWithInteger:(NSInteger)number {
return [self initWithInteger:number negativeSign:NULL];
}
+ (instancetype)stringWithInteger:(NSInteger)number {
return [[self alloc] initWithInteger:number];
}
+ (instancetype)stringWithInteger:(NSInteger)number negativeSign:(NSString *)negativeSign {
return [[self alloc] initWithInteger:number negativeSign:negativeSign];
}
/**
* Convert any value from 0 to 999 inclusive, to a string.
* @param value The value to convert.
* @param and whether to use the word 'and' in the output.
* @return a String representation of the value.
*/
+ (NSString *)tripleAsText:(NSInteger) value {
int subhun = value % 100;
long hun = value / 100;
NSMutableString *string = [[NSMutableString alloc] init];
if (hun > 0) {
[string appendString:digitName(hun)];
[string appendString:@" hundred"];
if (subhun > 0) {
[string appendString:@" and "];
}
}
if (subhun < DIGIT_LENGTH) {
[string appendString:digitName(subhun)];
} else {
int tens = subhun / 10;
int units = subhun % 10;
[string appendString:tensName(tens)];
if (units>0) {
[string appendString:@" "];
}
[string appendString:digitName(units)];
}
return string;
}
/**
* Convert any long input value to a text representation
* @param value The value to convert
* @param useand true if you want to use the word 'and' in the text (eleven thousand and thirteen)
* @param negname
* @return Number as a text representation
*/
- (instancetype)initWithInteger:(NSInteger)number negativeSign:(NSString *)negativeSign {
NSMutableString *string = [[NSMutableString alloc] init];
if (number < 0) {
if (!negativeSign) {
negativeSign = @"negative";
}
number = ABS(number);
}
else {
negativeSign = nil;
}
if (negativeSign) {
[string appendString:negativeSign];
[string appendString:@" "];
}
if (0 == number) {
return digitName(0);
}
// break the value down in to sets of three digits (thousands).
NSMutableArray *thous = [NSMutableArray array];
for (int i = 0; i < MODIFIER_LENGTH; ++i) {
[thous addObject: modifierName(i)];
}
// do not make negative numbers positive, to handle Long.MIN_VALUE
int scale = 0;
while (number != 0) {
// use abs to convert thousand-groups to positive, if needed.
thous[scale] = @(ABS(number % 1000));
number /= 1000;
++scale;
}
Boolean first = true;
while (--scale > 0) {
if (!first) {
[string appendString:@", "];
}
first = false;
if (thous[scale] > 0) {
[string appendString:[[self class] tripleAsText:[thous[scale] integerValue]]];
[string appendString:@" "];
[string appendString:modifierName(scale)];
}
}
if (!first && thous[0] != 0) {
[string appendString:@" and "];
}
[string appendString:[[self class] tripleAsText:[thous[0] integerValue]]];
return string;
}
@end
main.m
:
#import "NumberToText.h"
int main(void)
{
@autoreleasepool
{
NSLog(@"%ld: %@", LONG_MIN, [NSString stringWithInteger:LONG_MIN]);
NSLog(@"%ld: %@", LONG_MAX, [NSString stringWithInteger:LONG_MAX]);
}
}
Please feel free to review anything you see could be better in the code.