О культе карго и хранении денег

ноября 15, 2012  |  Published in Best Practices  |  7 Comments

Вы должно быть знаете о культе карго (cargo cult)? Если нет — в Википедии есть замечательная статья. Культом карго также стало модно называть любое подражание без понимания сути. Члены культа карго подражают человеку или группе людей, которые добились успеха в чем-либо, рассчитывая на то, чтобы добиться аналогичных результатов от своего повторения.

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

Если спросить — Чем тушить пожар? — Большинство не задумываясь ответят — Водой. На самом же деле не всякий пожар можно тушить водой. Например, нельзя тушить водой электротехнику, нельзя потушить водой горючие жидкости, например, бензин или керосин и т.д. Водой также нельзя тушить воспламенившиеся щелочные металлы и много-много чего прочего.

А теперь отойдем от философствований (их я очень люблю, но блог не о том) и вернемся к теме разработки ПО. Сразу расскажу вам предысторию, почему у меня возникла идея написания такой статьи.

Недавно у меня возник спор с одним человеком. Он уверенно заявил, что деньги следует хранить в копейках. — В копейках, — переспросил я, — а в чем профит от этого? Человек замялся и в ответ выдал что-то вроде «Так все говорят.».

Делать что-то потому, что так делают «все» — культ карго. Невольно вспоминается несколько грубая поговорка: «Миллиарды мух не могут ошибаться — в говне определенно что-то есть!». Если бы меня интересовало мнение большинства, то я программировал бы на PHP, а не на Ruby, просто потому, что PHP более популярен, а вместо разработки крутых нагруженных проектов я бы «создавал» сайты-визитки. Важно не мнение большинства или вообще чье-либо мнение, а истина.

 

Итак, в чем истина, брат?

Истина в том, что деньги не нужно хранить в копейках. Можно, но не нужно. Нет такой необходимости. Вы можете хранить деньги в рублях, копейках, «милликопейках», «микрокопейках» — в чем угодно! Суть заключается в том, что деньги НЕ нужно хранить числом с плавающей точкой. Почему? — читай ниже.

1.

16.8 * 1.1 = 16.8 + 1.68 = 18.48 Верно?

А вот и неверно!

16.8 * 1.1 == 18.48 #=> false
16.8 * 1.1 #=> 18.480000000000004

А вот еще более простой пример:

1.0 - 0.9 #=> 0.09999999999999998

Мы не можем выполнять точные сравнения!

2. Числа с плавающей точкой очень ненадежны. Знающие люди поговаривают, что результат операций с ними может зависеть от температуры (реально проводился такой эксперимент) — напоминает генерацию случайных чисел. Кроме того, не стоит забывать о проблеме округления чисел с плавающей точкой. Количество знаков после точки ограничено, а потому, хотите вы того или нет — происходит округление. При каждом новом действии с таким числом ошибка накапливается.

«Хранить деньги в копейках» — это означает хранить количество денег целым числом с точностью до копейки. Вместо 1000.57 («одна тысяча рублей и пятьдесят семь копеек») мы храним 100057 («сто тысяч пятьдесят семь копеек»). Разумеется, при различных операциях мы теряем дробную часть, но это прогнозируемо и не так страшно, особенно, если вспомнить то, что я говорил выше — вы не обязаны хранить количество денег с точностью до копейки. Сумму в 1000 руб. 57 коп. вы можете хранить как 100057000 — с точность до 1/1000 копейки, или 100057000000 — 1/1000000 копейки. Я думаю, что точности до 1/1000 копейки вам хватит с лихвой. Потеря нескольких десятков рублей на миллиардных оборотах — это мелочь. Работать с идеальной точностью невозможно! Помните об иррациональных числах.

5 / 3 #=> 1
5.0 / 3 #=> 1.6666666666666667

Потери будут всегда. Ваша задача, как разработчиков — сделать их минимальными и более-менее прогнозируемыми.

Tags: , , , , , ,

Responses

  1. proton says:

    ноября 15, 2012 at 22:11 (#)

    Не уловил смысл статьи.
    В большинстве случаев хранение в копейках в целом числе, либо в рублях в числе с фиксированной точкой — вполне нормально. Деление/Умножение денег — не такая частая операция, а когда она требуется — нужно чётко понимать, что происходит.

  2. Alex Klyanchin says:

    ноября 15, 2012 at 22:24 (#)

    Деньги надо хранить в минимальных единицах основной валюты. У нас это рубли — значит в копейках. Основной сутью моей фразы было «минимальных единицах». То есть тоже самое о чем статья, хранить в целых числах =]

  3. admin says:

    ноября 16, 2012 at 07:44 (#)

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

    Смысл поста в том, что целые числа более прогнозируемые, то есть мы способны более-менее запланировать потери системы + с целыми числами можно выполнять точное сравнение. Если, есть условие, что с сумой < = 18000 идет одна процентная ставка, а с сумой >18000 другая, то очевидно, что c 18.000000000004 будет идти неправильная процентная ставка.

    Предусмотреть все такие моменты нельзя.

    Касательно «нужно чётко понимать, что происходит» — это невозможно в сложных проектах, где работает большое количество людей просто потому, что никто не имеет абсолютно полного представления о работе системы.

  4. admin says:

    ноября 16, 2012 at 09:25 (#)

    Саша, возможно, мы друг друга недопоняли тогда, но вот конкретно с минимальными единицами я не согласен. Копейка — это ведь не минимальная единица стоимости — это просто минимальный номинал, ну просто полкопейки или 0.1 копейки никто печатать не будет. На милионах транзакций с точностью в копейку могут накапливаться большие суммы расхождения. Потому в некоторых случаях имеет смысл увеличивать точность до 1/100 или 1/1000 копейки. В общем основная суть — целые числа, вместо float и контроль погрешности.

  5. admin says:

    ноября 16, 2012 at 09:25 (#)

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

  6. Yuri Artemev says:

    ноября 16, 2012 at 10:17 (#)

    Постил об этом в coderwall, там подсказали решение и я провел тесты производительности двух решений.

    https://coderwall.com/p/swy-kw

  7. admin says:

    ноября 18, 2012 at 09:58 (#)

    BigDecimal provides arbitrary-precision floating point decimal arithmetic. BigDecimal — это не не путь целых чисел.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.