Как хранить и работать с деньгами в коде и базе данных?

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


Давайте попробуем раз и навсегда обсудить и понять как хранить и работать с денежными суммами.


Я изучил вопрос и понял, что люди разделились на два лагеря:


1) Хранение в int

2) Хранение в decimal


Допустим, что мне достаточно вести биллинг в рублях с точности до копеек.

Хранение в bigint

Плюсы:
  • Нет плавающей точки — меньше неточностей
  • Можно производить стандартные мат. операции и не бояться, что потеряешь где-то копейки
  • mithraen: Мат. операции идут быстрее (От автора вопроса — я проверил. Расчеты ниже)
  • gleb_kudr: Вот международный стандарт по денежным единицам en.wikipedia.org/wiki/ISO_4217

    Там видно, что число знаков после запятой у них может быть разное. А значит для простоты разработки, все стоит хранить в минимальной дробной денежной единице валюты, осущеставляя конвертацию при выводе (т.е. в int)


Минусы:
  • Надо помнить о постоянном умножении/делении на 100


Хранение в Deciamal/Numeric

Плюсы:
  • Храниться в естественном виде
  • Не надо дополнительно париться при выводе


Минусы:
  • При неаккуратной работе (не через bcmath) можно ошибиться при умножении или делении
  • Работа через bcmath медленее
  • У разной валюты разное кол-во знаков поле запятой — если у вас мультивалютная система, будет избыточность данных. Придется делать более 2 знаков после запятой, но они будут нужны не всем
  • Роберт Мартин: «Использовать числа с плавающей точкой для представления денежных сумм — почти преступление»



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

Замеры скорости

Примеры приведу на языке php.

Работа с плавающей точкой
<?php
$a = 1.2;
$b = 3.4;

for ($i=0;$i<1000000;$i++) {
    $c = bcdiv($a,$b,2);
}


Запускаем:
arturgspb@debian:/home$ time php 1.php

real    0m3.490s
user    0m3.468s
sys     0m0.020s


Работа с Int
<?php
$a = 11.2;
$b = 3.4;

$a *= 100;
$b *= 100;

for ($i=0;$i<1000000;$i++) {
    $c = $a/$b;

    // Избавляемся от лишних чисел
    $c *= 100;
    $c = (int)$c;
    $c /= 100;
}


Запускаем:
arturgspb@debian:/home$ time php 2.php

real    0m0.562s
user    0m0.540s
sys     0m0.020s


Разница скорости в 6 раз очевидна.
  • Вопрос задан
  • 22792 просмотра
Решения вопроса 1
@gleb_kudr
Вот международный стандарт по денежным единицам en.wikipedia.org/wiki/ISO_4217
Там видно, что число знаков после запятой у них может быть разное. А значит для простоты разработки, все стоит хранить в минимальной дробной денежной единице валюты, осущеставляя конвертацию при выводе (т.е. в int)
Ответ написан
Пригласить эксперта
Ответы на вопрос 11
m08pvv
@m08pvv
Есть хорошее обсуждение на stackowerflow
Ответ написан
@mithraen
int удобнее.

Мы просто считаем что все суммы у нас указаны в копейках. И все математические операции выполняются крайне быстро. А понятие «рубли/копейки» имеют смысл только при вводе/выводе.
Там и конвертировать.
Ответ написан
AR1ES
@AR1ES
У нас в компании все денежные средства хранятся в int.
Деление на 100 ни капли не напрягает, т.к. уже привыкли. И даже мысли о хранении в другом виде не возникает.
Да и форматы с плавающей точкой по сути своей избыточны с точки зрения хранения и работы с денежными средствами, хотя в наше время эта избыточность вряд ли будет хоть как-то ощутима :)
Ответ написан
Melkij
@Melkij
PostgreSQL DBA
> Если пишите что-то вроде «деньги нельзя хранить в float» — пишите почему именно.
Как почему? Потеря точности и постоянные округления, конечно. И весьма забавные вычисления около нуля.
Но при чём тут float, когда рассматриваете int и decimal?

Ещё одно «за» int — тупое целочисленное действие. В отличии от decimal, не являющегося простым типом данных.
Ответ написан
Edro
@Edro
используем oracle+number
ни каких проблем не испытываем
Ответ написан
@nitogel
А у меня вопрос про суммы списания. Как хранить отрицательные суммы в базе? Со знаком минус или добавить тип записи debit & credit ? Но как тогда быстро одним запросом посчитать сумму всех записей?
Ответ написан
@dborovikov
В некоторых случая сумму можно хранить в float, например когда это промежуточные результаты. А непосредственно записи с денежными суммами нужно в целочисленном формате, так как в бухучете нет дробных копеек, только целые. Ну и при выполнении операций округление нужно делать осторожно.
Ответ написан
meteozond
@meteozond
Коллеги, я дико извиняюсь, что ворошу старое.
У меня такой вопрос — если у меня цены казаны за месяц, а производить списание нужно микротранзакциями, в течение периода.
Часто сумма таких транзакций может составлять тысячные копейки.
Ответ написан
@ryabininea
Данный вопрос до сих пор терзает нас! )

Смотрите, когда два знака после запятой, все норм, все понятно. А как же быть, допустим, если мне необходимо хранить по 7 знаков до, и 7 знаков после. Будет ли bigInt лучше numeric (decimal)

Буду благодарен, если кто-либо даст ответ с учетом особенностей базы данных PostgreSQL.
Ответ написан
BeLove
@BeLove
security
Если соберетесь хранить деньги и делать обмен валют - рекомендую презентацию 2013.zeronights.ru/includes/docs/Adrian_Furtuna_-_...
Ответ написан
@abratko
Здесь https://www.postgresql.org/docs/9.5/static/datatyp... (раздел 8.1.2)
рекомендуют NUMERIC
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через TM ID
Похожие вопросы
ICONIC Москва
от 250 000 руб.
ИНВИТРО Москва
от 120 000 до 145 000 руб.
18 июля 2018, в 22:20
4000 руб./за проект
18 июля 2018, в 21:54
1000 руб./в час