Хранение денег в десятичной колонке - какая точность и масштаб?

173

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

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

И какую точность и масштаб я должен использовать? Я думал о точности 24/8. Это излишне, недостаточно или хорошо?


Вот что я решил сделать:

  • Сохраняйте коэффициенты конвертации (если применимо) в самой таблице транзакций в виде числа с плавающей запятой.
  • Храните валюту в таблице счетов
  • Сумма сделки будет DECIMAL(19,4)
  • Все вычисления, использующие коэффициент конверсии, будут обрабатываться моим приложением, поэтому я контролирую проблемы округления

Я не думаю, что плавающая ставка для конверсии является проблемой, так как она в основном для справки, и я все равно приведу ее к десятичной дроби.

Спасибо всем за ваш ценный вклад.

Иван
источник
2
Задайте себе вопрос: действительно ли необходимо хранить данные в десятичной форме? Не могу ли я сохранить данные в виде центов / пенни -> целых чисел?
Теренс
5
DECIMAL(19, 4) популярный выбор, проверьте это, также проверьте здесь Форматы Мировой валюты, чтобы решить, сколько десятичных знаков использовать, надеюсь, поможет.
Shaijut

Ответы:

181

Если вы ищете универсальный подход, я бы посоветовал DECIMAL(19, 4)это популярный выбор (быстрый Google подтверждает это). Я думаю, что это происходит от старого типа данных VBA / Access / Jet Currency, являющегося первым десятичным типом с фиксированной точкой в ​​языке; Decimalтолько в стиле «версия 1.0» (т.е. не полностью реализовано) в VB6 / VBA6 / Jet 4.0.

Основное правило для хранения десятичных значений с фиксированной запятой - хранить как минимум еще одно десятичное число, которое фактически требуется для округления. Одной из причин сопоставления старого Currencyтипа в переднем конце с DECIMAL(19, 4)типом в заднем конце было то, что у Currencyбанкиров было округление по природе, в то время как DECIMAL(p, s)округление путем усечения.

Дополнительное десятичное место в хранилище DECIMALпозволяет реализовать собственный алгоритм округления, а не принимать по умолчанию поставщика (и округление банкиров вызывает тревогу, если не сказать больше, для дизайнера, ожидающего, что все значения, заканчивающиеся на .5, округляются от нуля) ,

Да, DECIMAL(24, 8)звучит как излишнее для меня. Большинство валют указаны в четыре или пять десятичных знаков. Я знаю ситуации , когда десятичная шкалу 8 (или более) в необходимой , но это где «нормальная» денежная сумма (скажем , четыре знака после запятой) была про rata'd, подразумевая точность десятичной должно быть соответственно уменьшено (также рассмотреть тип с плавающей точкой в ​​таких обстоятельствах). И ни у кого сейчас нет таких денег, которые требуют десятичной точности 24 :)

Однако, вместо того, чтобы подход «один размер подходит всем», некоторые исследования могут быть в порядке. Спросите своего дизайнера или эксперта по домену о правилах бухгалтерского учета, которые могут быть применимы: GAAP, EU и т. Д. Я смутно припоминаю некоторые внутригосударственные переводы в ЕС с четкими правилами округления до пяти десятичных знаков, поэтому они используются DECIMAL(p, 6)для хранения. Бухгалтеры обычно предпочитают четыре знака после запятой.


PS Избегайте MONEYтипа данных SQL Server, потому что он имеет серьезные проблемы с точностью при округлении, среди прочих соображений, таких как переносимость и т. Д. См . Блог Аарона Бертранда .


Microsoft и дизайнеры языка выбрали округление банкира, потому что дизайнеры оборудования выбрали его [цитата?]. Например, оно закреплено в стандартах Института инженеров по электротехнике и электронике (IEEE). И дизайнеры оборудования выбрали это, потому что математики предпочитают это. Смотрите Википедию ; Перефразируя: издание «Вероятность и теория ошибок» 1906 года назвало это «правилом компьютера» («компьютеры», то есть люди, которые выполняют вычисления).

