Должен ли я использовать NSDecimalNumber, чтобы иметь дело с деньгами? - PullRequest
51 голосов
/ 07 января 2009

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

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

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

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

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

Ответы [ 6 ]

63 голосов
/ 08 января 2009

У Маркуса Зарры довольно четкая позиция по этому вопросу: «Если вы вообще имеете дело с валютой, то вам следует использовать 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 descriptionWithLocale: предоставляет строку с локализованной версией числа, включая правильный десятичный разделитель. То же самое происходит в обратном направлении для его initWithString: locale: method.

8 голосов
/ 14 июня 2013

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

NSDecimalNumber и

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

Почему это ??

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

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

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

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

Согласно Apple Docs,

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

Таким образом, NSDecimalNumber рекомендуется для сделки с валютой.

5 голосов
/ 08 января 2009

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

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

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

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

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

4 голосов
/ 09 января 2009

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

2 голосов
/ 08 апреля 2015

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

2 голосов
/ 07 января 2009

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

...