Лучшие практики работы с денежными суммами в C#

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

Прочитав эти статьи, каковы лучшие практики и рекомендации по работе с деньгами на C#? Должен ли я использовать один тип данных для небольшого количества, а другой - для большего? Кроме того, я живу в Великобритании, что означает, что мы используем (например, 4000 фунтов стерлингов, в то время как в других культурах такая же сумма представлена ​​по-разному).

Ответов (7)

Решение

Вы не должны использовать числа с плавающей запятой из-за ошибок округления. Вам должен подойти десятичный тип.

Я использую объект значения для хранения суммы (как decimal ) и валюты. Это позволяет работать с разными валютами одновременно. decimal - рекомендуемый тип данных для денег в .NET.

Как вы указали в своем вопросе, помимо использования соответствующего типа данных, важно, насколько хорошо ваша программа будет обрабатывать конвертацию валюты. Эта проблема, конечно, если не исключительно в отношении валюты. Джефф Этвуд сделал отличный пост, в котором резюмировал достоинства выполнения The Turkey Test .

Десятичная дробь - наиболее разумный тип для денежных сумм.

Decimal - это числовой тип с плавающей запятой и основанием 10, с точностью до 28+ десятичных цифр. Используя Decimal, у вас будет меньше сюрпризов, чем при использовании типа base 2 Double.

Double использует вдвое меньше памяти, чем Decimal, а Double будет намного быстрее из-за аппаратного обеспечения ЦП для многих распространенных операций с плавающей запятой, но он не может точно представлять большинство десятичных дробей (например, 1.05) и имеет менее точные 15+ десятичных цифр точность. У Double есть преимущество в большем диапазоне (он может представлять большие и меньшие числа), что может пригодиться для некоторых вычислений, особенно некоторых статистических вычислений.

Один ответ на ваш вопрос гласит, что десятичная дробь - это фиксированная точка с 4 десятичными цифрами. Это не тот случай. Если вы сомневаетесь в этом, обратите внимание, что следующая строка кода дает 0,0000000001:

Console.WriteLine("number={0}", 1m / 10000000000m);

Сказав все это, интересно отметить, что наиболее широко используемое в мире программное обеспечение для работы с денежными суммами, Microsoft Excel, использует двойники. Конечно, им приходится преодолевать множество препятствий, чтобы это работало хорошо, и это по-прежнему оставляет желать лучшего. Попробуйте эти две формулы в Excel:

  • = 1-0,9-0,1
  • = (1-0,9-0,1)

Первый дает 0, второй ~ -2,77e-17. Excel фактически массирует числа при сложении и вычитании чисел в некоторых случаях, но не во всех случаях.

Мартин Фаулер рекомендует использовать класс Money . См. Ссылку для обоснования. Есть несколько вариантов реализации его идеи, или вы можете написать свою собственную. Собственная реализация Фаулера находится на Java, поэтому он использует класс. Версии C#, которые я видел, используют структуру, что кажется разумным.

Я бы порекомендовал использовать Decimal, как рекомендуют другие, если требуется деление. Для простого подсчета я бы рекомендовал целочисленный тип. Для обоих типов я всегда буду работать с наименьшим денежным номиналом. (т. е. центов в Канаде / США)

Мне нравится теория денежного вызова Фаулера, добавленная @dangph.

Что бы вы ни делали, убедитесь, что вы понимаете, как обрабатываются суммы в валюте на каждом уровне вашего приложения.

Однажды я потратил неделю на отслеживание ошибки в 1 ¢, потому что SQLServer и .Net используют разные способы округления валют, а приложение не согласовывалось в том, как оно обрабатывает определенные типы вычислений - иногда они выполнялись в SQL, иногда в .net. Если вам интересно, посмотрите на « банковское округление ».

Существуют также проблемы, связанные с форматированием валют - не уверен, что вам нужно иметь дело с суммами за пределами Великобритании, другими языками / культурами и т. Д., Но это добавит еще один уровень сложности.