onedaywhen
источник
1
Посмотрите этот ответ и эту страницу, чтобы узнать, почему языковые дизайнеры выбирают округление Банкира.
Ник Чаммас
1
onedaywhen: округление банкира не из-за Microsoft. @NickChammas: и это не изобретение дизайнера языка. Microsoft и дизайнеры языка в основном выбрали его, потому что дизайнеры оборудования выбрали его; например, оно закреплено в стандартах Института инженеров по электротехнике и электронике (IEEE). И дизайнеры оборудования выбрали это, потому что математики предпочитают это. См. En.wikipedia.org/wiki/Rounding#History ; Перефразируя: издание «Вероятность и теория ошибок» 1906 года назвало это «правилом компьютера» («компьютеры», то есть люди, которые выполняют вычисления).
Phoog
9
так что я должен использовать для биткойнов?
Инструментарий
1
@onedaywhen почему DECIMAL(19, 4)популярнее чем DECIMAL(19, 2)? Большинство мировых валют имеют только два десятичных знака.
Cokedude
3
@ zypA13510: да, мое утверждение так десять лет назад! Но это мой третий самый проголосовавший за ответ и объясняющий значительную часть изменений относительно моего представителя SO, так что я в
фиде
105

Недавно мы внедрили систему, которая должна обрабатывать значения в нескольких валютах и ​​конвертировать между ними, а также выяснили некоторые сложные вещи.

НИКОГДА НЕ ИСПОЛЬЗУЙТЕ НОМЕРА С ПЛАВАЮЩИМИ ТОЧКАМИ ДЛЯ ДЕНЕГ

Арифметика с плавающей запятой вносит неточности, которые могут быть не замечены, пока они не напортачили. Все значения должны храниться в виде целых чисел или типов с фиксированной десятичной дробью, и если вы решите использовать тип с фиксированной десятичной дробью, убедитесь, что вы точно понимаете, что этот тип делает под капотом (т. Е. Использует ли он внутренне целое число или число с плавающей запятой). тип).

Когда вам нужно сделать расчеты или преобразования:

  1. Преобразовать значения в число с плавающей запятой
  2. Рассчитать новое значение
  3. Округлить число и преобразовать его обратно в целое число

При преобразовании числа с плавающей запятой обратно в целое число на шаге 3, не просто приведите его - используйте математическую функцию, чтобы сначала округлить его. Это обычно будет round, хотя в особых случаях это может быть floorили ceil. Знайте разницу и тщательно выбирайте.

Храните тип числа рядом со значением

Это может быть не так важно для вас, если вы работаете только с одной валютой, но для нас это было важно при работе с несколькими валютами. Мы использовали трехсимвольный код для валюты, такой как USD, GBP, JPY, EUR и т. Д.

В зависимости от ситуации также может быть полезно хранить:

  • Является ли число до или после налога (и какая ставка налога была)
  • Является ли число результатом преобразования (и из чего оно было преобразовано)

Знайте границы точности чисел, с которыми вы имеете дело

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

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


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

class Currency {
   String code;       //  eg "USD"
   int value;         //  eg 2500
   boolean converted;
}

class Price {
   Currency grossValue;
   Currency netValue;
   Tax taxRate;
}

В базе данных значения хранятся в виде строки в следующем формате:

USD:2500

Это хранит стоимость $ 25,00. Мы смогли сделать это только потому, что код, который работает с валютами, не обязательно должен находиться внутри самого уровня базы данных, поэтому все значения можно сначала преобразовать в память. Другие ситуации, без сомнения, поддаются другим решениям.


И в случае, если я не дал понять раньше, не используйте float!

Маркус Даунинг
источник
1
Никогда не говори никогда: иногда денежные суммы пропорциональны, и их нужно будет добавить позже. Пример: деление общей суммы дивидендов (относительно небольших) на количество выпущенных акций (относительно небольших) для получения чистой доли на акцию. Иногда лучше
проплыть по кругу
23
Я стою на своем никогда. Спецификация с плавающей запятой имеет неточности, которые добавят больше вычислений, которые вы делаете. Если вам нужно хранить значения меньше цента или копейки, определите уровень точности, который вам нужен, и придерживайтесь его. Не используйте поплавок. Шутки в сторону. Это плохая идея.
Маркус Даунинг
4
Этот ответ также согласуется с лучшими практиками javascript, изложенными Дугласом Крокфордом в его серии «Crockford on JavaScript», где он рекомендует выполнять все расчеты валют в PENNIES, чтобы избежать ошибок машин при округлении. Поэтому, если вы работаете с валютами в javascript, имеет смысл сохранить таким образом значение.
paperreduction
1
@onedaywhen В этих случаях на каждую акцию вы можете суммировать пропорциональные суммы и сравнивать их с исходной суммой и разрабатывать стратегию обработки остатка (при использовании десятичных / целочисленных типов).
Шив
2
Для Mysql я рекомендую хранить целое число (2500) как, bigintесли вы собираетесь сортировать по сумме. И не тратьте свое время на 32-битный PHP при работе с большими целыми числами, обновитесь до 64-битного или Node.JS;)
Рикки Бойс
4

