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



Не используйте double


О том, что для хранения сумм нельзя использовать двоичный тип с плавающей точкой одинарной точности float, знают все. Однако распространено мнение, что вместо float можно использовать double. Между тем double не намного лучше.

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

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

599.99
Код на Java
float f = 599.99f;
System.out.printf("float %s%n", new BigDecimal(f).setScale(6, BigDecimal.ROUND_DOWN));
double d = 599.99d;
System.out.printf("double %s%n", new BigDecimal(d).setScale(15, BigDecimal.ROUND_DOWN));

Код на C#
float f = 599.99f;
float fi = (long) f;
float fp = f - fi;
long fil = (long) fi;
long fpl = (long) (fp * 1000000);
Console.WriteLine("float {0}.{1}", fil, fpl);
double d = 599.99d;
double di = Math.Truncate(d);
double dp = d - di;
long dil = (long) di;
long dpl = (long) (dp * 1000000000000000);
Console.WriteLine("double {0}.{1}", dil, dpl);

float 599.989990
double 599.990000000000009

То есть программа еще не сделала никаких вычислений, просто сохранила сумму в локальных переменных в двоичном формате, а в ней уже потеряна точность. В случае использования float — в 6-м знаке, в случае с double — в 15-м.

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

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

Проявиться ошибка точности может при сравнении чисел, которые вроде бы должны быть равны между собой, или сравнении остатка с нулем. Вот как это может выглядеть:

z = 599.99 руб. - 0.98 руб. - 599.00 руб. - 0.01 руб.
Код на Java
double z = ((599.99d - 0.98d) - 599.00d) - 0.01d;
if (z == 0d) {
    System.out.println("z == 0");
} else if (z > 0d){
    System.out.println("z > 0");
} else {
    System.out.println("z < 0");
}


Код на C#
double z = ((599.99d - 0.98d) - 599.00d) - 0.01d;
if (z == 0d)
{
    Console.WriteLine("z == 0");
}
else if (z > 0d)
{
    Console.WriteLine("z > 0");
}
else
{
    Console.WriteLine("z < 0");
}


Результат
z < 0

Вместо double надо использовать представление числа на основе степеней с основанием 10. В Java для этого предусмотрен тип BigDecimal, в C# — decimal.

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

Сумма не может быть отрицательной


Количество денег не может быть меньше 0. Это может показаться странным для программиста в момент, когда он пишет код, но когда тот же программист пойдет в магазин, для него аксиома неотрицательности сумм будет очевидна. Ни разу при осуществлении покупок ни один покупатель не сказал кассиру: «Я вам должен еще минус сто рублей», — вместо: «Дайте мне сдачу сто рублей».

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

Компания, оказывающая услуги ЖКХ, создала веб-сервис, который по номеру лицевого счета возвращает сумму к оплате. Коммерческий банк реализовал сервис оплаты этих услуг на своем сайте. Плательщик в прошлом месяце заплатил за квартиру с запасом. Он заходит в свой личный кабинет в интернет-банке, вводит лицевой счет, и банк предлагает ему провести платеж на -1500 рублей. При попытке совершить данную операцию пользователю сообщают об ошибке, так как сумма платежа должна быть положительной. Несчастный пользователь думает, что у него задолженность, поскольку очень часто в счетах задолженности обозначают со знаком минус, поэтому он исправляет сумму и совершает платеж. Теперь у него переплата 3000 рублей. На самом деле сумма счета может быть только положительной. Вместо отрицательной задолженности в счете должно было быть указано, что клиент должен оплатить ноль рублей, а сумма переплаты должна идти отдельной графой.

Не так давно в новостях много рассказывалось о так называемом «техническом овердрафте», который якобы образовался на карточных счетах клиентов в одном российском банке. Могу предположить, как это произошло. У клиента на счете не было денег, подошло время списания средств за обслуживание счета, банк сделал проводку со счета клиента на свой счет доходов, в результате чего на счете образовался подозрительный отрицательный баланс. Клиент, конечно, больше никогда не прикоснется к такому счету, не станет больше пополнять его, так как по правилам сложения сумма его перевода сложится с отрицательным остатком на счете, и он потеряет часть своих денег. За клиента в этом плане можно не беспокоиться, а вот банк в таком случае имеет на своем счету доходов виртуальные средства. Если он их потратит на хозяйственную деятельность, то в итоге у него в балансе образуется дыра. А все потому, что на счет доходов записали средства, которых у клиента не было. Вместо этого надо было сделать совсем другую проводку, со счета задолженностей клиента на счет невыполненных обязательств. А поскольку затронуты ошибкой были некредитные карты, то такую проводку физически невозможно было сделать.

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

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

На мой взгляд, выписки по счету, которые предоставляет большинство банков, выглядят нелепо. Что значит покупка на минус две пятьсот? Что значит расходы минус двадцать тысяч? По логике это должно означать, как будто у держателя счета что-то купили или он что-то заработал. Если перед вами стоит задача сделать выписку, то сделайте в выписке 2 столбца, один для списаний, другой для зачислений, и пишите нормально: «покупка в супермаркете 3500 ...», «зарплата… 20000» «всего списаний столько-то, зачислений столько-то».

Сумма — это не только число, но и валюта


Еще в средней школе нас всех учили, что в формулах надо указывать размерность величин и проверять ее. Килограммы нельзя складывать с метрами или сравнивать с литрами. Прежде чем умножить скорость на время, надо убедиться, что скорость указана в м/c, а время в секундах. Но при работе с суммами об этом часто забывают. Сумма — это физическая величина, и она имеет размерность. Поэтому надо работать с нею так, как учат на уроках физики.

Если система имеет дело исключительно с одной национальной валютой, то ее можно опустить, однако так бывает очень редко. Даже в пределах одной страны зачастую в ходу сразу несколько валют. Например, белорусский рубль до деноминации имеет обозначение BYR, а после деноминации — BYN. Если вы их перепутаете, это будет в 10 раз хуже, чем сложить метры с миллиметрами, потому что 1 BYN = 10 000 BYR. А еще говорят был BYB, для которого 1 BYR = 1000 BYB. Тем более важно контролировать размерность сумм в системе, которая работает с валютами разных стран.

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

Не используйте понятия «покупка» и «продажа»


Термины «покупка» и «продажа» — это бытовые понятия, которыми оперируют люди при выполнении повседневных задач. Люди ориентируются на систему координат, в которой они находятся в начальной точке отсчета. В финансах же любая сделка одновременно является и покупкой, и продажей.

Наберите в Яндексе «купить автомобиль», и половина ссылок в выдаче будет на объявления о продаже автомобилей, а другая — на объявления о покупке. То же самое будет, если поискать по запросу «аренда квартир», «сдать», «снять» и т. п.

Особенно наглядно такая двойственность проявляется при обмене валюты. Банки на своих сайтах предлагают курсы покупки и продажи различных валют. Как понять, что означает курс покупки доллара 56.61 руб.? Это банк по такой цене продает доллары или клиент рубли покупает? На самом деле пользователи, меняющие валюту, помнят, что 1 доллар стоит примерно пятьдесят с чем-то рублей, и что банк меняет валюту таким образом, чтобы получить прибыль, поэтому, когда надо обменять доллары на рубли, они смотрят на меньшую сумму в рублях, а когда надо обменять рубли на доллары — на большую. При этом, как правило, понять, что означают надписи «Покупка» или «Продажа» над цифрами, никто даже не пытается. Еще общая практика заключается в том, что меньшая сумма пишется слева, а большая — справа. Если написать одно предложение «Курс покупки израильского шекеля 15.75 руб.», мало кто угадает, что имеется в виду.

Когда разработчик пишет код, у него перед глазами нет конкретных значений, нет таблицы, где привычные цифры расположены в определенных местах, поэтому он ориентируется только на названия своих переменных. Если использовать названия типа buySum, sellSum, buyPrice, sellPrice и т. д., можно запросто перепутать обменные курсы. Такая же ситуация возможна и у сотрудников, которые эти курсы будут забивать в справочники.

Что будет, если клиент обнаружит, что ему предлагают обменять рубли на доллары по курсу 56 долларов за рубль? Он не будет писать в поддержку, он не станет жаловаться в фейсбуке, не будет скринить страницы. Предприимчивый клиент, скорее всего, поменяет все имеющиеся у него рубли и быстренько снимет все, что наменял.

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

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

Обменный курс — это вектор из двух сумм


Примерно так выглядит стандартное предложение обмена валют для клиента:


Как оно должно быть реализовано в коде? Курс выглядит как сумма, поэтому к нему должны быть применимы общие правила.

Десятичное представление — OK.
Неотрицательное значение — OK.
Размерность —?

Давайте попробуем определить, какая размерность у тех цифр, которые обычно горят на табло обменных пунктов. На первый взгляд кажется, что там везде рубли, но какие операции мы можем с ними сделать? Можно ли сложить курс покупки доллара 55.61 руб. с остатком на счете 1500.00 руб.? Нельзя, потому что настоящая размерность обменного курса представляет собой руб./доллар или, еще точнее, валюта-1/валюта-2.

А еще можно вспомнить, что для «удобства» в некоторых валютах используются коэффиценты. Например, для йены используется курс за 100 единиц валюты. Поэтому под суммой в 1-й валюте есть еще коэффициент во 2-й валюте.

Получается, размерность — валюта-1/(валюта-2 * коэффициент)?

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

В случае с «курсом покупки» клиент дает банку 1$ и получает 56.61?.
В случае с «курсом продажи» клиент дает банку 58.79? и получает 1$.

То есть у левого числа размерность ?/$, а у правого — $/?.



Вот в таком виде спутать различные обменные курсы никак не получится. Ведь у них размерности разные.

Только не надо делить 100 йен на 52.79 рублей, так как получившееся число придется округлить, и потеряется точность.

В итоге, с учетом всех нюансов, курс обмена будет выглядеть как-то так:

public final class ExchangeRate {
    public static final int PRECISION = 4;
    private final Sum clientGives;
    private final Sum clientTakes;

    public ExchangeRate(Sum clientGives, Sum clientTakes) {
        this.clientGives = clientGives;
        this.clientTakes = clientTakes;
    }
 
    public Sum exchange(Sum sum) {
        if (!sum.getCurrency().equals(clientGives.getCurrency())) {
            throw new IllegalArgumentException();
        }
        BigDecimal amount = sum.getAmount().mulitply(clientTakes.getAmount())
                .divide(clientGives.getAmount(), PRECISION, BigDecimal.ROUND_HALF_UP);
        return new Sum(amount, clientTakes.getCurrency());
    }
}

