it-swarm.com.ru

Должен ли я использовать NSDecimalNumber, чтобы иметь дело с деньгами?

Начав кодировать свое первое приложение, я дважды подумал о денежных значениях NSNumber. Тогда я подумал, что, возможно, c типов было достаточно, чтобы справиться с моими значениями. Тем не менее, на форуме iPhone SDK мне посоветовали использовать NSDecimalNumber из-за его превосходных возможностей округления.

Не будучи математиком по темпераменту, я подумал, что парадигма мантисса/показатель степени может быть излишней; Тем не менее, поглядывая вокруг, я понял, что большинство разговоров о деньгах/валюте в какао были отнесены к NSDecimalNumber.

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

Я на 90% уверен, что мне нужно использовать NSDecimalNumber, но так как я не нашел однозначного ответа в Интернете (что-то вроде: "если вы имеете дело с деньгами, используйте NSDecimalNumber!"), Я подумал, что я должен спросить здесь. Может быть, ответ очевиден для большинства, но я хочу быть уверен, прежде чем начать массивный рефакторинг моего приложения.

Убедить меня :)

51
nutsmuggler

У Маркуса Зарры довольно четкая позиция по этому вопросу: "Если вы вообще имеете дело с валютой, то вам следует использовать NSDecimalNumber." Его статья вдохновила меня на изучение NSDecimalNumber, и я очень впечатлен этим. Ошибки IEEE с плавающей запятой при работе с математикой base-10 меня некоторое время раздражали (1 * (0.5 - 0.4 - 0.1) = -0.00000000000000002776), и NSDecimalNumber устраняет их.

NSDecimalNumber не просто добавляет еще несколько цифр двоичной точности с плавающей запятой, он фактически выполняет математику с начальным значением 10. Это избавляет от ошибок, подобных той, что показана в примере выше.

Сейчас я пишу символическое математическое приложение, поэтому мое стремление к точности более 30 десятичных разрядов и отсутствию странных ошибок с плавающей запятой может быть исключением, но я думаю, что на это стоит обратить внимание. Операции немного сложнее, чем простая математика в стиле var = 1 + 2, но они все еще управляемы. Если вы беспокоитесь о выделении всех видов экземпляров во время ваших математических операций, NSDecimal является структурным эквивалентом C NSDecimalNumber, и есть функции C для выполнения точно таких же математических операций с ним. По моему опыту, это достаточно быстро для всех, кроме самых требовательных приложений (3 344 593 дополнений/с, 254 017 делений/с на MacBook Air, 281 555 дополнений/с, 12 027 делений/с на iPhone).

В качестве дополнительного бонуса NSDecimalNumber's descriptionWithLocale: метод предоставляет строку с локализованной версией числа, включая правильный десятичный разделитель. То же самое происходит в обратном направлении для его initWithString: locale: метод.

63
Brad Larson

Да. Вы должны использовать

NSDecimalNumber и

не двойной или плавающий при работе с валютой на iOS.

Это почему??

Потому что мы не хотим получать такие вещи, как $ 9.9999999998 вместо $ 10

Как это происходит?

Плавает и удваивает приблизительные значения. Они всегда идут с ошибкой округления. Формат, используемый компьютерами для хранения десятичных знаков, вызывает эту ошибку маршрутизации. Если вам нужно больше подробностей, прочитайте

http://floating-point-gui.de/

Согласно Apple документам,

NSDecimalNumber является неизменяемым подклассом NSNumber, предоставляет объектно-ориентированную оболочку для выполнения арифметики с основанием 10. Экземпляр может представлять любое число, которое может быть выражено как показатель мантиссы х 10 ^, где мантисса - это десятичное целое число длиной до 38 цифр, а экспонента - целое число от –128 до 127.wrapper для выполнения арифметики с основанием-10.

Поэтому NSDecimalNumber рекомендуется для сделки с валютой.

8
MadNik

(Адаптировано из моего комментария к другому ответу.)

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

Единственное правильное решение - NSDecimalNumber (или что-то подобное), которое откладывает проблему до 10 ^ -128 ¢ (т.е.
0,0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000001 ¢).

(Другой способ - арифметика произвольной точности, но для этого требуется отдельная библиотека, например GNU MP Bignum) . GMP входит в LGPL. Я никогда не использовал это библиотеки и не знаю точно, как это работает, поэтому я не могу сказать, насколько хорошо это будет работать для вас.)

[Edit: Очевидно, по крайней мере один человек - Брэд Ларсон - думает, что я говорю о двоичной с плавающей точкой где-то в этом ответе. Я не.]

5
Peter Hosey

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

4
Tony

VISA, MasterCards и другие используют целочисленные значения при прохождении сумм. Отправитель и получатель должны правильно анализировать суммы в соответствии с показателем валюты (делить или умножать на 10 ^ num, где num - это показатель валюты). Обратите внимание, что разные валюты имеют разные показатели. Обычно это 2 (следовательно, мы делим и умножаем на 100), но у некоторых валют экспонента = 0 (VND и т.д.) Или = 3.

2
Eugene Martinson

Я нашел удобным использовать целое число для представления количества центов, а затем разделить на 100 для представления. Избегает всего вопроса.

2
wisequark