При обработке денег в MySQL используйте DECIMAL (13,2), если вы знаете точность значений ваших денег, или DOUBLE, если вы просто хотите получить достаточно хорошее и приблизительное приблизительное значение. Так что если ваше приложение должно обрабатывать денежные суммы до триллиона долларов (или евро или фунтов), то это должно работать:

DECIMAL(13, 2)

Или, если вам нужно соблюдать GAAP, используйте:

DECIMAL(13, 4)
pollux1er
источник
3
Можете ли вы сослаться на конкретную часть руководящих принципов GAAP вместо содержимого документа на 2500 страниц? Спасибо.
ReactingToAngularVues
@ReactingToAngularVues кажется, что страница изменилась. Приносим свои извинения
contax1er
2

4 знака после запятой позволят вам хранить самые маленькие в мире денежные единицы. Вы можете сделать это дальше, если вам нужна точность микроплатежей (наноплатежей ?!).

Я тоже предпочитаю использовать DECIMALтипы денег, специфичные для СУБД, так как вы сохраняете логику в IMO приложения. Другой подход в том же духе заключается в простом использовании [длинного] целого числа с форматированием в ¤unit.subunit для удобства чтения (¤ = символ валюты) на уровне приложения.

bobince
источник
1

Тип данных money на SQL Server имеет четыре цифры после десятичной.

Из электронной документации по SQL Server 2000:

Денежные данные представляют собой положительные или отрицательные суммы денег. В Microsoft® SQL Server ™ 2000 денежные данные хранятся с использованием типов данных money и smallmoney. Денежные данные могут храниться с точностью до четвертого знака после запятой. Используйте тип данных money для хранения значений в диапазоне от -922,337,203,685,477.5808 до +922,337,203,685,477.5807 (для хранения значения требуется 8 байтов). Используйте тип данных smallmoney для хранения значений в диапазоне от -214 748,3648 до 214 748,3647 (для хранения значения требуется 4 байта). Если требуется большее количество десятичных разрядов, используйте тип данных decimal.

Остин Салонен
источник
1

Иногда вам нужно будет перейти на менее чем за цент, и есть международные валюты, которые используют очень большие демонизации. Например, вы можете взимать с ваших клиентов 0,088 цента за транзакцию. В моей базе данных Oracle столбцы определены как NUMBER (20,4)

WW.
источник
1

Если вы собираетесь выполнять какие-либо арифметические операции в БД (умножение тарифов и т. Д.), Вы, вероятно, захотите гораздо большей точности, чем предлагают здесь люди, по тем же причинам, по которым вы никогда не будете хотите использовать что-либо меньшее, чем значение с плавающей запятой двойной точности в коде приложения.

Хэнк Гей
источник
Это было то, о чем я думал, но с точки зрения курсов валют (то есть, конвертирование долларов Зимбавы в доллары США). Я проведу несколько экспериментов с базами данных, которые я использую (psql, sqlite), чтобы увидеть, как они обрабатывают округление с очень маленькими десятичными знаками.
Иван
Кроме того, нет ли у поплавков проблем с точностью в некоторых dbms / языках?
Иван
2
У поплавков есть проблемы с точностью во ВСЕХ языках.
Маркус Даунинг
Самая распространенная рекомендация в наши дни - использовать произвольную точность (подумайте BigDecimal), но долгое время это была двойная точность (подумайте doubleвместо float). Кроме того, произвольная точность имеет значительные потери производительности в некоторых случаях. Тестирование, безусловно, правильный подход.
Хэнк Гей
0

Если бы вы использовали IBM Informix Dynamic Server, у вас был бы тип MONEY, который является второстепенным вариантом для типа DECIMAL или NUMERIC. Это всегда тип с фиксированной запятой (тогда как DECIMAL может быть типом с плавающей запятой). Вы можете указать масштаб от 1 до 32 и точность от 0 до 32 (по умолчанию используется масштаб 16 и точность 2). Таким образом, в зависимости от того, что вам нужно хранить, вы можете использовать DECIMAL (16,2) - все еще достаточно большой, чтобы удерживать дефицит федерального бюджета США, с точностью до цента - или вы можете использовать меньший диапазон или больше десятичных знаков.

Джонатан Леффлер
источник
0

Я думаю, что в значительной степени требования вашего или вашего клиента должны определять, какую точность и масштаб использовать. Например, для веб-сайта электронной коммерции, над которым я работаю, который касается только денег в фунтах стерлингов, я должен был указывать их в десятичном формате (6, 2).

Аяз
источник
0

Поздний ответ здесь, но я использовал

DECIMAL(13,2)

что я правильно думаю, должно позволить до 99 999 999 999,99.

Майк Апджон
источник