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

Вопрос решен. Использовать 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 раз очевидна.
  • Вопрос задан
  • 43180 просмотров
Решения вопроса 1
@gleb_kudr
Вот международный стандарт по денежным единицам en.wikipedia.org/wiki/ISO_4217
Там видно, что число знаков после запятой у них может быть разное. А значит для простоты разработки, все стоит хранить в минимальной дробной денежной единице валюты, осущеставляя конвертацию при выводе (т.е. в int)
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 10
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 ? Но как тогда быстро одним запросом посчитать сумму всех записей?
Ответ написан
@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
Ответ написан
@yoda_code
Там видно, что число знаков после запятой у них может быть разное. А значит для простоты разработки, все стоит хранить в минимальной дробной денежной единице валюты, осущеставляя конвертацию при выводе (т.е. в int)


Согласен с полезностью хранения в int. Однако, стоит помнить и о дополнительной точности, а не только о минимальной дробной единице самой валюты. Пример, клиент вносит сумму 0,02 или 2 цента. В базу вы пишете в центах, 0,02×100=2, у вас пойдет 2. Но, что если нужно списать комиссию в 10%? 2×10%=0,2. Понадобится дополнительная точность, т.е. введение дополнительного нуля, делить\умножать на 1000, а не на 100. Или решение этого вопроса другим способом.
Ответ написан
Комментировать
Ваш ответ на вопрос

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

Похожие вопросы