How to add all decimal numbers in an NSMutableArray
Asked Answered
M

3

1

I have a NSMutableArray which have some NSDecimalNumber in it, like (500,50.80,70,8000)

Now I want to add all those decimal numbers together.

I've tried to use

for (NSDecimalNumber *number in self.numbersArray)
{
    NSDecimal *sum += [number decimalValue]
}

But failed.

Murvyn answered 13/3, 2013 at 6:42 Comment(3)
if your array contains dictionaries then apple provides a single line execution NSNumber *amountSum = [YourArray valueForKeyPath:@"@sum.amount"]; NSLog(@"%@",amountSum);Indeed
for more info you can go through this link developer.apple.com/library/ios/#documentation/cocoa/conceptual/…Indeed
@Manohar: You can use KVC also for a plain array: [myArray valueForKeyPath:@"@sum.self"].Mastery
H
3

Use - (NSDecimalNumber *)decimalNumberByAdding:(NSDecimalNumber *)decimalNumber

Take a look at NSDecimalNumber Class Reference

NSDecimalNumber *lNumber = [NSDecimalNumber zero];
for (NSDecimalNumber *number in self.numbersArray)
{
    lNumber = [lNumber decimalNumberByAdding:number];
}
Hyperon answered 13/3, 2013 at 6:53 Comment(0)
M
6

A simple way to add all NSNumbers in an array is (similar to what @Mahonor said in a comment):

NSArray *myArray = ... // array of NSNumber (or NSDecimalNumber) objects
NSNumber *sum = [myArray valueForKeyPath:@"@sum.self"];

Contrary to what the Collection Operators: sum states, the numbers in the array are not converted to double, but to NSDecimal. Therefore, no precision is lost when adding decimal numbers. Even NSNumber objects which are not decimal numbers are converted to NSDecimal for the addition. The result of the summation is an instance of NSDecimalValue.

I verified (or tried to) that in two different ways. First, I ran this code

NSNumber *a = [NSNumber numberWithDouble:1.2];
NSNumber *b = [NSDecimalNumber decimalNumberWithString:@"-5.7"];
NSArray *myArray = @[a, b];
id sum = [myArray valueForKeyPath:@"@sum.self"];

and activated Objective-C message logging by setting the environment variable "NSObjCMessageLoggingEnabled=YES". As can be seen in the created "/tmp/msgSends-NNNN" file, decimalNumber (and not doubleValue) is sent to both number objects.

Second, I created a custom class implementing both decimalValue and doubleValue, and applied @sum.self to an array of objects of the custom class:

@interface MyClass : NSObject
@property (nonatomic, assign) double value;
@end

@implementation MyClass

- (NSDecimal)decimalValue
{
    return [[NSNumber numberWithDouble:self.value] decimalValue];
}

- (double)doubleValue
{
    return self.value;
}

@end

MyClass *a = [MyClass new]; a.value = 1.2;
MyClass *b = [MyClass new]; b.value = -5.7;
NSArray *myArray = @[a, b];
id sum = [myArray valueForKeyPath:@"@sum.self"];

By setting breakpoints in both methods, it is seen that only decimalValue is used for the summation (and valueForKeyPath:@"@sum.self" throws an exception if the class does not implement decimalValue).

One can also see that decimalValue is called from

-[NSArray(NSKeyValueCoding) _sumForKeyPath:]

and the assembler code for this method shows that NSDecimalAdd is uses to add the numbers.

Mastery answered 13/3, 2013 at 10:54 Comment(1)
Very nice research! Always great to learn something new about the inner workings.Sapsucker
H
3

Use - (NSDecimalNumber *)decimalNumberByAdding:(NSDecimalNumber *)decimalNumber

Take a look at NSDecimalNumber Class Reference

NSDecimalNumber *lNumber = [NSDecimalNumber zero];
for (NSDecimalNumber *number in self.numbersArray)
{
    lNumber = [lNumber decimalNumberByAdding:number];
}
Hyperon answered 13/3, 2013 at 6:53 Comment(0)
S
1

Manohar's suggestion in the comments is not bad. You can indeed use KVC collection operators to make a one-liner out of this: [myArray valueForKeyPath:@"@sum.doubleValue"];, but you potentially lose precision (depending on the numbers you have stored).

You're basically looking for "reduce" functionality; you need to chain calls to decimalNumberByAdding: so that each call has the succeeding element of the array as its argument. Doing this on an NSArray is easy enough, using performSelector:withObject:

@implementation NSArray (Reduce)
- (id)reduceUsingSelector: (SEL)sel
{
    id res = [self objectAtIndex:0];
    for( id obj in [self subarrayWithRange:(NSRange){1, [self count]-1}] ){
        res = [res performSelector:sel withObject:obj];
    }

    return res;
}
@end

Use this like so: NSDecimalNumber * sum = [myArray reduceUsingSelector:@selector(decimalNumberByAdding:)];

The code you have isn't successful because NSDecimal is a struct, not an object; it shouldn't be declared as a pointer, and if it wasn't, you wouldn't be able to add it. That's not the right route to a solution.

Sapsucker answered 13/3, 2013 at 7:11 Comment(8)
I have fixed the length parameter for the subarray, I hope that is OK.Mastery
Remark: [myArray valueForKeyPath:@"@sum.self"] works also and returns a NSDecimalNumber object, but I don't know if NSDecimalNumber is used for all intermediate calculations.Mastery
@MartinR: Collection Operators: sum states that each number is transformed to a double. Also it doesnt mentioned NSDecimalNumber, I would assume, that it is done in the same way.Killer
@vikingosegundo: OK, then using @sum is prone to precision loss. I just noticed that it always seems to return an instance of NSDecimalNumber.Mastery
@vikingosegundo: I have done some testing both with a custom class and with subclassing (using some trickery) NSNumber and NSDecimalNumber. It seems that [myArray valueForKeyPath:@"@sum.self"] sends decimalValue: to each object in the array, contrary to what the documentation says, and uses (as it seems from the assembler code) NSDecimalAdd to add the values.Mastery
@MartinR: that would be great. can you publish your code? I'd like to see it. Also you should post [myArray valueForKeyPath:@"@sum.self"] as an answer.Killer
@MartinR: I did some isa swizzeling and came to the same resultKiller
@vikingosegundo: That is exactly what I did (using object_setClass)! One can also use "Objective-C message tracing" to verify this, I have written an answer containing this information.Mastery

© 2022 - 2024 — McMap. All rights reserved.