36

Был на собеседовании, спросили, "в переменной какого типа лучше хранить деньги", сказал Float, мол скорость, все дела. - забраковали. Ответа на вопрос "почему" не услышал. так вот почему "деньги" нельзя хранить во Float?

JVic
  • 3,309
  • 14
    погрешность операций. В double ещё кое-как можно, но страшно. Правильный ответ - их НЕЛЬЗЯ хранить в числах с плавающей запятой. Проще всего в long умножив на гарантированную точность (например в копейках или сколько надо). Можно иногда в аналогах decimal пытаться, но лучше не надо. Вы например хотите добавить 1 рубль к 10^10 которые уже есть на счету. Он просто потеряется из-за нормирования. – pavel May 18 '17 at 13:57
  • 11
    @pavel Вот чего вы стесняетесь ответ написать? – tutankhamun May 18 '17 at 14:05
  • Если в языке есть тип fixed point, то стоит хранить в нем (по поводу Java не в курсе). Если такого типа нет, реализуем через целочисленный тип. Почему нельзя в float хранить, Pavel все верно написал. – insolor May 18 '17 at 14:30
  • потому что 1.40f - 0.43f == 0.97f и 1.03d - 0.42d == 0.61d - false. – zRrr May 18 '17 at 14:45
  • long с делением на 100 при выводе, BigDecimal, специализированные классы/библиотеки (Java Money, Joda Money) – pavlofff May 19 '17 at 06:36
  • 1
    В C#, например, есть специальный тип decimal для таких случаев. – VladD May 19 '17 at 11:16
  • @pavlofff, а не на 10000? У Currency же 4 знака. – Qwertiy May 19 '17 at 17:55

3 Answers3

25

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

Простой пример: 5.1 переведем в двоичный вид

Целая часть имеет только 3 цифры

 5₁₀ = 1*2²  + 0*2¹  + 1*2⁰ = 101₂

А вот дробная...

.1₁₀ = 0*2⁻¹ + 0*2⁻² + 0*2⁻³ + 1*2⁻⁴ + 1*2⁻⁵ + 0*2⁻⁶ + 0*2⁻⁷ + 1*2⁻⁸ + 1*2⁻⁹...

Если взять только первые 7 цифр (двоичных) после запятой, получится не 0.1, а 0.09375

Добавлено: Что касается подходящих типов (поскольку на вопросе тег Java - примеры для него) для хранения финансовых данных, как уже написали в комментариях и соседних ответах есть два подхода:

  1. Типы с произвольной точностью (например BigDecimal). В исходники не смотрел но внутри хранение скорее всего происходит или в строках или массивах цифр;
  2. Целочисленные примитивные типы (int или long). При этом в переменных финансы хранятся в неделимых единицах измерения (это не всегда копейки/центы, в ряде случаев должны учитываться, например, сотые доли этих единиц)

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

tutankhamun
  • 11,366
  • Хороший ответ... Но вопрос "в чем хранить" - укажите типы в которых можно хранить. – nick_n_a Jan 16 '19 at 09:03
  • Ответы уже даны, но в комментариях к вопросу. Не думаю что это нужно дублировать. См. комментарии pavel и pavlofff. – tutankhamun Jan 16 '19 at 09:17
  • Ответ на вопрос должен отвечать, а не комментарий. Давно вы видимо не видели запись "Коментарии удалены или перемещеный в чат" – nick_n_a Jan 16 '19 at 09:36
  • @nick_n_a Убедили :) – tutankhamun Jan 16 '19 at 18:23
19

Немного, наверное, конкретики можно внести без углубления в тонкости вычислений машины:)
Тут надо еще уточнение, какие операции будут с деньгами производиться.
Для платежей и переводов - достаточен long. А сама сумма - в минорных единицах. Потому что мы не платим десятыми долями копеек/центов. Т.е. для 1 руб. 10 копеек, будем перечислять 110.
Хранить также можно в таком же типе. Но уже появляются вопросы, когда надо проводить вычисления. К примеру, насчитать некий процент за месяц. Тут уж типы double/float, как заметили некоторые, могут давать погрешности из-за тонкостей стандарта чисел с плавающей запятой. Но есть хороший выход: есть объекты чисел, которые хранят все значения в целых числах. Конкретнее: два целочисленных значения: мантиссу вещественного числа в виде объекта класса большого целочисленного, и неотрицательный десятичный порядок числа типа int. Тут напрашивается пример для Java: класс BigDecimal. Я бы хранил деньги в нем.
Ну а если еще углубиться в расчеты, то для всяких операций есть свои стандарты. Например, если рассчитываем пеню, то округление в большую сторону:
При пене в 1.123456 руб, получим 1.13. Все просто.

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

Sv__t
  • 2,713
Anton Mukhin
  • 5,535
  • 9
  • 53
  • 92
  • 1
    1.13 - это просто если его не трогать. Давайте представим что это float. Выполните в Java System.out.println(1.13f + 10000f - 10000f); Сразу напрашивается вопрос: "Зачем мы это округляли?" :) – tutankhamun May 18 '17 at 16:31
  • 7
    Я бы хранил деньги в нем. - Храните деньги в сберегательных кассах, если, конечно, они у вас есть :-D – Grundy May 19 '17 at 08:25
  • @tutankhamun А вы интересный пример предоставили! Получилось 1.1298828. Тут уж интересный и неожиданный, с точки зрения матвычислений, результат. Что еще раз доказывает, что лучше не хранить во float деньги из-за погрешностей в вычислениях. Ну а округление тут все равно одинаковое. Но нет гарантий, что после нескольких тысяч вычислений, получится даже с округлением, правильный результат. – Anton Mukhin May 19 '17 at 08:27
  • 2
    всегда округлять в большую сторону хоть и выгодно банку, но применяется в "нечестных банках". Обычно подобное округление либо явно прописано в соглашениях, либо применяется "бухгалтерское округление". В нем смотрят на округляемую цифру - если она равна пяти, то округляют в "парную сторону". То есть 1.5 -> 2, 2.5 -> 3, 3.5 -> 4. это позволяет минимизировать ошибку округления. – KoVadim May 19 '17 at 08:41
  • 2
    @KoVadim чем приведенный пример отличается от округления к ближайшему? Может вы имели ввиду: 1.5 -> 2, 2.5 -> 2, 3.5 -> 4 – tutankhamun May 19 '17 at 08:49
  • да, опечатался. конечно же 2.5 к 2. – KoVadim May 19 '17 at 08:50
  • @KoVadim Все верно это называется округление по Гауссу или т.н. банковское округление, его так же используют в траекторном анализе – Stranger in the Q Jul 06 '19 at 07:31
7

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

Преимущества

  1. Точность и значность известна
  2. Арифметики и сравнение включены
  3. Поддерживается в JDK, JDBC и т.д.
  4. Неплохая производительность