Какой правильный выбор между NSDecimal, NSDecimalNumber, CFNumber?

Я много читал о NSDecimal, NSNumber, NSNumberDecimal, CFNumber ... и это начинает казаться мне чем-то вроде джунглей.

По сути, я пытаюсь создать простой класс модели, который будет обрабатывать простые вычисления, например этот:

#import <Foundation/Foundation.h>


@interface Test : NSObject
{
    float rate;
    float amount;
    int duration;
}

- (float)capitalizedAmount;

@end

@implementation Test

- (float)capitalizedAmount {
    return (amount*pow((1.0+rate),duration));
}

@end

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

// This is just the desired behavior
// This evidently won't work with the previous class definition
Test *obj = [[Test alloc] init];
[NSNumber numberWithInt:10]
...
float r;
r = [obj performSelector:NSSelectorFromString(@"capitalizedAmount")];

Я понимаю, что это невозможно, что performSelector: вернет объект, и поэтому capitalizedAmount должен вернуть объект. Я читал кое-что о NSInvocation и соответствующей части в Objective-C Faq на comp.lang.

Я также понимаю, что мне следует использовать NSDecimalNumber, но я хотел бы знать две вещи:

  1. Допустимы ли накладные расходы на память и потеря производительности для несколько более сложного класса (только финансовые вычисления такого рода, показанные в UITableView)? У меня не очень большой опыт в C ...
  2. Не слишком ли привередливо и сложно использовать такие функции, как decimalNumberByAdding:? В Python было легко определить __add__ для использования операторов с объектами. Должен ли я получать значения с плавающей запятой из NSDecimalNumber, затем выполнять вычисления и возвращать результат, завернутый в NSDecimalNumber? Как бы вы справились с этой проблемой?

Ищу простое и красивое решение!

Еще один вопрос из той же области: CFBoolean объектная оболочка для BOOL в iPhone Core Foundation?

Спасибо большое за помощь!


person charlax    schedule 09.11.2009    source источник


Ответы (3)


Если вы имеете дело с финансовыми вычислениями, вам действительно следует использовать арифметику с основанием 10, чтобы избежать ошибок округления, которые могут возникнуть при использовании стандартных типов с плавающей запятой с основанием 2. Значит, это либо NSDecimal, либо NSDecimalNumber. А поскольку вы пишете объектно-ориентированный код, NSDecimalNumber - правильный выбор для вас.

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

К сожалению, вы не сможете избежать подобных decimalNumberByAdding:, поскольку Objective-C не поддерживает перегрузку операторов, как это делает C ++. Я согласен, что это делает ваш код несколько менее элегантным.

Один комментарий к опубликованному вами коду: r = [obj performSelector:NSSelectorFromString(@"capitalizedAmount")]; довольно нелегко. Либо

r = [obj performSelector:@selector(capitalizedAmount)];

или даже простой

r = [obj capitalizedAmount];

было бы лучше, если вам не нужен синтаксис NSSelectorFromString по какой-либо другой причине.

person Ole Begemann    schedule 10.11.2009
comment
Спасибо за ответ, мне действительно нужно создавать селекторы из строки. Подходит ли CFBoolean для упаковки BOOL? - person charlax; 10.11.2009
comment
В Какао вы должны использовать NSNumber для обертывания объекта BOOL. См. +[NSNumber numberWithBool:]. - person Ole Begemann; 10.11.2009

Оле прав в том, что вы должны использовать NSDecimal или NSDecimalNumber, чтобы избежать математических ошибок с плавающей запятой при выполнении финансовых расчетов. Однако я предлагаю использовать NSDecimal и его функции C, а не NSDecimalNumber. Расчеты NSDecimal могут быть намного быстрее, и, поскольку они избегают создания большого количества автоматически выпускаемых объектов, намного лучше с точки зрения использования памяти.

В качестве примера я протестировал математические операции для двух типов на моем MacBook Air:

NSDecimal

Additions per second: 3355476.75
Subtractions per second: 3866671.27
Multiplications per second: 3458770.51
Divisions per second: 276242.32

NSDecimalNumber

Additions per second: 676901.32
Subtractions per second: 671474.6
Multiplications per second: 720310.63
Divisions per second: 190249.33

Подразделения были единственной операцией, производительность которой не увеличилась примерно в пять раз при использовании NSDecimal по сравнению с NSDecimalNumber. Подобные улучшения производительности происходят и на iPhone. Это, наряду с экономией памяти, было причиной, по которой мы недавно переключили Core Plot на использование NSDecimal.

Единственная трудность, с которой вы столкнетесь, - это получение значений в типах NSDecimal и из них. Переход непосредственно к значениям с плавающей запятой и целочисленным значениям и обратно может потребовать использования NSDecimalNumber в качестве моста. Кроме того, если вы используете Core Data, вы будете хранить свои значения как NSDecimalNumbers, а не NSDecimals.

person Brad Larson    schedule 10.11.2009
comment
Спасибо, Брэд, за тест, который я уже видел и нашел очень интересным. Я планирую использовать NSDecimalNumber, поскольку я считаю их более простыми в использовании, и я буду использовать Core Data в ближайшем будущем. Кстати, мне нужна не такая высокая скорость, как ваша графическая библиотека! - person charlax; 10.11.2009
comment
Какой самый простой способ создать NSDecimal из числа с плавающей запятой или двойного? - person Paul; 15.03.2011
comment
@Paul - Посмотрите в файле CPUtilities.m платформы Core Plot некоторые вспомогательные функции, которые выполняют различные преобразования: code.google.com/p/core-plot/source/browse/framework/Source/. Лично я начал создавать экземпляры NSDecimalNumber с использованием строк формата, а затем получать от них decimalValue, потому что я немного параноидально отношусь к преобразованию из double в NSNumber в NSDecimal, которое происходит в подпрограммах Core Plot. - person Brad Larson; 15.03.2011
comment
Имейте в виду, что если вы хотите хранить эти данные и использовать Core Data, NSDecimalNumber - правильный выбор, несмотря на разницу в производительности. - person Jorge Ortiz; 01.06.2011

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

r = [obj performSelector:NSSelectorFromString(@"capitalizedAmount")];

KVC - это не просто отправка сообщений объектам с использованием строк. См. Руководство по программированию кодирования значений ключа.

Должен ли я получать значения с плавающей запятой из NSDecimalNumber, а затем выполнять вычисления и возвращать результат, завернутый в NSDecimalNumber?

Нет. Преобразование в двоичное число с плавающей запятой (_2 _ / _ 3_) из десятичного числа с плавающей запятой (NSDecimal / NSDecimalNumber) происходит с потерями. Ваш результат будет неверным для некоторых расчетов, если вы сделаете их таким образом.

person Peter Hosey    schedule 10.11.2009