Комментарии (238)


  1. marckel
    18.09.2017 08:31
    +1

    Дополню.
    Алгоритм округления может отличаться в разных программах. Это приводит к тому что поставщик, выписывающий документы в Аксапте например, и покупатель, приходующий товар в одноэсе получат определенные расхождения в суммах товара.


    1. 4dmonster
      18.09.2017 09:47
      +2

      Поэтому в 1с можно ввести и сумму а не только цену.


      1. d-stream
        18.09.2017 10:35
        +3

        А потом «потанцевать» с фискальным чеком =)


      1. vanxant
        18.09.2017 10:45
        +1

        Не только поэтому. Есть ещё НДС, который нацело никогда не вычисляется.
        Если у вас есть табличка товар | цена | кол-во | сумма | ндс, и последней строкой у вас идёт «итого», то сумма по столбцу ндс чаще всего не будет равна итого*18%. И налоговая это нормально хавает.


        1. Mendel
          18.09.2017 13:41
          +2

          Честно говоря ожидал статью как раз по округлениям и хитростям с ними связанным. А тут только самые общие моменты.


        1. edb
          18.09.2017 15:11
          -2

          нормальные пацаны ведут учет НДС отдельно. То есть считаем НДС один раз для ИТОГО.


          1. vanxant
            18.09.2017 15:26
            +3

            Ну конечно. Счета-фактуры эти «нормальные пацаны» не принимают, не выставляют и не сдают в налоговую, потому как работают на помойке, так?


            1. edb
              18.09.2017 15:47

              Ну, ладно вам. Это вам просто законодательство такое не позволяет. В нашей же помойке, если речь не идёт про клиента-физика, все позиции счета идут без НДС, но в конце считаем НДС от всей суммы. Для физиков же позиции указываем цену с НДС, и так же для общей суммы считаем общий НДС. Но это конечно только в нашей помойке такое удобство…


              1. relia
                18.09.2017 16:04
                -1

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


                1. 4dmonster
                  18.09.2017 16:17
                  +2

                  А как, извините, применить ваш алгоритм для покупки в которой присутствуют товары со ставками НДС в 18%, 10% и 0%?


                  1. edb
                    18.09.2017 16:26
                    -1

                    вам ничто не мешает написать: НДС 18%: 10 руб, НДС 10%: 5 руб
                    А если НДС 0%, то зачем его указывать?
                    Или вы что-то другое имели ввиду?


                    1. 4dmonster
                      18.09.2017 16:34

                      Внутри чека разные ставки НДС, т.е.

                      То есть считаем НДС один раз для ИТОГО.
                      Не получится просто из-за отсутствия единой ставки.


                      1. relia
                        18.09.2017 16:42

                        Специально для минусителя законодательство РФ:

                        Налоговый кодекс

                        С т а т ь я 153. Налоговая база

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



                        1. 4dmonster
                          18.09.2017 16:44

                          Т.е. как раз внутри одного документа: «налоговая база
                          определяется отдельно по каждому виду товаров (работ, услуг),
                          облагаемых по разным ставкам»


                          1. relia
                            18.09.2017 16:48

                            А такие нюансы регулируются подзаконными актами. Прямого указания выписывать одну накладную на товары/услуги с разными ставками НДС я в вашем Налоговом кодексе не нашел. Как, впрочем, и запрет на выписку раздельных накладных.

                            И внимательно читайте второе выделение.


                            1. 4dmonster
                              19.09.2017 09:54
                              +2

                              Вот именно, в одной накладной можно выписывать разные ставки. А ваша придумка про разные накладные — не нужна.


                      1. edb
                        18.09.2017 16:43

                        я не вижу проблемы посчитать НДС для каждой ставки.
                        То есть у вас в чеке есть по одной строке для каждой ставки НДС.
                        Но если вы хотели меня поймать на том, что я сказал «считаем НДС один раз для ИТОГО», то считайте что вы победили, но мы говорили немного о другом.


                        1. 4dmonster
                          18.09.2017 16:46

                          Зачем ловить, вы сами так написали.

                          Следующей итерацией, можно дойти и до «не вижу проблемы посчитать НДС для каждой строки»


                          1. edb
                            18.09.2017 16:52

                            вот как раз и бессмысленность следующей итерации я и хотел показать.


                  1. relia
                    18.09.2017 16:27
                    -3

                    А как, извините, применить ваш алгоритм для покупки в которой присутствуют товары со ставками НДС в 18%, 10% и 0%?

                    Тупое минусение извинению не подлежит.

                    Речь не об алгоритмах, а о требованиях закона. Товары с разными ставками НДС будьте добры отпускать разными расходными накладными.


                    1. 4dmonster
                      18.09.2017 16:40
                      +1

                      «Тупое минусение извинению не подлежит.» это был эвфемизм.
                      А, извините, где и когда законы требуют такую, извините, странную операцию производить как «Товары с разными ставками НДС отпускать разными расходными накладными.»?


                1. edb
                  18.09.2017 16:22

                  А давайте вот у vanxant и спросим. Это же ведь он написал:

                  Если у вас есть табличка товар | цена | кол-во | сумма | ндс, и последней строкой у вас идёт «итого»,


                  1. 4dmonster
                    18.09.2017 16:36

                    итого для строки это сумма + НДС. Так что, несмотря, на отсутствие поля хранения итого в строке(что плохо, но возможно опущено, также как и ставка НДС), такая ситуация возможна.


    1. valis
      18.09.2017 11:50

      Более того — алгоритм округления реально может работать по разному на разном железе. Было что при переносе с теста на продуктив получали совершенно неожиданный результат.


      1. Regis
        18.09.2017 14:35
        +1

        Такое обычно происходит если вычисления выполняются во float/double (и отдаются на откуп железу). А если использовать decimal-типы, то вам гарантируется одинаковый результат на любом вменяемом железе.


      1. sergey-b Автор
        18.09.2017 14:42

        В яве можно добавить модификатор strictfp, чтобы эти граблей обойти.


    1. relia
      18.09.2017 15:46

      Если бы только в этом проблема была! В AXAPTA «не одобряется» использование более 2-х знаков после запятой, зато для 1сников 4...6 знаков для получения «красивых» сумм из-за НДС — почти норма.
      И сколько «этим» не рассказывай, что при ставке НДС 20% и цене товара 100 грн с НДС (83,[3] грн без НДС) будут копеечки гулять, — они все равно проводят «округления» в 4...6 знаке и потом удивляются, что остальные такие тупые и так не делают.


      1. cleaner_it
        19.09.2017 15:05
        +1

        Не нужно 1С-ников под одну гребенку чесать, иногда ситуации патовые, и что там одобряет Аксапта с 1С — дело десятое. С сайта госзакупок приходят цены с торгов, которые по закону включающих в себя налоги. И будьте добры, чтобы итоговая цена в реализациях совпадала с контрактной. Клиенты выкручиваются тем, что правят циферки в Экселе. И не волнует никого, что правильным расчётом эту цену с учётом НДС не получить. Так и появляются эти дикие дроби. Конечно, исходная проблема в том, что нет проверки правильного расчёта цен.


  1. SlavniyTeo
    18.09.2017 08:31
    +5

    Вместо double надо использовать представление числа на основе степеней с основанием 10.

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


    1. sergey-b Автор
      18.09.2017 08:32

      Какую монету наименьшей ценности выберем для биткоина?


      1. fireSparrow
        18.09.2017 08:38
        +16

        У биткоина есть минимальная неделимая единица «сатоши», равная 0.00000001 биткоина.


        1. tangro
          19.09.2017 09:50

          Биткоин в данном случае был плохим примером. А хорошим было бы, например, золото, которое меряется в унциях, граммах, фунтах, килограмах, тоннах — какую наименьшую неделимую массу золота можно предложить эталоном?


          1. fireSparrow
            19.09.2017 10:36
            +2

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


          1. Mendel
            19.09.2017 11:27
            +1

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


            1. sshmakov
              19.09.2017 17:25
              +1

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


          1. alexeykuzmin0
            19.09.2017 12:58
            +1

            3.27 x 10^-22 грамма.


            1. Germanets
              19.09.2017 16:21

              Ну если уж брать в молекулах, то обязательно получим преценденты, когда часть золота может испариться)


              1. alexeykuzmin0
                19.09.2017 16:23
                +1

                В атомах, у золота нет молекул, ибо это металл.
                Ну а вам не все ли равно, в каком виде золото пришло — в твердом, жидком или газообразном? =)


                1. Germanets
                  20.09.2017 09:34
                  +1

                  Ну если уж учитывать разные атомарные состояния, то молекулы у металлов всё-таки есть, правда это одноатомные(и иногда двухатомные) молекулы для газообразного состояния)
                  Но за поправку спасибо — перечитаю на досуге советскую энциклопедию по химии, освежу память)


    1. rafuck
      18.09.2017 08:42
      +4

      Сколько центов дадим за одну копейку?


      1. mayorovp
        18.09.2017 10:00

        Это уже вопрос представления данных.


        1. allter
          18.09.2017 11:21

          Нет, это вопрос предметной области, т.к. часто курсы даются с точностью до сотых долей цента/копейки.


          1. mayorovp
            18.09.2017 11:35

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


          1. Mendel
            18.09.2017 13:45

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


          1. brzsmg
            18.09.2017 15:11
            +1

            курсы даются с точностью до сотых долей цента/копейки


            Курс в теории не ограничен количеством знаков после запятой.
            ЦБ на сайте выдает до 4 знаков после запятой.
            Но при при конвертации миллионов $, с таким курсом можно потерять несколько тысяч рублей.
            Поэтому иногда используют курс с 6 — 8 знаками после запятой.


            1. fukkit
              18.09.2017 20:28

              Поэтому иногда используют курс с 6 — 8 знаками после запятой.

              Любые 2-4 к ЦБшному дописывают или строго нули?


              1. alexeykuzmin0
                19.09.2017 12:59

                Я это встречал только в банках при хранении внутренного курса банка. Там дописывают те, которые считаю нужным (да и первые цифры тоже от ЦБшных могут отличаться).


      1. rraderio
        18.09.2017 14:17

        40 nano центов


      1. Germanets
        18.09.2017 14:59

        Ни одного, и вообще такие операции откажемся проводить — так сейчас считают большинство обменников, с которыми я сталкивался… Есть минимальная сумма, от которой операция разрешается — это округлённое в большую сторону количество копеек за один цент.


    1. Alexsandr_SE
      18.09.2017 10:00

      Лог лучше или хуже в данном случае? А то у себя для денег как раз лонг выбрал.


      1. mayorovp
        18.09.2017 10:01

        Это вопрос необходимого уровня абстракции


    1. ds_ufa
      18.09.2017 10:11

      Плюсую. В одном конкретном известном мне банке так и делается (про остальные банки просто не в курсе).


      1. ds_ufa
        18.09.2017 10:20

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


        1. Ivan22
          18.09.2017 10:25

          в банках в абс деньги хранятся в базе в обычных decimal. Сам виде.. делал


          1. sergey-b Автор
            18.09.2017 10:33
            +1

            Боюсь, что в банках в этом плане полный зоопарк. В пределах одной АБС может быть в одной таблице decimal, а в другом bigint или даже float. Поскольку нигде нет документации, где сказано: «Вот делайте так, и да пребудет с вами Cила».


          1. ds_ufa
            18.09.2017 10:38

            то, что decimal лучше подходит для хранения денежных сумм, это да, не спорю. Но если, например, от 1353 рублей 65 копеек нужно взять 18,5 % за несколько дней, то результат легко может быть разным в случае округления до целых копеек до суммирования по дням и в другом случае, когда округление выполняется после суммирования.
            Ниже уже написали, все это должно быть прописано в нормативных документах банка.


            1. Ivan22
              18.09.2017 14:28

              ну реальность такова, что на все расчеты нормативки не напасешься. Плюс в половине случаев даже аналитики не знают где ее искать. Но это так, лирика.


              1. Ndochp
                18.09.2017 15:39

                В основном в кредитных договорах формула тщательно расписана, включая краевые эффекты, прямо в договоре. Полагаю формула в договоре согласована с формулой в софте.


    1. d-stream
      18.09.2017 10:36

      Цена с НДС 2311 «монеток» — сколько это будет без НДС? а сколько будет величина НДС? -)


      1. bopoh13
        18.09.2017 11:20

        Стоимость без НДС = Стоимость с НДС / 118 * 100 (если НДС 18%)
        НДС = Стоимость с НДС / 118 * 18 (если НДС 18%)


        1. d-stream
          18.09.2017 11:25

          Hint: не все числа с точностью 2 знака после запятой можно безболезненно разделить на 1.18 =)

          Ну или попроще «3 шарикоподшипника на сумму 100 руб.» -)


          1. bopoh13
            18.09.2017 11:53

            Всегда можно найти компромиссы: либо стоимость с НДС делать кратным 1.18, либо к любой стоимости без НДС добавлять НДС (избавить себя от лишних расчётов).


            1. d-stream
              18.09.2017 11:59

              Ога-ога =)
              Заодно построить поставщиков на предмет кратности -)

              p.s. Next Level: счет-фактура и фискальный чек — там несколько взаимоисключающе вычисления -)

              p.p.s. Собственно решения для всех этих случаев есть, не всегда бескостыльные, но тут увы…


              1. cleaner_it
                19.09.2017 15:13

                Точно. Особенно, если контракт с сайта госзакупок пришёл


                1. d-stream
                  19.09.2017 15:29

                  Хватает даже просто контрактов с полугосами, по которым после отгрузки вылезает отказ в оплате типа «в документе хххх на сумму 100500 миллионов рублей 37 копеек в строчке 47645 указана цена за единицу 13.11 копеек и количество 0.01, а в контракте на странице 873 оговаривалась цена 13.10 копеек без НДС» -))


  1. KvendyZ
    18.09.2017 08:46
    -1

    я человек простой. прочел. понравилось. говорю автору спасибо!
    з.ы. и это, друг, в 3:30 лучше спасть, а не на хабр писать xD


    1. alan008
      18.09.2017 09:13
      +2

      А может он из Владивостока


      1. Oxoron
        18.09.2017 11:12
        +1

        где-то тут была статья про время…


  1. Cobolorum
    18.09.2017 08:52
    +7

    Те кто не когда не работал в банках может что то и откроют для себя новое, но реально все гораздо сложнее.
    В нормальных системах существует цела иерархия объектов и классов предназначенных для работы с денежными объектами. И она существенно сложнее чем вы описываете.
    Есть правила расчетов – то с какой точностью ведутся расчёты для примера для рубля РФ это чаще всего 4 знака после запятой(т.е. с точностью до 0,01 коп.)
    Правила округления, правила отображения и печати сумм. Вводится понятие операция: кредитовая или дебетовая. И еще много вроде бы очевидных вещей, но наличие «стандарта» на них обязательно.
    А уж если вводился понятие счет (объект для хранения сумм и правил работы с ними) то описания подобных вещей реально взрывает мозг. Там и связанные счета и активные и пассивные. А сверху это цементируется понятием «проводка» чтобы исполнялся закон сохранения энергии — закон сохранения денег.
    И все это ноу-хау о которых разработчики не очень любят распространяться.


    1. novice2001
      18.09.2017 09:26
      +3

      Это никакое не ноу-хау, это знание предметной области.


    1. sergey-b Автор
      18.09.2017 09:35
      +6

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


      1. Ivan22
        18.09.2017 10:27
        +1

        ну в статье даже термина «проводка» нету. Реально лайт. А двойная запись и баланс — основа денежного учета в нашем мире.



  1. SbWereWolf
    18.09.2017 09:23
    +1

    Спасибо, очень доходчиво


  1. mmMike
    18.09.2017 09:51
    +4

    Не так давно в новостях много рассказывалось о так называемом «техническом овердрафте», который якобы образовался на карточных счетах клиентов в одном российском банке.

    Технический овердрафт по карточному счету это немного другое.
    Сложилось так, что кредитовый карточные продукты (с точки зрения платежной системы) выдают в банках клиентам, у которых нет кредитного лимита. В результате возникают такие уродцы как "Золотые дебетовые карты Visa и MasterCard".
    Кредитная карта (по правилам платежной системы) допускает операции в offline без жесткого контроля лимита, а у клиента кредитного лимита в банке нет.
    И такие ситуации (на счете 0, а приходит presentment по операции сделанной ранее в offline), скромно называют "технический овердрафт".
    Назначая драконовские проценты и т.д. Забавно все это...


    Вообще то, для дебетового счета существуют дебетовые карты (типа Visa Electron). Технология которых подразумевает только on-line авторизации (по правилам платежной Системы), что в какой то мере гарантирует "дебетовость". Про извраты с приемом дебетовых карт в offline лучше печально промолчать.


    Само наличие у человека кредитной карты (кредитные карточные продукты) в других странах означает: Банк клиенту доверяет и открыл ему кредитную линию. Gold кому попало не дают. Поэтому для аренды машины, бронирования и пр. карту кредитную (кредитовый карточный продукт Платежной Системы).


    А у нас даже бомжу (если почитать на сайтах Российских банков) могут Visa Platinum выдать на дебетовый счет.


    1. sergey-b Автор
      18.09.2017 10:04
      -1

      Так ли часто бывает, что у держателя карты нет денег на счету, а он этой картой за границей расплачивается? Как же ему визу дали?

      Думаю, в основной массе там имело место списание платы за обслуживание. И вот тут как раз вопрос, куда они эти средства зачислили. Если в доходы, то это провал.


      1. mmMike
        18.09.2017 10:23

        Так ли часто бывает, что у держателя карты нет денег на счету, а он этой картой за границей расплачивается?

        Да сплошь и рядом. Сам так попадал. Не ту карту сунешь второпях..


        Как же ему визу дали

        C визой это никакой связи не имеет. Причем здесь деньги на счету. Никто это не контролирует. Всегда только справку о доходах на шанген подавал (и дают его на 3-5 лет обычно).


        Типичный способ попасть на теховер:


        1. несколько карт.
        2. Перепутал карту. (воспользовался специально выделенной дебетовой с минимальным остатком для paypal и/или покупок в Азии)
        3. не удосужился проконтролировать остаток перед поездкой
        4. и пр.

        Оплачиваешь услуги типа платной дороги, кофе в вендинговой машине, проезд в метро и тп. и тд. (суммы мелкие и как правило в offline).
        Запросто можно выйти за пределы остатка и "попасть" на так называемый теховер.


        Да еще эти транзакции в банк могут приползти через неделю или больше.


        Думаю, в основной массе там имело место списание платы за обслуживание.

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


        Что там проскочило в новостях (без всяких подробностей)… Уровень журналистов знаю и поэтому такие "желтые" новости всегда делю на 10 минимум.


      1. Dimes
        18.09.2017 12:50

        за границей можно и в интернете расплатиться, и если за 1-2 дня курс резко скакнул вверх, то легко попасть в теховер — авторизация (блокировка средств) приходит по курсу даты совершения операции, а списание фактически происходит через 2-3 дня по курсу того дня, когда оно пришло в банк.
        день Х — на карте 5600 рублей, курс бакса 56, покупка на 100 баксов прошла успешно
        день Х+2 — курс 58 рублей, пришло списание 100 баксов, с карты списалось 5800 баксов и клиент должен банку 200 рублей (тех овер).

        Ну и еще миллиард способов попасть в тех овер — типа штрафов за прокат машины, выпитый минибар в отеле и т.д.


      1. cdmlex
        18.09.2017 13:20
        +1

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


        1. Dimes
          18.09.2017 14:46

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


          1. mmMike
            19.09.2017 05:58
            +1

            просто полет фантазии.

            Нравятся мне такие без безапелляционные заявления…
            Вы вообще в этой обрасти работали когда ни будь?
            Я так уже больше 15 лет платежными картами занимаюсь.
            Слипы блин…
            У меня вот на моей личной EMV карте лимит offline есть. Хотя она к дебетовому счету привязана. Сам лично в служебном ПО ее ковырял и профиль анализировал. Чисто из любопытства.
            Зачем банк такие карты заказывал — ХБЗ.


            1. vikarti
              19.09.2017 07:33

              А разве оффлайн лимит небольшой в некоторых случаях не штатная функция?
              Просто вспоминаются покупки через Samsung Pay с использованием дебетовой карты, место такое что связи там нет, оплата проходит успешно, ИБ платеж не видел несколько дней хотя обычно сразу видит. Суммы в этих случаях всегда меньше 1000 рублей были.


              1. mmMike
                19.09.2017 07:56
                +1

                А разве оффлайн лимит небольшой в некоторых случаях не штатная функция?

                Штатная для кредитных карт, привязанных к банковскому счету с кредитной линией.
                В иных случаях (дебетовый счет) запросто может возникнуть ситуация "технический овердрафт".
                Есть Кредитный карточный продукт Платежной Системы. Он четко описан правилами конкретной платежной системы. Кредитные карточные продукты технологически допускают транзакций без online обращения к эмитенту (к процессингу эмитента). Это означает, что если эквайрер выполнил все требования ПС по авторизации (выполнил без обращения к хосту эмитента), то это не его дело есть деньги на счету клиента или нет.
                Это проблема банка/организации эмитента карт.


                По простому… (исторически сложилось… слипы..), если карта с "выпуклыми цифрами" — то это кредитный карточный продует. Если с "давленными", то дебетовый.


                Кстати, никто не мешает эквайреру обслуживать и, например, Maestro в offline (видел и такое извращение), особенно если эмитента = эквайрер.
                Но в общем случае по дебетовым картам технический овердрафт не получить.


                Хотя я видел и Maestro и Electron (логотип) эмбоссированных как будто это Кредитный карточный продукт. Уж не знаю, как они апрув получили на эмиссию такого дизайна.


                В общем, не надо путать Карточный продукт Платежной Системой с тем, как какой счет в банке. "Снаружи" никто и не знает дебетовый у вас счет или с кредитной линией.


                1. Dimes
                  19.09.2017 11:24

                  Оффлайн лимит, CVM-лист и тип продукта вообще ни как не связаны.
                  Про эмбоссинг/индент тоже чушь :) разница только в том, можно ли ее катать в импринтере или нет.


                  1. mmMike
                    19.09.2017 11:38

                    Про эмбоссинг/индент тоже чушь :) разница только в том, можно ли ее катать в импринтере или нет.

                    Да ну? А Вы не в курсе про требования к продуктовому дизайну карт, рекомендованным профилям персонализации (куда CVM входит) и согласование эмиссии с ПС (Visa|MC|CUP|Мир)?
                    Что не разу не сталкивалиcь? Ни разу такие документы от ПС не видели и процедуру согласования эмиссии не проходили?


                    И правила для эквайреров по настройке терминалов/обслуживания разных карточных продуктов (по AID, по логотипам) ни разу не читали?


                    Что то меня сомнения берут насчет вашего опыта в 12 лет...


                    1. Dimes
                      19.09.2017 12:14

                      да все это согласование это заполнение трех экселек-опросников :)
                      хорошо если бизнес вообще понимает что туда пишется и сколько денег за это платить :) а так туда можно вообще все что угодно запихать и мпс радостно согласует — любой каприз за ваши деньги.


                      1. mmMike
                        19.09.2017 12:25

                        Ну да. Только в опроснике обычно ссылка идет на типовой профиль.
                        А если что то не типовое, то переписка с ПС (а можно так?).
                        Возможно эта переписка перестраховка. Но кому хочется брать на себя ответственность?
                        Бизнесу все подешевле… бы…
                        И появляются уродцы типа Maestro|Electron instant карта с эмбоссированием и дизайном косящим под visa infinite. "А чо круто же смотрится".


                  1. mmMike
                    19.09.2017 11:47

                    чего то я начал через чур активно возмущаться; )
                    Задевают фразы "чушь".
                    Извините.
                    Предлагаю прекратить и общаться покорректнее.


                    А тип продукта и профили и дизайн все же связаны.
                    Получить от Плат. Системы добро на "нестандартный" дизайн/профиль — практически не реально


                    1. Dimes
                      19.09.2017 12:11

                      делали в мпс на букву М голдовую карту с индентом по бину зарегистрированному как кредитный. без смс и регистрации.


                      1. mmMike
                        19.09.2017 12:20

                        Наверное у вас крупный банк, что MC так пошел навстречу.
                        Голд с идент это что очень странная комбинация. ATM все равно. Хоть Gold, хоть Cirus.


                        А смысл Голда, на мой взгляд, помахать им в аэропорту что бы в VIP lounge попасть. Но Gold с идентом…
                        Народ (сотрудники сервиса) то привык к конкретному внешнему виду карт. Им же "буквари с картинками" показывают..


                        Впрочем, я и не говорил, что не бывает. Приводя пример Electron c эмбоссированием.
                        Но как объяснить обычному человеку как на беглый взгляд отличить разные карточные продукты? Логотип и тип печати.
                        Тип печати нагляднее и понятнее и в 99% верен.
                        Разве не так?


                        1. Dimes
                          19.09.2017 12:42

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


                          1. mmMike
                            19.09.2017 12:46

                            Карта это то же весчь бывает.
                            Валяются образы пластика (рекламные):


                            1. Светящиеся (Не помню чьи)
                            2. Прозрачные с голограммой бабочки (шлямбурже еще)
                            3. С запахом. Уже не пахнут… (Gеmplus)
                            4. Бесконтактные в разном форм-факторе (только в зубную щетку не догадались засунуть)

                            Даже обидно, что рано или поздно будет вытеснены другими средствами платежа.


                            Даже самые первые Java карты Gemplus (еще с усеченном 3DES) валяются. И VisualBasic OS карта и диск Microsoft SDK для нее (2000год).
                            RIP карте MS..


                            1. MacIn
                              19.09.2017 19:44

                              И VisualBasic OS карта

                              Что это за зверь?


                              1. mmMike
                                20.09.2017 05:22
                                +1

                                Была неудачная попытка Microsoft выйти на рынок смарт карт. В пику Java картам. VB в качестве языка написания апплетов… Вызвала неподдельный (скептический) интерес на конференции в 2000 году.
                                Нашел у себя диск тех времен и из ностальгических воспоминаний памятнику ему сделал: )



                                1. MacIn
                                  20.09.2017 10:36

                                  Хм, это они смело. Спасибо.


              1. Dimes
                19.09.2017 11:18

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


            1. Dimes
              19.09.2017 11:11

              здравствуйте коллега :) 12 лет в отрасли :)
              как вы знаете по своему опыту — оффлайн операция прилетит в клиринге, а это лаг минимум в 1-2 дня. Если в банке пополнение карты через АТМ происходит через АБС сперва а синхронизация АБС-Процессинг имеет лаг больше 1-2 дней, то мое утверждение про кейс 20-летней давности и/или полет фантазии справедливо.

              Однако если вы 15 лет печатаете пин-конверты, то скорее всего этого не знаете, хотя и работаете в этой отрасли: Р


              1. mmMike
                19.09.2017 11:31

                cинхронизация АБС-Процессинг имеет лаг больше 1-2 дней

                Это кейз 20 летной давности. Голубиной почтой…
                Не встречал банк, который бы не требовал синхронизации минимум раз в день.
                А большинство вообще через on-line интерфейс с процессингом предпочитают.
                Впрочем, без конкретных примеров АБС+процессинг — это разговор ни о чем.


                Если в банке пополнение карты через АТМ происходит

                Если уж корректно говорить, то функция пополнения счета наличными в АТМ с идентификацией (аутентификацией) по карте, это не стандартная операция и вариантов реализации существует много. И процессинг (если это его сетка) может поддерживать это. И в АБС может напрямую (через on-line API) зачисления идти.


                Однако если вы 15 лет печатаете пин-конверты, то скорее всего этого не знаете, хотя и работаете в этой отрасли: Р

                Начнем бодать и меряться? Так смысла нет. Если вы еще в этой области работаете, то прекрасно понимаете, что конкретными примерами я не могу оперировать (иначе по шапке получу).


                А на счет PIN конвертов… А что, вам известны какие то другие способы кроме:


                1. Выдачи карты с конвертом.
                2. Выдачи карты с собственоручным вводм клиентом нового PIN в служебном терминале.
                3. Выдачи PIN через AVRS (телефон)


          1. cdmlex
            19.09.2017 07:29

            в процессинге информация то есть, а вот в банке — еще пока нет.


            1. Dimes
              19.09.2017 11:04

              разрешение на снятие средст с карты (авторизацию) как раз процессинг и дает.
              Нет средств — в авторизации откажут с 51 кодом.


              1. cdmlex
                20.09.2017 07:25

                На карте есть деньги
                ПЦ об этом знает (как следствие разрешение на авторизацию ПЦ дает)
                Банк — не знает
                В Банке открывается теховер, не в процессинге, а в банке.


                1. Dimes
                  21.09.2017 10:54

                  так не бывает :) просто в банке ни чего не открывается и не происходит.
                  счета ведутся в АБС, а не где-то в вакууме в банке.
                  Теховер это задолжность по счету неразрешенной задолженности. Задолжность по счету образуется в результате банковских проводок. Проводки по банковским картам попадают в АБС в результате появления требований с клиентского счета от платежных систем или собственных устройств. Эти требования в первую очередь появляются в процессинге (если свое устройство то от устройства при проведении операции, если другого банка то в результате файлового обмена с платежной системой — виза/мс/мир).

                  Если о пополнении карты процессинг знает и дает разрешение на авторизацию, то и проводки по пополнению он тоже выгрузит — одновременно с требованием по операции списания.

                  Хотя конечно нарукожопить можно все что угодно. Например требования выгружать в течении всего дня, а пополнения выгружать только при закрытии дня(или вообще после инкассации кеш-ин банкомата :) ).


        1. Portnov
          18.09.2017 19:16

          В актуальных системах конкретно описанные вами сценарии не пройдут. Процессинг знает, на какую сумму он одобрил вам транзакций, и не даст уйти в минус (отклонит транзакцию). Но, я вполне верю, что такие ситуации возможны в какой-нибудь условной Оклахоме, где используются системы тридцатилетней давности, импринтеры, слипы, вот это всё.

          Реальный сценарий, возникающий из-за разделения авторизации и финансового представления: вы купили товар за 100$ = 5867р, и остался у вас на счету 1р. А представление пришло через неделю, и обработалось по курсу 59.00р/$, вот вам и минус на счету.


          1. mmMike
            19.09.2017 06:17
            +1

            В актуальных системах конкретно описанные вами сценарии не пройдут.

            Проезд на транспорте по банковской бесконтактной карте — практически никто не использует авторизацию в on-line. (Авторизации проводятся позже пакетно. С утерянной выгодой мирятся).
            Вендинговые машины в Европе… везде offline.
            Оплата платных дорог — везде offline.
            Курьез — оплата туалета на ЖД вокзале в Норвегии (точно не помню какой) — картой в off-line.


            Ага… системы 30 летней давности… слипы…
            Off-line лимит на VSDC|UP|MChip карте — это отнюдь не "off-line остаток". А просто типично сбрасываемый в начальное значение лимит при любой успешной online авторизации.
            Сделан под кредитные банковские продукты. А не под дебетовые.


            То что Вы никогда не сталкивались с off-line платежами по чиповым картам совсем не означает, что это их не бывает.


            1. Dimes
              19.09.2017 11:27

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


              1. mmMike
                19.09.2017 11:43
                -1

                Ну да. Там сумбурно написано. Сходу понял не так.
                Перечитал еще раз.


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

                Такого конечно не бывает. Теховер по on-line операциям (снятия нала в АТМ) естественно не возможен.


      1. vlivyur
        18.09.2017 16:50

        Как же ему визу дали?
        Занимаешь денег у друзей на день. Кладёшь их на счёт в банке. Берёшь справку с этого счёта. Снимаешь деньги. Раздаёшь занятое. Профит.


        1. sergey-b Автор
          18.09.2017 21:05
          +1

          А потом уходишь в минус и гуляешь без денег по Парижу в поисках, чего бы пожрать…

          Эх, как же скучно я живу.


          1. vlivyur
            19.09.2017 14:01

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


    1. vlivyur
      18.09.2017 16:46

      С Visa Electron тоже может не прокатить — «Зачисление проводится раз в сутки, поэтому денег, которые вы только что положили на карту, на счёте нет, они есть только на карте».


  1. TOBBOT
    18.09.2017 09:52

    Хорошо написано. Может когда-нибудь и пригодится. Спасибо!


  1. ds_ufa
    18.09.2017 10:05

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


    1. sergey-b Автор
      18.09.2017 10:13
      +2

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

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


      1. ds_ufa
        18.09.2017 10:15

        > На это в кредитной организации должны быть нормативные документы. Разработчик тут решать не может.

        Абсолютно согласен


      1. dernasherbrezon
        18.09.2017 11:22

        Именно потому что округление все равно нужно, правило "не используйте double потому что сложно округлять" бессмысленно.


        Если много операций, то double будет на порядок быстрее BigDecimal. Не всё так однозначно.


        1. sergey-b Автор
          18.09.2017 11:32
          +1

          О, спасибо. Вспомнил 6-е правило: не экономьте на вычислениях с суммами.


          1. dernasherbrezon
            18.09.2017 11:46
            +3

            Если давать советы, то нужно объяснять не только минусы, но и плюсы. А то придут новички, прочитают и бездумно применят.


            У нас был отличный пример как разработали систему в которой все immutable, stateless и BigDecimal. Проблема с этой системой только в том, что ей нужно 20Gb Heap, чтобы показать табличку пользователям. Максимум 5 пользователей.


            1. staticlab
              18.09.2017 12:48

              Так может использовать long, а не BigDecimal?


              1. dernasherbrezon
                18.09.2017 13:33

                У меня, к сожалению, нет опыта использования long. В принципе должно сработать, если везде передавать: long amount + int precision.


                Disclaimer: не пытайтесь повторить это дома и в бухгалтерской системе где скорость не важна :)


            1. sergey-b Автор
              18.09.2017 15:30

              Вообще да, mutable BigDecimal был бы очень полезен. Так как каждое значение в новый объект запихивать может быть очень накладно. Его можно для своих нужд быстренько сделать или готовый скачать.

              Я, пожалуй, погорячился насчет экономии, но ради производительности double применять все равно не стоит.


    1. psvg42
      18.09.2017 10:19

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


      1. ds_ufa
        18.09.2017 10:21

        К сожалению, не панацея. Например, при начислении процентов по вкладам или кредитам.


        1. fukkit
          18.09.2017 21:02

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


          1. ds_ufa
            19.09.2017 06:44

            Есть вот такая сложность. Допустим 10000 рублей лежит на депозите 100 дней. По ним за 100 дней получается некоторая сумма процентов. И берем второй случай, когда 10000 положили на депозит, через 50 дней сняли, открыли другой депозит, положили на него те же 10000 рублей еще на 50 дней. В итоге с высокой вероятностью получится, что начисленная сумма процентов за 100 дней на одинаковую сумму будет различаться на копейки. Казалось бы — мелочь. Но в реальности может оказаться головной болью для кредитной организации.


            1. fukkit
              19.09.2017 07:55

              На практике никто не сравнивает суммы процентов за 100 дней по одному договору и за 50+50 по двум другим, у банка нет обязанности поддерживать их одинаковыми до копейки, а следовательно, нет и проблемы.

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


              1. ds_ufa
                19.09.2017 08:26

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

                Фактически, в этом банке расчет начисляемых процентов по вкладам и кредитам идет на остаток на счете за каждый отдельный день, затем округление до копеек и суммирование процентов по дням за заданный период. Тогда проблема с вкладами на 100 и на 50 дней не возникает. Правда, и у этого метода есть недостаток. Так, если на двух депозитах на 1 год под 10% лежат суммы 10000р. и 10000р. 10к., то сумма процентов за год получится по ним одинаковая. Хотя, должна отличаться на 1 копейку.

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


                1. fukkit
                  19.09.2017 11:44

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

                  При таком расчете копится ошибка округления, причем немаленькая. А мелкие суммы (ниже 30 или 15 рублей, в зависимости от способа округления) могут вообще неопроцентиться никогда.

                  При изменении суммы вклада (а также процентной ставки или любых других условий, влияющих на расчет процентов, например, количества дней в году) в рамках действия одного договора вводится понятие «периода постоянства условий начисления», период с начала действия договора по дату начисления разбивается на «периоды постоянства...» по ним проходят начисления процентов с округлением до копеек, затем начисленные проценты складываются.

                  Вот такие ноу-хау. Иногда настолько хау, что большинство внедренцев АБС ни разу ни ноу (печальный опыт)


  1. staticlab
    18.09.2017 10:06
    +4

    А ещё "сумму" лучше называть не sum, а amount.


    1. sergey-b Автор
      18.09.2017 21:07

      У меня там внутри класса Sum поле amount.


      1. staticlab
        18.09.2017 22:25
        +2

        Кажется, Amount.value было бы корректнее.


  1. mayorovp
    18.09.2017 10:14
    +3

    Про неотрицательные суммы — не согласен. В приведенном вами примере вы, фактически, предлагаете заменить одну проверку двумя (нужна была проверка на неотрицательность дебетового счета, предлагается ввести неотрицательность любого счета, автоматический кредит при нехватке средств и запрет кредита на дебетового счете). Но ведь проблема-то и заключалась в том, что программисты даже одну проверку — и ту забыли написать!


    Чтобы избежать таких вот ошибок — надо не усложнять модель, а вводить внутреннее API, которое просто не даст совершить некорректную проводку.


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


    1. sergey-b Автор
      18.09.2017 10:38

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

      Мой подход таков: Каждый переход суммы через 0 — это инцидент, требующий анализа. Когда же мы используем знак, то мы просто откладываем расследование этого инцидента со стадии разработки в рантайм.


      1. Mendel
        18.09.2017 14:13
        +1

        Почему в рантайм?
        Знак числа никоим образом не может быть единственным источником типа счета.
        Это даже не вопрос грамотной нормализации и т.п., а просто неизбежный факт — возможность существования нулевых значений у счета делает данный признак бессмысленным.
        На самом деле у любого действия с деньгами (будь то проводка или «кварковая проводка» моих оппонентов, не суть) есть информация о том с каким счетом мы работаем. И этот счет «знает» свои правила. В простейшем случае — активные счета не могут быть отрицательными, пассивные — положительными. В реальной жизни таких валидаций может быть много разных. И на суммы транзакции, и на «неснижаемые остатки» и счета у которых всегда ноль, и только ноль и т.п.
        Соответственно если у нас стоит ограничения что бывают только активные и пассивные счета, а активно-пассивных нет в принципе (а это достижимо даже в регламентированном учете), то в рантайме как раз переходов через ноль не будет физически, ибо валидатор просто не пропустит такую операцию. Так что разбираться придется еще на этапе разработки или тестирования.


        1. sergey-b Автор
          18.09.2017 14:25

          Просто мы с вами используем разный понятийный аппарат. То, что вы называете «отрицательным активным счетом» я называю «кредитовым сальдо по активному счету». Когда вы говорите, что «пассивный счет не может быть положительным», я говорю:
          «Образование в конце дня в учете дебетового сальдо по пассивному счету или кредитового по активному счету не допускается».

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


          1. Mendel
            18.09.2017 14:41

            Дело не в терминологии а в контексте.
            Я говорю о том, что отрицательные суммы В БАЗЕ очень даже хорошо смотрятся если счет пассивный. Это упрощает целый ряд вычислений. Например это упрощает проводку. Нам не нужно вот это вот «если счет активный, то дебет это плюс....». Дебет у нас всегда плюс, кредит всегда минус. Мы упрощаем контроль целостности — просто сумма баланса всегда равна нулю, иначе «ужас-ужас» и падаем с ошибкой, и т.п. А так, то я сторонник того чтобы счета были либо строго активными либо строго пассивными, а если сильно нужно активно-пассивный, то делать два счета и отдельно сверху логику их взаимного зачета.


            1. sergey-b Автор
              18.09.2017 14:53
              -1

              если счет активный, то дебет это плюс

              Когда все суммы положительные, то не нужен ни плюс, ни минус. Поэтому я считаю, что моя модель проще. Мне не нужно ничего запоминать.

              У вас дебет — это плюс? Т. е. вы от кассы считаете? Вам так удобно?

              Если бы я использовал плюс-минус, то у меня дебет был бы минус.


              1. Mendel
                18.09.2017 15:03

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


            1. Kan70
              18.09.2017 15:17
              +1

              Надо разделить понятия — тип счета и тип остатка по счету.
              Счета могут быть активные, пассивные и активно-пассивные по типу. И у счета любого типа может быть активный (дебетовый) или пассивный (кредитовый) остаток (+ или -). Просто когда у счета «неправильный по знаку (неположенный ему по типу) остаток» — то и называют сие счет с «красным сальдо».
              Валидаторы проверки остатка по типу остатка и контролируют может ли быть пассивный (кредитовый) остаток по счету например по типу — активному (при наличии признака например на этом счете «разрешенного красного сальдо»).

              Говорить что «нельзя иметь активно-пассивные счета» — достаточно недальновидно. Есть системы бухгалтерского учета где они активно используются и непонятно почему ради этого надо держать два и т.п. счета. Если пользователю нужен такой счет — ну пусть объявит счет с таким типом и валидаторы будут пропускать любые проводки по этому счету без контроля знака остатка. С контролем проводок тоже нет однозначности. Во многих системах бухучета есть возможности за контролем транзакции — когда например один счет по дебету и 2 счета по кредиту (например по дебету проводка на 1000, по кредитам одна на 300, вторая на 700). Просто идет тогда контроль что общая сумма дебетов и кредитов в одной транзакции равна.


              1. Mendel
                18.09.2017 15:40

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


            1. Ndochp
              18.09.2017 15:47

              Счет 70 (взаиморасчеты с сотрудниками) в день аванса — самый частый пример пассивного счета с отрицательным остатком на хозрасчетном плане счетов.
              В банках вроде с этим борятся.


  1. White_Scorpion
    18.09.2017 11:46

    Дополню, что в конвертации валют надо обязательно зафиксировать 0.01 единицу валюты для округления и привязать её к конкретной стороне, чтобы А -> Б при округлении в стиле 3.455 — шло как 3.46, а наоборот 3.45. Т.е. принудительно! Обычно это делается в сторону основной валюты. Избежите кучи проблем.
    Также обратите особое внимание на обработку нулевых сумм (0.00) и сумм очень маленьких (0.01) — дабы избежать начисления процентов на суммы по типу: 0.00 и 0.0001 (некотоыре системы, например топливные, работают с 3 цифрами после запятой, а банки вообще могут работать и с 5-тыми цифрами после запятой).


    1. sergey-b Автор
      18.09.2017 12:49

      Вы совершенно правы. Даем 1000?, получаем по курсу 58.79?/$ 17.0096$.

      Если округлить в большую сторону, то получится 17.01$ на руки и потеря (хотя, кому-то и выгода) 2 копеек. А на самом деле нужно округлить до 17.00$ и выдать 57 неразменянных копеек на сдачу.


  1. AlexPu
    18.09.2017 12:13
    +2

    1) Отрицательные значения денежных сумм вполне обычное явление — автор статьи просто считает, что его личный опыт является всеохватывающим, и ничего что не укладывается в этот опыт не может существовать в природе. Привожы простейший пример (который я регулярно наблюдаю в в онлайн-клиентах банков клиентами который я являюсь — Nordea и Danske Bank) кредитные счета — тут положительное значение счета есть размер моего долга. Задолженности по кредитным картам я лично погашаю в течение первого месяца после их возникновения (для меня кредитные карты это просто средство платежа через интернет — в магазинах удобнее использовать дебетовые карты). Теперь представьте — в течение месяца я совершил покупок на EUR 189.67. поскаольку я человек исключительно неаккуратный и запросто могу забыть оплатить счет по карте я просто плачу при подходящей оказии «вперед» и с запасом — т.е. когда я в очередной раз залогинюсь в онанй банк, я проверю что там у меня на кредитном счете и переведу туда денег чуть больше. т.е. в данном примере я просто переведу 200 евро и не буду больше париться по этому поводу. Теперб вопрос к автору — каким образом онлайн банк должен отобразить остаток EUR -10.33 — возможно он что нибудь придумает, но разработчики банков Nordea и Danske Pankki пока ничего умнее отрицательного остатка не придумали. Другое дело, что и счетов с отрицательными значениями суммы к оплате мне никто не присылает — у нас нулевой счет вообще не обязательно присылать (его генерят и тут-же «закрывают» при этом честные люди не взимают в таком случае invoicing fee)


    1. sergey-b Автор
      18.09.2017 12:26

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

      Теперь представьте, что кто-то вам закинул без вашего ведома 380 евро. Вы помните, что у вас долг около 200, заходите и видите -190.33. Вы быстренько берете и закидываете заготовленные 200. Потом, когда сумма станет -390.33, вы поймете, что это значит, до следующего раза, который произойдет может быть через год.

      А надо было вместо знака минус написать словами Переплата или Собственные средства.

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


      1. mayorovp
        18.09.2017 12:34
        +1

        "Переплата" вместо минуса — это не анализ перехода через ноль, а простое условие при отображении.


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


        1. Mendel
          18.09.2017 14:25

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


        1. Dejsving
          18.09.2017 15:08

          Соглашусь с mayorovp. Автор анализирует и советует на тему внутреннего представления.
          А минус на счете — это представление пользователю.
          Мне наоборот при анализе счета удобнее не 2 колонки, а приход "+", трата "-". Так интуитивно понятнее для непосвященного пользователя. Чем пресловутые дебет/кредит.
          Хотя идея Nordea (со слов sergey-b — я с этим банком не знаком) мне не нравится.
          Логичнее переплату показывать как "+", а вот кредит как "-" — ибо кредит на кредитных картах, это Вы должны банку.


      1. AlexPu
        18.09.2017 14:53
        -3

        >>Таков мой подход

        В этом все идея — вы искренне полагаете, что есть ваш подход и неправильный подход… Но к сожалению, о ващем подходе ничего не известно ни в банке Nordea ни в Dabske Bank (и очень очень сильно подозреваю, что ни в одном другом банке планеты тоже о вашем подходе ничего не известно)

        >>А надо было вместо знака минус написать словами Переплата или Собственные средства.

        Очень советую вам никогада и нигде не упоминать про это ваше гениальнейшее изобретение. Ибо:
        1) переплата есть суть транзакция и оная транзакция УЖЕ отражена в списке транхакций вашего (ну или моего) кредитного счета. Отрицательное значение о котором я писал есть БАЛАНС — выучите это слово — очень полезно

        2) Собственные средства — ВСЕ средства которые отражаются на моих счетах есть суть мои собственные средства. но отрицательный БАЛАНС моего КРЕДИТНОГО счета означает, что банк ДОЛЖЕН мне некую сумму денег, а положительный БАЛАНС означает, что я должен ему некую сумму денег. Все инсинуации относительно того, кому и в каком случае принадлежат деньги указанные на БАЛАНСЕ МОЕГО счета есть не более чем ваши инсинуации

        3) Вы помните, что у вас долг около 200, заходите и видите -190.33. Вы быстренько берете и закидываете заготовленные 200. потом, когда сумма станет -390.33, вы поймете, что это значит, до следующего раза — опять же ваши фантазии в чистом виде. оные фатназии проистекают из того, что вы искренне полагаете, что
        — все люди вроде вас не способны постичь смысл отрицательного БАЛАНСА
        — что вы с какого-то перепугу вдруг решили, что если вы переведете лишние деньги на ваш кредитный счет, то они пропали или по крайней мере доступ к ним заблогирован на некий период времени — уверяю вас, никто вам не может запретить перевести ваши средства на другой счет в любой момент, или, если это счет кредитной карты, просто использовать эти средства для оплаты ваших покупок


        1. Mendel
          18.09.2017 15:09

          все люди вроде вас не способны постичь смысл отрицательного БАЛАНСА

          Активно-пассивные счета это зло. Ну не прям совсем конечно, но обычно это или упрощение плана счетов и корреспонденции (ИМХО чрезмерное упрощение) или явно ошибочная корреспонденция.
          У топикстартера безусловно каша в терминологии и т.п., но если перевести его позицию на человеческий язык, то он выступает против активно-пассивных счетов, и предлагает отдельно отображать долг банка нам, а отдельно наш долг банку. Ну и соответственно если это предусмотрено договором (обычно подразумевается или явно озвучено) то автоматически делать их взаимозачет.
          Тогда «отрицательный баланс» как вы говорите может быть только в случае сторно и прочих специфических ситуаций.


        1. Germanets
          18.09.2017 15:46
          -1

          Ситуация номер три — это очень даже вкусный вариант для бизнеса, так как получает деньги заранее, пусть даже и запутывая клиента)


  1. Kan70
    18.09.2017 12:14

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


    Это непонимание бухгалтерского учета в принципе. Никакого отношения начисленные доходы не имеют к хоз.деятельности банка. Все расходы банка производятся не со счета доходов, а со счета расходов. И разница образуемая между ними и есть прибыль или убыток.
    Ну и далее — реально вывести можно только деньги, которые находятся на корсчетах банка или в кассе. Вы можете нарисовать хоть 1 млрд доходов, но для образования дыры вам доступны только средства на корсчете или в кассе. Упрощенная модель выглядит так — клиент вносит в кассу банка 1 млн во вклад, банк выдает другому клиенту 1 млн как кредит. Далее % по вкладу вы начисляете как расходы банка, % по кредиту — как доходы банка. Но есть разница между начисленными и реализованными расходами и доходами. Например % вы согласно правил начисляете клиенту по вкладу каждый месяц, а выплата % по договору раз в квартал. Т.е. можно спокойно показывать что у вас расходы нарастают, но реально никаких денег вам для выплат в кассе или на корсчете не нужно. Мало того — вы можете % выплачивать на счет клиента и тогда это просто «бумажное» нарастание обязательств, до тех пор пока клиент не захочет вывести деньги из банка. Вот тогда и выяснится что денег на корсчетах и в кассах может быть 0, а все деньги были выведены за счет выдачи кредитов, которые никто возвращать не собирается, или ценных бумаг, которые ничего не стоят и т.п.

    Далее — на что попадет банк или другая организация, которая начислит «лишние доходы». А попадет она на «лишние налоги».


    1. sergey-b Автор
      18.09.2017 12:30
      +2

      Представляете, если бы я вместо слов «хозяйственная деятельность», расписал бы как вы, что можно сделать со счетом доходов. Это был бы неоправданный оффтоп. А в комментариях эти подробности смотрятся очень даже гармонично.


    1. AlexPu
      18.09.2017 13:37
      -2

      >>Это непонимание бухгалтерского учета в принципе

      Да при чем тут бухгалтерский учет! Это непонимание… всего! В описанном случае речь в принципе не может идти о доходах банка! Ибо это вообще не доходы — это просто средства клиента которые переданы банку на хранение (намеренно или по ошибке — не важно). Вое если банк получит какую — то прибыль с оборота тех средств которые были переданы ему на хранения, то да — это может быть прибылью


      1. mayorovp
        18.09.2017 13:53
        +2

        Почему регулярный платеж за обслуживание карты вы называете "передачей денег банку на хранение"?


        1. AlexPu
          18.09.2017 16:03

          >>Почему регулярный платеж за обслуживание карты вы называете «передачей денег банку на хранение»?

          Почему вы приписываете мне высказывания, которые я никогда не делал?


          1. mayorovp
            18.09.2017 16:12
            +1

            Вы их сделали. 4 с половиной часа назад в комментарии, на который я отвечал. Если я его как-то неправильно понял, то поясните что именно было не так.


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


            Вы же начали рассказывать про какие-то "средства клиента которые переданы банку на хранение". Какие именно средства тут имелись в виду?


  1. OksikOneC
    18.09.2017 13:34

    Мне кажется один из самых интересных траблов при работе с суммовым показателем описан в этом этом комментарии. Кстати, подобное касается не только приведенных НДС, но и аналогично в любой ситуации, когда вам нужно фиксировать валютную сумму итого в национальной валюте регламентного учета. Часто бывает, что общий итог <> итог по колонке сумм. И тут возникает вопрос, как от этого избавиться? Мне кажется, это один из топовых по своей интересности вопросов. В свое время много было дров поломано на эту тему. ;)


    1. SergeyGershkovich
      18.09.2017 15:48

      … Часто бывает, что общий итог <> итог по колонке сумм… И тут возникает вопрос, как от этого избавиться?

      Не стоит технарю решать проблему экономиста/бухгалтера?

      Опишите прямой алгоритм расчета (когда сумма по ИТОГО не сходится с ПРОИЗВЕДЕНИЕМ) или обратный (когда сумма по ИТОГО с ПРОИЗВЕДЕНИЕМ сходится, но в последней позиции копеечка подправлена). Подпишите порядок расчета у Главного экономиста(бухгалтера). И не ломаете себе голову.

      … налоговая это нормально хавает.


      1. OksikOneC
        19.09.2017 17:19
        +1

        Я не знаю, чем руководствовался автор строчки «налоговая это нормально хавает», но обычный короткий гуглинг, показывает что ФНС идет в арбиртаж из-за погрешности подобного округления. (А56-17988/2011 от 19.01.2012г., А32-13441/2010 от 28.06.2012г., А40-69215/08-107-326 11.03.2009г.)

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

        но в последней позиции копеечка подправлена

        Представьте себе список, состоящий из несколько сот позиций, у которых веса (суммы) каждой позиции приблизительно одинаково небольшие. Если сделать так как вы предлагается, то у вас погрешность округления будет больше чем вес самой позиции. Но вы же сделали все по «подписанному», какая разница, да? :) Я именно про это, а вы все сразу редиректите на экономиста/бухгалтера. Типа, а это не техническая задача, мне это по барабану, это чисто бухгалтерская задача, мне как сказали, я так и накодил. Подписано? Подписано! А ко мне какие проблемы? Хотя отчасти вы, конечно же правы. Если заказчик подписал 2+2=5, то это 5 и никак иначе. Но мне кажется, свои 5 копеек в исходную постановку, если вы видите очевидный косяк, вставить можно. Я рассматриваю для себя такую позицию так: я вижу, что у вас есть некоторые проблемы в постановке, я вам их сообщаю, а делать вам что-то с этим или нет — дело ваше. После этого считаю, что моя совесть, чиста.


        1. SergeyGershkovich
          20.09.2017 16:05
          +1

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

          PS: Бухгалтерский учет часто не понять логически. Бывают законы, которые противоречат друг другу, или наоборот, не раскрывают важные моменты, как с округлениями. В таком случае судьба предприятия будет зависеть от позиции бухгалтера, его опыта, умения вести разговор с инспекцией. Поэтому как он скажет...(2+2=5 ;-)


          1. sergey-b Автор
            20.09.2017 16:17

            Полностью согласен. При этом задача IT — создать такую модель, в которой все хотелки заказчика будут работать. А сомнительные результаты будут достаточно подробно документированы.

            У меня в одной системе было требование 1.45 = 2. Сделал так, как просили, но чтобы округление так работало, надо было зайти в настройки и явно включить эту фичу, которая по умолчанию была отключена.


          1. OksikOneC
            21.09.2017 23:35

            Другой пример с положительной судебной практикой для налогоплательщика

            Так и все мои «гугли»-примеры, они тоже с положительным решением арбитража, который встал на сторону клиента, а не фнс. Я привел эти решения только на постфактум, что якобы «налоговая это нормально хавает». Налоговая, как раз таки и не хавает. Хавает пока арбитраж, куда фнс любит ходить по этим спорным моментам.


            1. SergeyGershkovich
              22.09.2017 08:14

              фнс любит ходить по этим спорным моментам

              Профессиональный бухгалтер тоже пойдет в арбитраж, если не согласен с фнс.
              А вот программисту желательно иметь основания не появляться там по этим спорным вопросам.


  1. VanKrock
    18.09.2017 14:09
    -1

    В C# вроде double теперь работает с точными значениями

            static void Main()
            {
                var a = 0.000001;
                var b = 0.000003 / 3;
                Console.WriteLine(a == b); //True
            }
    


    1. sergey-b Автор
      18.09.2017 14:10

      double a = 1111111.100001d;
      double b = 333333330.0003d / 300d;
      Console.WriteLine(a == b); // False
      


      1. VanKrock
        18.09.2017 14:22

        Теперь это стало совсем забавно работать

                    double a = 1111111.100001d;
                    double b = 333333330.0003d / 300d;
                    Console.WriteLine(a == b); // False
                    Console.WriteLine(a - b);
                    Console.WriteLine(a);
                    Console.WriteLine(b);


    1. alexeykuzmin0
      18.09.2017 18:07
      +1

      А почему бы ему случайно не совпасть после округления? Так будет не всегда.


  1. johnnymmc
    18.09.2017 14:15

    Справедливости ради следлвало бы отметить зачем тогда вообще нужен тип double когда есть decimal: математические операции производятся над decimal очень намного медленнее (сам точно разницу не замерял, только на глазок, но давеча видел где-то коммент, что примерно в 100 раз). Аппаратная поддержка десятичных типов вроде есть только в процессорах IBM начиная с POWER6.


    1. sergey-b Автор
      18.09.2017 14:33

      Ну так в компьютерных играх этот тип незаменим. Он просто для финансовых операций не подходит.


    1. Ivan22
      18.09.2017 14:36

      справедливости ради следует добавить, что в тех местах где обычно и проводится основная масса финансовых расчетов (а именно в СУБД) накладные расходы CPU на обработку decimal вместо float настолько ничтожны по сравнению с прочими типа I/O, sorting, hash group и т.д. что при них ни один DBA на свете не чешется.


      1. sergey-b Автор
        18.09.2017 14:40

        Точно. А в оракле double вообще только в десятке появился.


  1. mickvav
    18.09.2017 14:33

    Классно было бы уметь отслеживать размерности во время компиляции. Получает, например, функция два параметра со совими размерностями и компилятор проверяет, что все арифметические операции в функции так устроены, что результат имеет осмысленную размерность… Наверное это можно сделать в рамках шаблонов C++. Кто-нибудь пробовал?


    1. propell-ant
      18.09.2017 15:31
      +1

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


    1. alexeykuzmin0
      18.09.2017 18:09

      Есть boost::units.


  1. crocodile2u
    18.09.2017 14:43

    В классе ExchangeRate лучше оперировать понятиями из мира трейдинга: fixedSum & floatingSum. fixedSum (фиксированная сумма) — сумма, которая известна заранее. floatingSum (плавающая сумма) — сумма, которую мы вычисляем.


    Поясню, почему clientGives/Takes — не совсем точно с точки зрения доменной области.


    Например, клиент продает USD за EUR, в скобках — свойства класса ExchangeRate:


    fixedSum = X USD (clientGives — верно, потому что у клиента есть фикс. сумма USD, которые он продает)
    floatingSum = Y EUR (clientTakes — тоже верно, мы считаем, сколько EUR клиенту отдать согласно курсу)


    То же самое будет, если клиент покупает USD за EUR, но описание ситуации в нашем классе уже не соответствует действительности, ведь на самом клиент отдает EUR, а забирает USD:


    fixedSum = X USD (clientGives — это неправильно, потому что это фикс. сумма USD, которую клиент забирает)
    floatingSum = Y EUR (clientTakes — и это тоже, потому что это сумма EUR, которую мы считаем, а клиент отдает)


    В то же время fixed/floatingSum — это хорошие имена свойств, которые в обоих случаях релевантны ситуации.


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


    1. sergey-b Автор
      18.09.2017 15:05
      +1

      Так ведь нельзя клиенту по этому же курсу продать USD за EUR. Это будет убыток банку. В этом-то то и фишка моей технологии. Нужно создать другой курс с параметрами clientGives: EUR, clientTakes: USD. И там цифры будут другие. Хотя, если вы готовы по тому же курсу в обратную сторону торговать, можно просто переставить значения местами, но это уже будет не бизнес, а благотворительность.

      Курс обмена — односторонний, именно поэтому он является вектором.


      1. crocodile2u
        18.09.2017 16:59

        Я понимаю, что будут 2 курса. Фишка в том gives/takes не всегда соответствуют реальности. Прочитав код, я буду думать, что этот курс только для случая продажи клиентом Н-ой суммы.


        1. mayorovp
          18.09.2017 17:07

          fixedSum = X USD (clientGives — это неправильно, потому что это фикс. сумма USD, которую клиент забирает)
          floatingSum = Y EUR (clientTakes — и это тоже, потому что это сумма EUR, которую мы считаем, а клиент отдает)

          Зачем вы сначала неправильно расставили имена параметров, а потом говорите что это не соответствует реальности?


          Чем вас не устраивает вариант clientGives: EUR, clientTakes: USD?


          1. crocodile2u
            18.09.2017 17:44

            Смотрите. Я хочу купить 1000 USD, у меня есть деньги в EUR. По вашей логике, я должен создать объект ExchangeRate вот так:


            new ExchangeRate(1 EUR, 1.2 USD)


            Как мне теперь посчитать, сколько EUR мне нужно для сделки? Метод exchange() выдаст мне сумму в USD, а предоставить ему я должен сумму в EUR!


            Получается, чтобы осуществить задуманное, мне надо писать примерно так:


            rate = new ExchangeRate(1.2 USD, 1 EUR)
            rate.exchange(1000 USD)


            При этом я получу то, что нужно, но при создании объекта я передаю доллары в поле clientGives, тогда как на самом деле это clientTakes ((


            1. mayorovp
              18.09.2017 19:04
              +1

              Нет, не надо писать так как вы написали! В данном случае, надо добавить метод обратного преобразования в класс, при сохранении семантики clientGives/clientTakes.


            1. sergey-b Автор
              18.09.2017 19:41

              Конечно, объект ExchangeRate может содержать много дополнительных методов, в том числе и расчет суммы в валюте списания от фиксированной суммы валюты зачисления, но смысл его полей при этом не поменяется. Будет как-то так:

              public Sum howManyClientShouldGive(Sum clientWants) {
                  if (!clientWants.getCurrency().equals(clientTakes.getCurrency())) {
                      throw new IllegalArgumentException();
                  }
                  BigDecimal amount = clinetWants.getAmount().multiply(clientGives.getAmount())
                          .divide(clinetTakes.getAmount(), PRECISION, BigDecimal.ROUND_HALF_UP);
                  return new Sum(amount, clientGives.getCurrency());
              }
              


              1. crocodile2u
                18.09.2017 21:10

                Это субъективно, конечно, но мне больше нравится решение с одним методом exchange().


                1. sergey-b Автор
                  18.09.2017 21:13

                  Это ж творчество. Тут каждый художник пишет в своем неповторимом стиле.


  1. relia
    18.09.2017 15:54
    -2

    1. Суммы округлять через round() перед каждой операцией — далеко не самый плохой приницп работы с ними.
    1.1. Как вариант: хранить суммы в полях int в значениях наименьшой возможной единицы (копейки, центы) и делить их на степень кратности к денежной единице только перед выводом на экран/печать. Кстати, такие реализации встречал неоднократно.

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

    3. Покупка/продажа валюты, акций и т.д. во всем мире идет как bid/ask. Оператор рынка (банк в данном случае) объявялет обменные курсы, по которым покупает и продает. Если юзер не понимает таких элементарных вещей, то лучше ему не заниматься такими операциями. Какой велосипед изобретаете?


    1. Portnov
      18.09.2017 21:43
      +2

      1. Применимость очень ограничена. Обычно, как было указано в комментариях выше, единственный правильный порядок округления — тот, который указан заказчиком. Если заказчик не указал, имеет смысл явно спросить, иначе рискуете где-нибудь под новый год получить блокер вида «у вас тут на целую копейку расхождение!!!111».

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

      3. Это терминология, характерная для бирж (и, возможно, ещё где-то). В банках говорят buy/sell, при этом подразумевая покупку/продажу иностранной валюты банком за национальную валюту.


      1. relia
        18.09.2017 22:00
        +1

        1. Мы будем искать и находить много новых вариантов. Последнее слово тут не скажет никто. :) Всего лишь поделился своими наблюдениями за имеющимися реализациями.
        У меня есть пачка «обезьянок», которым базовую математику исчисления НДС привить не могу, но они очень сильно любят «красивые цифирьки» и крайне недовольны, когда не могут занести в систему подготовленный в Excel прайс-лист, красивость которого обеспечена использованием до 8 знаков после запятой. :(

        2. Все зависит от конкретного законодательного поля данной страны. Корректирующие проводки налогового учета, например.
        Я же обратил внимание на то, что автор «запрещает» показывать отрицательные цифры/итоги. Но что делать с отрицательными балансом или сальдо — он не сказал. Когда смотришь оборотно-сальдовую ведомость по клиенту, то проще все понять по знаку, чем оперировать дебитом/кредитом. А тем более когда просматриваешь пачку клиентов одной таблицей. Ну за исключением идеального случая, когда сразу ноль, конечно :)

        3. buy/sell, bid/ask — суть одного поля термины, где традиции среды, в которой они используются, намного важнее программистского идеального мира.


      1. Mendel
        19.09.2017 11:25

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

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


        1. Portnov
          20.09.2017 17:39
          +1

          Ну вот я эту попоболь и имею ввиду, говоря что они не знают отрицательных чисел :) Можно ещё вспомнить какого-то беднягу, утопленного пифагорейцами за открытие иррациональных чисел.


          1. Mendel
            20.09.2017 18:01

            Вопрос в том эмоции и привычка это, или и вправду «не стоит». Присоединяйтесь к обсуждению сторно чуть ниже.


    1. netch80
      19.09.2017 12:37
      +1

      > Суммы округлять через round() перед каждой операцией — далеко не самый плохой приницп работы с ними.

      Вопрос: а зачем? Вычисления в целых числах заведомо не имеют подобной проблемы и обходятся обычно дешевле (сумма целых даже в виде Decimal из C# проще, чем два round плюс суммирование и масштабирование).

      > Как вариант: хранить суммы в полях int в значениях наименьшой возможной единицы (копейки, центы) и делить их на степень кратности к денежной единице только перед выводом на экран/печать. Кстати, такие реализации встречал неоднократно.

      Именно. Фактически, для целей финансов это самый нормальный режим. (С поправкой: не только печать, но и всякая конверсия; иногда и в какой-нибудь float надо переводить — например, проценты взять.)

      > Покупка/продажа валюты, акций и т.д. во всем мире идет как bid/ask. Оператор рынка (банк в данном случае) объявялет обменные курсы, по которым покупает и продает. Если юзер не понимает таких элементарных вещей, то лучше ему не заниматься такими операциями.

      Практически любой человек, который ездит за границу, вынужден думать о покупке/продаже валюты. Предлагаете вообще не ездить?


      1. relia
        19.09.2017 13:10
        -1

        Практически любой человек, который ездит за границу, вынужден думать о покупке/продаже валюты. Предлагаете вообще не ездить?

        Мне тоже очень нравится творчество А. Макаревича, но применять буквально к реальной жизни его «Не стоит прогибаться под изменчивый мир» как-то не додумался :) Думаете стоит попробовать?


  1. TimsTims
    18.09.2017 16:32

    Если вы их перепутаете, это будет в 10 разу хуже, чем сложить метры с миллиметрами, потому что 1 BYN = 10 000 BYR.

    Не в 10, а ровно в 10 тысяч раз хуже :)


    1. Germanets
      18.09.2017 16:49

      Складывать метры с миллиметрами — ошибка в 1000 раз, складывать BYR и BYN — ошибка в 10000 раз, что в 10 раз «хуже», видимо именно это и имел ввиду автор.


  1. vlivyur
    18.09.2017 17:05

    Метры с миллиметрами складывать не сложно. Сам так много раз делал. Куда сложнее складывать граммы с миллилитрами. Примерно так же сложно, как BYN с BYR или BYR с RUR.


    1. relia
      18.09.2017 17:40
      +1

      Куда сложнее складывать граммы с миллилитрами.

      Когда ГСМ или лаки-краски закупаются по весу, а реализуются по объему? В первом случае свои корректировки вносит температура товара, во втором точность отмеривания (40 кг бочку отпускают большим количеством порций с точностью до 10 мл). Обе эти задачи без регулярных инвентаризаций по нормативам решения практически не имеют.


  1. IGHOR
    18.09.2017 19:56

    Есть еще long double, который можно использовать для промежуточных вычислений.


  1. BD9
    19.09.2017 12:36
    +2

    Красное сторно — под запретом?
    Тип «Currency» отменили?


  1. netch80
    19.09.2017 18:26
    +3

    > Не используйте double

    Первый пункт я бы формулировал немного иначе, хотя по сути близко.

    Идти в бухгалтерских расчётах (речь о них — не о финансовых в целом, там может быть и «высокая» математика) надо от некоторых базовых правил. В первую очередь это то, что деньги не могут появляться ниоткуда и исчезать в никуда, любая сумма и разность должны быть точными. Как бы ни были сформулированы правила для округлений в каком-то случае (НДС, проценты, другое), это особые ситуации. Лучше получить исключение переполнения, если точности не хватает, чем потерять доли (копейки) и потом искать источник несведения баланса.

    Основной проблемный случай — деление на несколько частей. Например, деля 100.00 поровну на 3, мы должны получить не 33.33+33.33+33.33, а 33.33+33.33+33.34. Кому будет лишняя копейка — вопрос полиси, но она не должна потеряться неизвестно где. Аналогично, деля даже на 2: 2.33 пополам должно дать 1.16+1.17, а не 1.16+1.16, как получилось бы, если бы округляли оба результата деления по умолчанию. Проценты? Пожалуйста — 6.99 при выделении шестой части должно дать 1.16+5.83 или 1.17+5.82, но не 1.16+5.82.

    Далее. Чем плоха плавучка? И двоичная, и десятичная тут плохи. Для двоичной — банальный пример: в IEEE double, 0.1*3 — 0.3 равно 5.55...e-17. Часто достаточно того, что это не 0: чтобы сравнить с нулём, надо вначале округлить до 0.01 (или какая там выбрана точность). Вообще, все расчёты, получается, надо вести так, что вместо a+b мы делаем round(round(a,0.01), round(b,0.01), 0.01) (вторым аргументом я показал единицу точности). Но так как round(a,b) обычно выполняется как b*round_int(a/b), то резонный вопрос — нафига вообще держать числа в дробном виде, если на каждую операцию они переводятся в целые, а потом обратно?

    Есть десятичная плавучка — даже в IEEE754 (версии 2008 года), IBM её умеет аппаратно, многие остальные — программно (например, GCC даёт для C). Но и с ней проблемы. Decimal32 из IEEE это 7 точных десятичных цифр. Сложив 9999999 и 4, мы получим 10000000, а не 10000003, точности для последней цифры не хватит. И потеря будет молчаливой, за исключением (обычно замаскированного) inexact exception. С такими данными можно работать, но только при условии, что inexact exception всегда включен или регулярно проверяется, кроме отдельных операций, где он вреден (вдруг кому-то таки нужен квадратный корень из суммы). Более того, после операций за пределами обычного сложения/вычитания надо округлять в нужные пределы. Если 19.5% от 123.45, пусть мы даже подсчитаем проценты в float (любом), результат должен быть выдан уже как 23.46, а не 23.4555, и только этот результат имеет право быть назван суммой в выбранной валюте.

    Аналогично проблемен и Decimal из C#, и даже больше: если каким-то образом вылезли за его 28 цифр, то там даже inexact exception не будет: он не умеет жаловаться на такое. Поэтому вернуться к сумме после всех вычислений надо через Round(), не доверяя любым предыдущим результатам. Но там сложно найти случай переполнения этих 28 цифр :) и это спасает на практике. Но, боюсь, не все сертификаторы положительно оценят подобный «авось» даже тогда, когда суммы в септиллионы букозоидов не бывают в принципе: контроля переполнения и потери точности нет => не годится.

    Поэтому основной вариант, о котором надо думать — это масштабированные целые. Где конкретно кому — целые копейки, 1/100 цента, как-то ещё — это уже по местным требованиям.

    Далее, это было только про сложение/вычитание чисел одной размерности. Но есть и другие случаи. А именно:
    * одна валюта, но разные размерности (где-то копейки, где-то сотые доли копейки)
    * разные валюты и деноминации (автор поста достаточно изложил, не повторяю)
    * иные операции, чем сложение и вычитание однородных денег

    и вот тут действительно очень на пользу иметь обёртку, которая не позволяет просто так сложить, например, деньги США в сотых долях цента с деньгами Беларуси в копейках; и конверсия каждый раз должна быть явной (с возможным указанием метода округления); никаких автоматических сложений, например, копеек с сотыми долями копейки, даже если машина знает, что валюта одинаковая.

    Операции умножения на целое ещё могут проходить в том же логическом «поле» сумм, деление — уже нет (см. примеры выше), аналогично взятие процентов. Более сложная арифметика и алгебра изначально должны выполняться в пределах подходов с плавающей точкой, хотя, возможно, с расширенной точностью (single, он же float в C, очень часто недостаточен; double уже обычно годится). Возврат в финансы — только через приведение к соответствующему типу с указанием выходной точности и контролем особых входных данных типа INF или NaN.

    К остальному есть немного комментариев.

    > Сумма не может быть отрицательной

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

    Показ переплаты как долга с минусом — да, против не готовых к такому может быть диверсионным. Не зря в некоторых традициях вместо минуса используют другую нотацию — например, [в квадратных скобках]. Хотя сейчас эти скобки, наоборот, собьют технически подготовленных.
    Вот противоположный стиль — переплата с плюсом, а долг с минусом — меньше сбивает, насколько я видел; особенно если потом таки выписать «к оплате столько-то» и писать ноль в случае переплаты.

    > Поэтому лучше всего использовать нейтральные понятия «сделка», «обмен», «дебет», «кредит», «зачисление», «списание» и т. д.

    Это ещё менее понятно, just IMHO. Подозреваю, хоть как-то универсально понятным будут варианты типа «сторона A получает...», «сторона B получает...»


    1. sergey-b Автор
      19.09.2017 18:44

      Если вызвать round(0.1d) на выходе будет число, в котором все равно останется мусор после 15-го знака.

      В C# я проверял — выскакивает ArithmeticOverflowException. А вот Java проглатывает.


      1. netch80
        19.09.2017 18:47

        > Если вызвать round(0.1d) на выходе будет число, в котором все равно останется мусор после 15-го знака.

        Можно полный пример?

        > В C# я проверял — выскакивает ArithmeticOverflowException.

        Аналогично. О какой операции речь?


        1. sergey-b Автор
          19.09.2017 19:58

          round(0.1) продемонстрировать не получилось. Я думаю, это какой-то крайний случай, когда получается точное число. Зато на 0.33 все предсказуемо

          C#

          double d = Math.Round(0.33d, 2);
          d *= 10d;
          d -= 3d;
          d *= 10d;
          d -= 3d;
          Console.WriteLine(d); // 2.66453525910038E-15
          


          Исключение при переполнении целой части не выбрасывается, вы правы. Я спутал с decimal.


          1. netch80
            19.09.2017 21:24

            Да, ошибка тут накапливается, причём в C# ещё и как-то очень странно работает вывод значения. Если сделать так:

            double fd1 = 0.33d;
            double fd2 = Math.Round(0.33d, 2);
            Console.WriteLine("{0:F30} {1:E30}", fd1, fd1);
            Console.WriteLine("{0:F30} {1:E30}", fd2, fd2);
            fd2 *= 10d;
            Console.WriteLine("{0:F30} {1:E30}", fd2, fd2);
            fd2 -= 3d;
            Console.WriteLine("{0:F30} {1:E30}", fd2, fd2);
            fd2 *= 10d;
            Console.WriteLine("{0:F30} {1:E30}", fd2, fd2);
            fd2 -= 3d;
            Console.WriteLine("{0:F30} {1:E30}", fd2, fd2);


            то видно, что Math.Round ничего не изменил, а вот дальше возникает отклонение (но рантайм врёт в печати — проверено на Mono 4.6.2 и .NET Core 1.0.0-preview2-003118, результаты идентичны):

            0.330000000000000000000000000000 3.300000000000000200000000000000E-001
            0.330000000000000000000000000000 3.300000000000000200000000000000E-001
            3.300000000000000000000000000000 3.300000000000000300000000000000E+000
            0.300000000000000000000000000000 3.000000000000002700000000000000E-001
            3.000000000000000000000000000000 3.000000000000002700000000000000E+000
            0.000000000000002664535259100380 2.664535259100375700000000000000E-015
            


            Для сравнения то же на Python3 (идеально честный вывод: он для любой заданной точности подбирает десятичную строку такую, что обратно превращается в такую же двоичную) —
            0.330000000000000015543122344752 3.300000000000000155431223447522e-01
            3.300000000000000266453525910038 3.300000000000000266453525910038e+00
            0.300000000000000266453525910038 3.000000000000002664535259100376e-01
            3.000000000000002664535259100376 3.000000000000002664535259100376e+00
            0.000000000000002664535259100376 2.664535259100375697016716003418e-15
            


            он не зарезает «хвосты» там, где начинается дребезг, и видно, как именно он был заложен изначально.

            > round(0.1) продемонстрировать не получилось. Я думаю, это какой-то крайний случай, когда получается точное число.

            Да, и таких достаточно много. А вот на знаменитом из всяких stackoverflow примере уже лезет дребезг: тот же Python3:

            >>> d = 0.3
            >>> d*10-3
            0.0
            >>> d = 0.1*3
            >>> d*10-3
            4.440892098500626e-16
            


            > Исключение при переполнении целой части не выбрасывается, вы правы. Я спутал с decimal.

            Ну да, переполнений нет, но в варианте типа

            decimal d2 = 1.0e28m;
            decimal d3 = 1.0e-28m;
            Console.WriteLine("{0:F40}", d2 + d3);


            мы получаем

            10000000000000000000000000000.0000000000000000000000000000000000000000
            

            следы второго слагаемого тупо потерялись :( почему я и говорю — inexact не ловится.


            1. sergey-b Автор
              19.09.2017 21:32

              Понятно. Вы имели в виду 28 знак справа от точки, а я слева.

              На stackoverflow пишут, что формат {:F} в C# всегда округляет до какой-то захардкоженной позиции. Именно поэтому я в примерах умножал на 10^15, чтобы нужные знаки получить. Я просто не нашел формат, в котором нет неявного округления.


    1. sergey-b Автор
      19.09.2017 20:21

      Что касается покупки-продажи. Речь идет чисто о внутренней реализации. На табло можно надписать что угодно, все равно это никто не читает. А внутри, в коде, вместо терминов покупка-продажа должны быть точные нейтральные термины, которые тяжело спутать.

      У меня был реальный случай, когда одна половина команды считала, что операция обозначает покупку иностранной валюты у клиента за рубли, а другая половина команды была уверена, что это покупка валюты у компании. Чтобы разобраться подняли все документы. Везде было слово «покупка», но нигде не уточнялось, кто у кого покупает. Причем, в некоторых согласованных всеми участниками документах были разночтения в проводках. Т. е. даже по проводкам однозначно нельзя было смысл восстановить.

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


      1. Mendel
        20.09.2017 08:55

        Это вопрос первой фундаментальной проблемы программирования а не финансов :)
        Если есть выделенная сторона сделки то можно писать что-то вроде CompanyBuyCurrencyRate а если выделенной стороны нет (биржа), то там курс один, плюс спред или комиссия или еще что-то, но курс у сделки один.


        1. alexeykuzmin0
          20.09.2017 12:37

          На бирже как раз обычно есть Ask и Bid, а вовсе не «средний» курс и спред или комиссия.


          1. mayorovp
            20.09.2017 14:11
            +1

            Ask и Bid там есть в статистике. А у каждой конкретной сделки цена вполне определенная.


            1. Mendel
              20.09.2017 14:25

              Не в статистике а в «стакане». </зануда


              1. Ivan22
                21.09.2017 12:25
                +1

                и в статистике по истории сделок.


                1. Mendel
                  21.09.2017 14:10

                  В статистике оно отображается с точки зрения торговца. Обезличено, сточки зрения биржи цена колеблется от времени, а не от направления ибо нет у сделок направления.


                  1. alexeykuzmin0
                    21.09.2017 14:24
                    +1

                    Ну это смотря что понимать под статистикой. Весь стакан на каждый момент времени — тоже статистика, и Ask и Bid в нем есть


                    1. mayorovp
                      21.09.2017 15:40

                      Кажется, вы ходите по кругу.


    1. sergey-b Автор
      19.09.2017 21:12

      Ага, вспомнил, что я хотел сказать. Я хотел возразить вот на это

      Аналогично проблемен и Decimal из C#, и даже больше: если каким-то образом вылезли за его 28 цифр, то там даже inexact exception не будет: он не умеет жаловаться на такое.

      Decimal выкидывает Exception.

      decimal d0 = 79228162514264337593543950335m;
      decimal d1 = d0 + 1; // OverflowException
      


      1. netch80
        19.09.2017 21:31

        > Decimal выкидывает Exception.

        Я не о переполнении порядка, а о превышении точности представления (и Вы пропустили слово inexact — это не overflow):

        decimal d2 = 1.0e28m;
        decimal d3 = 0.1m;
        decimal d4 = d2 + d3;
        Console.WriteLine("{0:F40}", d4);
        Console.WriteLine("{0:F40}", d4 - d2);


        получаем:

        10000000000000000000000000000.0000000000000000000000000000000000000000
        0.0000000000000000000000000000000000000000
        


        d3 тупо ушло в никуда.

        Эффект сам по себе банальный и общеизвестный, в инструкциях по работе с плавучкой он описывается одним из первых. Речь не о нём в принципе, а о том, что (1) в бухгалтерии он недопустим, и (2) защита в .NET Decimal от него есть только в виде «а вы не допускайте таких больших чисел».


        1. sergey-b Автор
          19.09.2017 21:44

          Да уж. Я поражен. Оказывается, если к 79228162514264337593543950335m прибавить 0.1, то слагаемое потеряется. А если к этому же числу прибавить 1, то будет OverflowException. Плавающая точка — зло.


          1. netch80
            19.09.2017 22:11
            +2

            Ну почему же зло? Вполне добро, просто не для бухгалтерии. :)
            Для матфизики, наоборот, фиксированная десятичная точка будет бессмысленной.

            Вот то, что в C, C++, C# плавучка обязательна, а фиксированная точка — нет, уже показывает ориентацию языков. А в результате имеем сохранение старого кода на всяких коболах ;(


            1. sergey-b Автор
              19.09.2017 22:32

              Конечно, она в определенных областях незаменима. Но вот тип decimal в матфизике не нужен. Он предназначен именно для точного представления чисел, которые ведутся в десятичной системе счисления. Зачем в C# его плавающим сделали, для меня загадка. И вот этот эффект, когда в сходных случаях наблюдается разное поведение, меня удивляет. В double такого эффекта нет. Там, что 0.1 прибавить, что 100 — если точности не хватило, то число просто увеличено не будет.


              1. netch80
                19.09.2017 23:39
                +1

                > В double такого эффекта нет. Там, что 0.1 прибавить, что 100 — если точности не хватило, то число просто увеличено не будет.

                Хм, не согласен. Поведение decimal в этом смысле ничем не отличается от поведения плавучки IEEE (тут и далее — для дефолтного режима округления, roundToNearestTiesToEven).

                Возьмём для примера single, чтобы не было слишком больших степеней. Множество представимых в нём чисел это «сетка» с шагом, который меняется в 2 раза при переходе через степень двойки. Например, самое большое представимое число это 2^128-2^104; на одну позицию ниже 2^128-2*2^104, затем 2^128-3*2^104, и так далее по убыванию до 2^127; от 2^127 вниз до 2^126 шаг 2^103; и так далее. Следующая возможная точка в сторону увеличения была бы 2^128, но она уже непредставима. Результат любой операции округляется в сторону ближайшей представимой точки, а если точно посредине — выбирается та, которая более чётная (voting digit — минимальная цифра из представимых в округлённом результате — равна 0).

                Так вот — если Вы к этому MAX_SINGLE = 2^128-2^104 ~= 3.40282347E+38 прибавите любое положительное число меньше чем 2^104, округлённая сумма будет снова равна MAX_SINGLE, потому что точная сумма меньше среднего между MAX_SINGLE и 2^128; но если прибавите любое число не меньшее чем 2^104, точная сумма будет уже не меньше этого среднего, поэтому сработает округление к ближайшему чётному, оно будет равно 2^128, не представимо в single, и результатом будет возбуждение исключения переполнения (а если исключение заглушено, будет выдан результат +INF). (В стандарте это правило сказано другими словами, но мне описание через следующую возможную точку кажется более естественным.)

                Та же логика, с поправкой на размеры мантиссы и диапазоны порядка Decimal, применяется и к нему. «The finite set of values of type Decimal are of the form m / 10^e, where m is an integer such that -2^96 < m < 2^96, and e is an integer between 0 and 28 inclusive.» Шаг между значениями около Decimal.MAX_VALUE равен 1 (e == 0), и первое непредставимое — следующее за MAX_VALUE — равно MAX_VALUE+1 == 2^96. Прибавка к MAX_VALUE любого положительного меньше 0.5 игнорируется, а >=0.5 — даёт округление к 2^96, которое уже не влазит => генерируется исключение переполнения. Тесты это подтверждают.


                1. sergey-b Автор
                  20.09.2017 00:32

                  Вот теперь мне все понятно. Спасибо.


                1. netch80
                  20.09.2017 07:07

                  > Так вот — если Вы к этому MAX_SINGLE = 2^128-2^104 ~= 3.40282347E+38 прибавите любое положительное число меньше чем 2^104

                  Поправка — 2^103, ибо как раз половина интервала до следующего; то же самое дальше по абзацу.


  1. kretsu
    19.09.2017 20:28

    Может это повтор, но говоря о валютах, не следует забывать, что у некоторых валют, наименьшая единица не есть одна сотая(цент, копейка).
    Что касаемо конверсии, то если говорить о банках, то там будут для некоторых операций использованыофициальные курсы ЦБ, а для нкоторых курсы самого банка. Это тянет за собой доп проводки с участием счетов доходов/расходов и тд.


  1. Mendel
    20.09.2017 09:28

    Кстати коллеги, раз уж столько копий сломали тут об отрицательные числа, то вот такой вот вопрос:
    В нерегламентированном учете «для блондинок» (я сейчас начал новый экстрим-проект, хочу автоматизировать управленческий учет микро-бизнесов вроде киосков/батискафов, так что ради простоты готов на большие жертвы, ибо иначе люди просто не станут с этим работать), стоит ли использовать сторно не для исправления ошибок а для текущих реальных операций? Хочется иметь минимальный план счетов. До безобразия минимальный, ибо дюжина счетов это для неподготовленного человека уже адЪ. Ну и вот простейший кейс — киоск работает с кофе-машиной взятой в аренду. Была взята машина с расходниками, аренда ноль, за расходники заплатили. Машина на забалансе по залоговой стоимости (рядом я рисую забалансовый же пассивный счет «залоговая стоимость арендного имущества» с той же суммой, так что забаланс у меня живет по правилам баланса, но не суть), расходники приходуем на материалы, точнее «расходники кофе-машины», чтобы не пугать пользователя сложными названиями. Всё ок, работа кипит, материалы потихоньку списыватся согласно смет, и тут прилетает новый план он поставщика машинок. По новому плану не договорились и делаем возврат. Поскольку изменение условий по инициативе поставщика, то он забирает остатки расходников и возвращает за них денег пропорционально расходу.
    Эту операцию можно отразить по разному.
    Можно сделать «продажу» материалов поставщику. Вроде норм, но тогда у нас появляется «мусор» по оборотам. Ломается статистика себестоимости, рентабельности, да и просто продажа это доход, а на доход считаем проценты продавцу или манагеру… В общем некрасиво. Остальные варианты довольно монструозные. Больше всего мне нравится вариант — сторнировать эту частичную сумму и частичный возврат (те же проводки что при покупке, с теми же номерами счетов только сумма минусом). Плюс такого подхода в том, что обороты по счетам не будут содержать лишних сумм, при этом по всем счетам на любую дату будут правильные сальдо и т.п. Минусы: на некоторых периодах некоторые обороты будут красным, что не очень понятно, хоть и правильно (да, в этот день поступление расходников отрицательное, и оперативные расходы отрицательные, но это ведь так и есть), ну и у меня есть какое-то внутреннее сопротивление говорящее о том, что сторно только для ошибок.
    А вы что думаете?


    1. relia
      20.09.2017 09:42
      +1

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

      Можно сделать «продажу» материалов поставщику.

      Ну разве что для поржать :)


      1. Mendel
        20.09.2017 14:51

        Ну на вырост будет ядро. А продукт будет именно что нишевый. Да и на вырост ИМХО главное первичку правильно вести, чтобы потом можно было другую логику сделать по старой первичке. Но вот на счет сторно да. С одной стороны по логике так правильнее. С другой стороны у меня тоже стойкое предубеждение против сторно в живой работе.


        1. relia
          20.09.2017 15:09
          +1

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

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


          1. Mendel
            20.09.2017 17:37

            Не-не-не, я не о машинке в данном случае а о расходниках (кофе, молоко, стаканчики и т.п.). Машинка взятая в аренду а потом возвращенная это понятно, тут лучше не трогать, но она и так на забалансе идет.
            А с кофе/молоком выходит как раз близко к «по ошибке провели». Поставщик дал на условиях покупки, т.е. мы у него купили, не взяли под реализацию и т.п., а именно купили, а потом (спустя пару недель, так что да, другой учетный период, в разрабатываемой системе учета учетный период неделя планируется) вдруг решил разорвать договор. Но поскольку расходники в значительной мере привязаны к конкретному поставщику и конкретной машинке, а разрыв по его вине то он забрал остатки расходников (кофе/молоко/шоколад), посчитав пропорционально расходу остаток и вернув деньги. Фактически тут мы купили, а потом через две недели оказалось что «ой, нет, не купили, отдавай мои какашки, забирай свои бумажки». Тут по смыслу явное сторно, т.е. отмена операции, только на другом уровне абстракции, не ошибка учета а «тот день когда я с тобой познакомился был ошибкой».
            В большом, «взрослом» учете когда у меня бывали такие или похожие случаи, то я всегда делал тупо продажу поставщику обратно. Что достаточно близко к тому что требует налоговый кодекс (один фиг налоговую накладную писать), но не нужно заморачиваться со всякими рекламациями, письмами, доп.соглашениями и прочим. Я купил, я продал. Продал в ноль «потому что могу». (главное правильно объемы указать, чтобы не сделать в минус, тогда нужно будет доначислять себе НДС). Теоретически по бухучету это надо было бы проводить по другим счетам с другой корреспонденцией, но итог все равно был бы тот-же, и сальдо бы не поменялось.
            В учете для блондинок это «сложно-сложно-блин». Вот честно, я очень хорошо помню какая аудитория ходила ко мне когда я в банке аналитиком работал. Да что далеко ходить, тут у нас прямо на хабре куча людей не готова воспринять двойную запись, а тут куча малопонятных счетов с «более правильной» корреспонденцией. Нафиг, нафиг.


            1. relia
              20.09.2017 19:18

              А с кофе/молоком выходит как раз близко к «по ошибке провели». Поставщик дал на условиях покупки, т.е. мы у него купили, не взяли под реализацию и т.п., а именно купили, а потом (спустя пару недель, так что да, другой учетный период, в разрабатываемой системе учета учетный период неделя планируется) вдруг решил разорвать договор. Но поскольку расходники в значительной мере привязаны к конкретному поставщику и конкретной машинке, а разрыв по его вине то он забрал остатки расходников (кофе/молоко/шоколад), посчитав пропорционально расходу остаток и вернув деньги. Фактически тут мы купили, а потом через две недели оказалось что «ой, нет, не купили, отдавай мои какашки, забирай свои бумажки». Тут по смыслу явное сторно, т.е. отмена операции, только на другом уровне абстракции, не ошибка учета а «тот день когда я с тобой познакомился был ошибкой».

              Тут нет никакой ошибки — банальный [частичный] возврат товара поставщику. И который должен идти в оборот между вами как контрагентами (в РФ так?).
              Так же при подбитии управленческих итогов необходимо понимать, что «тут» продажа готового продукта (стаканчик кофе) и есть такая-то прибыль, а «там» возврат расходников поставщику и прибыли тут нет. А на следующем уровне анализа при подготовке среза «там» мы должны понимать с каким поставщиком и когда и по какой причине происходили возвраты — переоценка под акцию поставщика, возврат некондиции, выбрыки поставщика (описанный Вами случай) и т.д. Ведь как иначе понимать с кем стоит работать. а с кем нет?
              Так же при наличии налога с продаж такое проведение возврата приведет к необходимости уплаты этого самого налога. Кому это нужно?

              Для «учета для блондинок» все это можно спрятать под оберткой с каким-то подобием учета на регистрах, но внутри все должно быть ок.

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

              Не теоретически, а практически так надо делать, иначе «шоколадка» фининспектору будет расти в геометрической прогрессии. Сальдо — это просто показатель текущего среза. Показатели оборота очень важны, а Вы хотите это отрезать.

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

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

              PS
              Внимательно посмотрите на Gnucash — или придете к пониманию, что изобретаете велосипед, или сможете почерпнуть оттуда много полезного. В случае «велосипеда» может будет иметь смысл сосредоточиться на изготовлении обертки к Gnucash?


              1. Mendel
                21.09.2017 06:37

                Не теоретически, а практически так надо делать, иначе «шоколадка» фининспектору будет расти в геометрической прогрессии.

                Да ну, какая шоколадка инспектору там где вообще не было учета и все было красиво? Это у вас профдеформация взрослым учетом.
                СПД/ИП лепят что попало и никого ничего не волнует, лишь бы кассовый аппарат/безнал сошелся с отчетом (утрирую, есть наемные работники и т.п. но правильные счета возвратов это точно ерунда). А в цивилизованных странах у микробизнесов вообще все настолько пофиг что даже иногда шокирует.
                Помню дядя мой рассказывал лет десять назад — у него гринкарта. Он художник. Оформлен чем-то вроде наших СПД/ИП. Говорит инспектор его ругала что он не записывает себе в тетрадку сколько топлива у него уходит на поездки на натуры, и ворчит что надо записывать «на глаз». Ну и пару недель назад жена моя спрашивала у одной израилитянки которая занимается схожим с ней бизнесом, как у той с налоговой организованы отношения. Ответ той был еще веселее — «а зачем оно? Я еще не доросла до того объема когда надо что-то писать или платить». Не помню лимит который она назвала, но вроде речь шла о суммах в месяц в размере чуть больше их минимальной з/п.
                Сальдо — это просто показатель текущего среза. Показатели оборота очень важны, а Вы хотите это отрезать.

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


                1. relia
                  21.09.2017 09:46

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

                  Ну, по моим наблюдениям, у вас в последнее время делают все что угодно, только не облегчают жизнь микробизнесам. Поэтому необходимо быть готовым к закручиванию гаек.
                  То, что я за 4+ года налоговую ни разу не посетил и ни одного отчета вне общего графика не сдал не говорит ровно ни о чем — трахнут какого-то моего контрагента и пойдут трусить его контрагентов встречными проверками и штрафовать/сажать/штрафовать. А еще с вашими перекладками ответственности за определение добросовестности контрагентов на плечи бизнеса так вообще все весело становится…

                  Профдеформация у меня если и есть, то от другого, но в целом Вы правы — если информацию можно структурировать и сохранить, то ее обязательно надо структурировать и сохранить.

                  Ну так обороты то как раз становятся более правильными.

                  Примитивно != правильно.
                  Почему правильное чуть-чуть сложнее, написал ранее. Здесь Вы хотите облегчить свою работу, не особенно думая о завтрашнем дне. Имеете право — это Ваш продукт, — но истина дороже :)
                  Налог с продаж (американский вы имеете ввиду?) да, плясать будет плохо, хотя как я помню там он на бизнес бизнесу не распространяется.

                  В РФ его отменили в начале нулевых. В РФ его регулярно обсуждают с конца 2016. В РФ его скорее всего введут в 2019.
                  По каким правилам это будет работать в РФ — мне не известно. Но вряд ли по американским. :)
                  И опять же: разные операции подчинять одним и тем же правилам физического документооборота намного проще, чем «свалку» разделять по разным правилам на основании каких-то косвенных признаков.
                  А остальное можно учесть и так. Проводки то видны.

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


                  1. Mendel
                    21.09.2017 12:02

                    В РФ его отменили в начале нулевых. В РФ его регулярно обсуждают с конца 2016. В РФ его скорее всего введут в 2019.
                    По каким правилам это будет работать в РФ — мне не известно. Но вряд ли по американским. :)

                    Вот как раз на РФ-учет мне больше всего плевать ибо РФ-клиенты у меня идут по остаточному принципу — если подошло что-то под них, то хорошо, если нет то и нет, но США интересует а у них он есть.
                    И опять же: разные операции подчинять одним и тем же правилам физического документооборота намного проще, чем «свалку» разделять по разным правилам на основании каких-то косвенных признаков.

                    Так признак то не косвенный а очень даже прямой — сторно или не сторно.
                    Тут вопрос в основном стоит в том, что сторно ассоциируется с ошибками, и соответственно будет плохим стилем использовать его в другом смысле.
                    По своему опыту лоточной торговли в первой половине 90-ых знаю, что более-менее понимать чем выгодней торговать можно только при 2..3 поставщиках и не более 5...10 видов товаров. Дальше уже только нормальный учет или чутье (которое чаще подводит, чем нет).

                    Нет, ну вы прям обижаете. АВС-анализ и все такое, плюс минимальное бюджетирование (группировка субсчетов в привязке к разным направлениям чтобы видеть не только консолидированный баланс/обороты но и по направлениям) и т.д, в общем все стандартные виды анализов я конечно подразумеваю. Без этого никуда. Иначе тупо смысла нет. «Учет на регистрах» каждая бабушка торгующая семечками имеет. Даже с учетом того что версия «Киоск» и ядро пойдут под MIT, все равно — без внятного анализа оно никому не нужно.


            1. mayorovp
              20.09.2017 19:43

              В интерфейсе писать «возврат покупки», в базу писать как продажу или даже правильно?


              1. Mendel
                21.09.2017 06:40

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

                А правильно это как?)
                Продажу я точно делать не буду. Одно дело делать такое в большом учете для налоговой, другое — в управленческом для микро. А правильно… Вот как бы Вы сделали правильно?


    1. fukkit
      20.09.2017 13:37

      Если не планируете выходить на баланс, вам не нужна двойная запись, и можно вести учёт интересующих сущностей на регистрах. Накопительный регистр неплохо реализован в 1С, если не видели — гляньте. Аналог пишется +- за месяц, дальнейшая учетная жизнь сильно упрощается.
      P.S. Логотип поделки обязательно должен быть квадратным.


      1. Mendel
        20.09.2017 17:57

        Если не планируете выходить на баланс, вам не нужна двойная запись

        Я не могу не планировать «выходить на баланс». И даже дело не в том что одна из целей проекта это красивая демонстрация для второй части статьи про двойную запись. Учет без двойной записи — ущербен по своей природе. Даже если это шесть счетов всего.
        Аналог пишется +- за месяц

        Может мы о разных регистрах говорим и там есть какой-то специфический функционал которого я не знаю (в свое время прошел всю пачку их курсов на специалиста или профессионала как оно там у них, но сертификат получать не стал ибо 1с мне не очень интересен). Но насколько я помню там буквально пару фич у накопительного было. Сальдо на дату, дебетовый оборот за период, кредитовый оборот за период и вроде всё… День работы с отладкой, документацией и расписыванием АПИ. Нет, там еще вкусно то что можно их использовать в языке запросов (и соответственно построителю), но это уже не к регистру а к ядру запросов и построителю вопрос.
        Собственно накопительный регистр это тот же регистр сведений (т.е. запрос вытягивания последней записи у которой дата меньше или равна), только с предвычислениями сальдо и оборотов. Ну или я что-то забыл. Книжки на полке стоят, скоро буду перечитывать, может и вправду забыл, но какой-то магии сравнимой с тем что может дать двойная запись я там не помню.


        1. fukkit
          21.09.2017 10:53
          +1

          Учет без двойной записи — ущербен по своей природе.

          Не каждый хозяйственный учёт — бухгалтерский. Зачастую двойная запись порождает больше проблем, чем решает. Потому вполне приемлемо, например, сложную/специфическую аналитику вести в отдельных системах на регистрах (фактически, журналах с плюшками разной степени автоматичности агрегации), а сводные данные, сгруппированные по видам проводок, сливать в бухгалтерскую систему.

          Абстракция и API накопительного регистра весьма просты, некоторые сложности начинаются в оптимизациях структуры БД и методов обработки данных на относительно больших базах (10-20 Гб). Не всё красиво ложится на SQL с оптимальным планом. Очень повезёт, если за оптимистично оцененный мною месяц всплывут основные архитектурные ошибки.

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


          1. Mendel
            21.09.2017 14:05

            Зачастую двойная запись порождает больше проблем, чем решает.

            Пример можно?
            Я в общем и в целом не то чтобы прям против
            сложную/специфическую аналитику вести в отдельных системах на регистрах (фактически, журналах с плюшками разной степени автоматичности агрегации), а сводные данные, сгруппированные по видам проводок, сливать в бухгалтерскую систему

            Но хотелось бы реальных примеров.
            Абстракция и API накопительного регистра весьма просты, некоторые сложности начинаются в оптимизациях структуры БД и методов обработки данных на относительно больших базах (10-20 Гб). Не всё красиво ложится на SQL с оптимальным планом. Очень повезёт, если за оптимистично оцененный мною месяц всплывут основные архитектурные ошибки.

            Если речь идет о сугубо названном мною АПИ, то я вот просто не представляю что тут вообще делать, не то что «где могут быть узкие места». От объема базы зависит время проведения операций «задним числом», да и то не от размера базы а от того насколько это число будет задним. все остальное вообще О(1) будет (на самом деле конечно O(log(n)) но сиуации когда индексы влияют на производительность это уже проблемы DBA а не разработчика).
            Единственное узкое место для «взрослого» объема на накопительном регистре это переполнение оборотов накапливаемых за всю жизнь. Особенно если взять 32-бит знаковое целое и в него засунуть денежку с шестью знаками после запятой и ничего не делать, а просто ждать когда же к нам придет переполнение. Но даже в таком случае может и не придти.
            В реальности берем Unsigned BIGINT, ну и как минимум чтение оборотов организуем через вьюв, запись процедурой (если клиент может оказаться 32-бит) и об этой проблеме тоже забываем.
            Я могу ошибаться. Я могу сильно ошибаться. Но очень бы хотелось не просто «вы не понимаете», а хоть каких-то намеков в чем тут можно ошибиться…


    1. Portnov
      20.09.2017 17:56

      Если это чисто «управленческий учёт», не связанный с бухгалтерией и отчётностью перед ЦБ/налоговой/whatever, то делайте как вам удобнее.
      Если вы хотите по данным этого учёта непосредственно формировать отчёты органам, то открывайте учебник по бухучёту и делайте как там написано.
      Если у вас сейчас нет бухгалтерии, а в будущем планируется, то разрабатывайте систему так, чтобы к вашему учёту рядом сбоку можно было прикрутить параллельно работающий правильный бухучёт. Чтобы можно было в одном окне видеть ваш простой и удобный учёт из трёх счетов, а в соседнем — правильный учёт из 100. Вариант: продумать выгрузку данных из вашей системы во взрослую бухгалтерскую систему, и на уровне интеграции заставить бухгалтерскую систему делать правильный учёт. Но не надо надеяться, что это само получится, надо продумывать состав информации, который у вас хранится, с тем чтобы бухгалтерской системе потом было достаточно информации для правильного учёта.


      1. Mendel
        20.09.2017 18:05

        Но не надо надеяться, что это само получится, надо продумывать состав информации, который у вас хранится, с тем чтобы бухгалтерской системе потом было достаточно информации для правильного учёта.

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