Inspired by this question: Fraction class implemented in Objective-C, I have written what I feel is an improved version of the Fraction class in Objective-C.
As per the tips in this answer, the class is immutable (and a mutable subclass might eventually happen). Besides improving on the existing functionality and changing to an immutable object, I also added the necessary methods for comparing fractions to other objects, other fractions, and importantly adding the compare:
method so that the fractions can be sorted!
Fraction.h
#import <Foundation/Foundation.h>
@interface Fraction : NSObject
/*
An immutable class for representing and doing math
with rational numbers represented as fractions
with a numerator and denominator part.
*/
@property (readonly) NSInteger numerator;
@property (readonly) NSInteger denominator;
- (instancetype)initWithNumerator:(NSInteger)numerator
denominator:(NSInteger)denominator;
+ (instancetype)notANumber;
+ (instancetype)zero;
+ (instancetype)one;
+ (instancetype)fractionWithInteger:(NSInteger)integer;
+ (instancetype)fractionWithReciprocalOf:(NSInteger)integer;
+ (instancetype)fractionWithNumerator:(NSInteger)numerator
denominator:(NSInteger)denominator;
+ (instancetype)fractionWithFraction:(Fraction *)fraction;
// TODO: init with floating points.
- (instancetype)fractionByAddingFraction:(Fraction *)fraction;
- (instancetype)fractionBySubtractingFraction:(Fraction *)fraction;
- (instancetype)fractionByMultiplyingByFraction:(Fraction *)fraction;
- (instancetype)fractionByDividingByFraction:(Fraction *)fraction;
- (instancetype)reduced;
- (instancetype)reciprocal;
- (double)doubleValue;
- (float)floatValue;
- (int)intValue;
- (long)longValue;
- (NSInteger)integerValue;
- (BOOL)isEqualToFraction:(Fraction *)fraction;
- (NSComparisonResult)compare:(Fraction *)fraction;
@end
@interface NSNumber(Fraction)
+ (instancetype)numberWithFraction:(Fraction *)fraction;
@end
Fraction.m
#import "Fraction.h"
NSInteger greatestCommonDenominator(NSInteger first, NSInteger second) {
return second == 0 ?
first : greatestCommonDenominator(second, first % second);
}
@implementation Fraction {
BOOL _isNotANumber;
NSInteger _numerator;
NSInteger _denominator;
}
// Init methods
- (instancetype)initWithNumerator:(NSInteger)numerator
denominator:(NSInteger)denominator {
self = [super init];
if (self) {
_isNotANumber = (denominator == 0);
_numerator = numerator;
_denominator = denominator;
}
return self;
}
- (instancetype)init {
return [self initWithNumerator:0 denominator:1];
}
// Factory methods
+ (instancetype)notANumber {
return [[self alloc] initWithNumerator:0 denominator:0];
}
+ (instancetype)zero {
return [[self alloc] initWithNumerator:0 denominator:1];
}
+ (instancetype)one {
return [[self alloc] initWithNumerator:1 denominator:1];
}
+ (instancetype)fractionWithInteger:(NSInteger)integer {
return [[self alloc] initWithNumerator:integer denominator:1];
}
+ (instancetype)fractionWithReciprocalOf:(NSInteger)integer {
return [[self alloc] initWithNumerator:1 denominator:integer];
}
+ (instancetype)fractionWithNumerator:(NSInteger)numerator
denominator:(NSInteger)denominator {
return [[self alloc] initWithNumerator:numerator
denominator:denominator];
}
+ (instancetype)fractionWithFraction:(Fraction *)fraction {
return [[self alloc] initWithNumerator:fraction.numerator
denominator:fraction.denominator];
}
// Math operations
- (instancetype)fractionByAddingFraction:(Fraction *)fraction {
if (_isNotANumber || fraction->_isNotANumber) {
return [[self class] notANumber];
} else {
NSInteger newNumerator = (self.numerator * fraction.denominator) +
(self.denominator * fraction.denominator);
NSInteger newDenominator = self.denominator * fraction.denominator;
Fraction *newFraction =
[[self class] fractionWithNumerator:newNumerator
denominator:newDenominator];
return [newFraction reduced];
}
}
- (instancetype)fractionBySubtractingFraction:(Fraction *)fraction {
if (_isNotANumber || fraction->_isNotANumber) {
return [[self class] notANumber];
} else {
NSInteger newNumerator = (self.numerator * fraction.denominator) -
(self.denominator * fraction.denominator);
NSInteger newDenominator = self.denominator * fraction.denominator;
Fraction *newFraction =
[[self class] fractionWithNumerator:newNumerator
denominator:newDenominator];
return [newFraction reduced];
}
}
- (instancetype)fractionByMultiplyingByFraction:(Fraction *)fraction {
if (_isNotANumber || fraction->_isNotANumber) {
return [[self class] notANumber];
} else {
NSInteger newNumerator = self.numerator * fraction.numerator;
NSInteger newDenominator = self.denominator * fraction.denominator;
Fraction *newFraction =
[[self class] fractionWithNumerator:newNumerator
denominator:newDenominator];
return [newFraction reduced];
}
}
- (instancetype)fractionByDividingByFraction:(Fraction *)fraction {
Fraction *divisor = [fraction reciprocal];
return [self fractionByMultiplyingByFraction:divisor];
}
- (instancetype)reduced {
if (_isNotANumber) {
return [[self class] notANumber];
} else {
NSInteger gcd = greatestCommonDenominator(self.numerator,
self.denominator);
NSInteger newNumerator = self.numerator / gcd;
NSInteger newDenominator = self.denominator / gcd;
return [[self class] fractionWithNumerator:newNumerator
denominator:newDenominator];
}
}
- (instancetype)reciprocal {
if (_isNotANumber) {
return [[self class] notANumber];
} else {
return [[self class] fractionWithNumerator:self.denominator
denominator:self.numerator];
}
}
// Property methods
- (NSInteger)numerator {
return _isNotANumber ? NAN : _numerator;
}
- (NSInteger)denominator {
return _isNotANumber ? NAN : _denominator;
}
// "extractor" methods?
- (double)doubleValue {
return _isNotANumber ? NAN : (double)self.numerator / (double)self.denominator;
}
- (float)floatValue {
return _isNotANumber ? NAN : (float)self.numerator / (float)self.denominator;
}
- (int)intValue {
return _isNotANumber ? NAN : (int)self.numerator / (int)self.denominator;
}
- (long)longValue {
return _isNotANumber ? NAN : (long)self.numerator / (long)self.denominator;
}
- (NSInteger)integerValue {
return _isNotANumber ? NAN : self.numerator / self.denominator;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%li/%li",
(long)self.numerator,
(long)self.denominator
];
}
// Comparison methods
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
} if ([object respondsToSelector:@selector(doubleValue)]) {
return [self doubleValue] == [object doubleValue];
} else {
return NO;
}
}
- (BOOL)isEqualToFraction:(Fraction *)fraction {
if (self == fraction) {
return YES;
} else {
return [self doubleValue] == [fraction doubleValue];
}
}
- (NSComparisonResult)compare:(Fraction *)fraction {
double left = [self doubleValue];
double right = [fraction doubleValue];
if (left > right) {
return NSOrderedDescending;
} else if (left < right) {
return NSOrderedAscending;
} else {
return NSOrderedSame;
}
}
@end
@implementation NSNumber(Fraction)
+ (instancetype)numberWithFraction:(Fraction *)fraction {
return [self numberWithDouble:[fraction doubleValue]];
}
@end
I didn't feel the need to provide an NSDecimalNumber
category, as it is a subclass of NSNumber
, so by simply having the NSNumber
category here, the numberWithFraction:
method can be used to instantiate either an NSNumber
or NSDecimalNumber
object.