Здравствуйте, ·, Вы писали:
·>Да, в спеках UK-налоговой там шесть видов округлений: до пенсов, до фунтов * вверх, вниз, банковское.
·>Вопрос — возможно ли реализовать эти округления используя тип double?
Любое округление до доли размером P делается в случае идеально точных расчётов по формуле P*ROUND(val/P), где val — исходное значение, ROUND — конкретная функция округления до целого.
Как называется эта функция — зависит от языка.
В случае C: round() округляет до ближайшего, а половинки — в направлении от нуля; nearbyint() — в соответствии с текущим направлением (по умолчанию это к ближайшему и банкирское); rint() — то же, что nearbyint(), но поднимая inexact (для данной задачи явно не нужно); floor(), ceil() — любые с ненулевой целой частью. trunc() аналогично floor() для положительных и ceil() для отрицательных, отдельно не вспоминаем. Также nearbyint() при FE_DOWNWARD аналогично floor(), а при FE_UPWARD — ceil().
Пример: входное значение и результат округления; NB/RNE — nearbyint() при округлении к ближайшему (умолчание FE_TONEAREST для IEEE).
Если сумма в double в фунтах, то соответственно получается (но см. уточнение ниже):
для фунтов — ROUND(val); для пенсов — ROUND(val*100)/100.
Вверх — ROUND(x) = ceil(x); вниз — ROUND(x) = floor(x). Банковское — только NB/RNE.
Тут, однако, есть одна тонкость. Представим себе, что в результате некоторой операции у нас получилась сумма в фунтах вида 12.0299999999999999. Она должна восприниматься как 12.03 перед всеми воздействиями! Оба округления вверх и вниз до центов должны дать 12.03, хотя внутри это снова может оказаться 12.0299999999999999 или 12.03000000000002. Ещё хуже, если мы попытаемся округлять какое-нибудь 11.99999999999999 вниз до целых фунтов, получив 11, когда на самом деле нужно 12.
Поэтому: обычно говорят, что многократное округление — это очень вредно, приводя пример ситуацйй вида "1.453 сначала округлили до сотых до 1.45, а потом до десятых банкирским до 1.4; сразу до десятых это дало бы более корректное 1.5"; аналогично при правиле типа "0.5 округляется вверх" 1.47->1.5->2 вместо 1.47->1". Но в случае финансов, наоборот, необходимо двойное округление! (Но не более) 11.99999999999999 до фунтов — сначала должно быть округлено до центов — до 12.00, а потом уже смотреть, куда его и как округлять; иначе округление вниз до целых фунтов даст 11, а не правильное 12. А так как округление до центов это nearbyint(x*100)/100, то может оказаться, что лучше провести в целых всю цепочку округления, а не одну операцию (а то и вообще перейти на fixed).
Здравствуйте, netch80, Вы писали:
n> Поэтому: обычно говорят, что многократное округление — это очень вредно, приводя пример ситуацйй вида "1.453 сначала округлили до сотых до 1.45, а потом до десятых банкирским до 1.4; сразу до десятых это дало бы более корректное 1.5"; аналогично при правиле типа "0.5 округляется вверх" 1.47->1.5->2 вместо 1.47->1". Но в случае финансов, наоборот, правильно двойное округление! 11.99999999999999 до фунтов — сначала должно быть округлено до центов — до 12.00, а потом уже смотреть, куда его и как округлять; иначе округление вниз до целых фунтов даст 11, а не правильное 12. А так как округление до центов это nearbyint(x*100)/100, то может оказаться, что лучше провести в целых всю цепочку округления, а не одну операцию (а то и вообще перейти на fixed).
А почему первое округление именно до пенсов? По-моему логичнее на знак меньше, т.е. до 0.1 фунта.
Т.е. я правильно понял, что технически невозможно считать всё в double с точным соблюдением финансовых правил? Без fixed не обойтись никак?
Здравствуйте, pagid, Вы писали:
p> ·>Тогда он должен обсуждать с клиентами как они думают должно вычисляться чтобы они были довольны. p> ТС с нам обсуждал вопрос реализации не касающийся пользователей — какое внутреннее представление в его случае будет оптимальным/разумным. p> С вопросом в какой момент и каким способом нужно округлять результаты расчетов обращаться конечно же нужно к клиенту.
Мне кажется, что перечисленные варианты будут выдавать разные результаты, как раз из-за округлений, вызванных потерями точности. Поэтому тут не в оптимальности вопрос, а в корректности результатов. Хотя, конечно, если клиентов устроят любые более-менее правдоподобные результаты, то я бы считал в double — проще и быстрее. Но где найти таких клиентов...
Здравствуйте, ·, Вы писали:
·>Т.е. я правильно понял, что технически невозможно считать всё в double с точным соблюдением финансовых правил? Без fixed не обойтись никак?
Считать можно, перед выводом на экран/на печать/сохранением в БД, если округление специфическое танцы с бубном могут потребоваться.
Здравствуйте, ·, Вы писали:
·>Мне кажется, что перечисленные варианты будут выдавать разные результаты, как раз из-за округлений, вызванных потерями точности.
Не должны. Это его задача, и представление чисел выбирается в том числе с этой целью.
·> Поэтому тут не в оптимальности вопрос, а в корректности результатов. Хотя, конечно, если клиентов устроят любые более-менее правдоподобные результаты, то я бы считал в double — проще и быстрее. Но где найти таких клиентов...
Неплохо занаком с учетно-бухгалтерской системой вполне успешно существующей уже лет 20, внутреннее представление денег — double, пользователей системы, как конечных, так и пишущих на прилагаемом скриптовом языке это ничуть не волнует и источником проблем не является.
Здравствуйте, ·, Вы писали:
n>> Поэтому: обычно говорят, что многократное округление — это очень вредно, приводя пример ситуацйй вида "1.453 сначала округлили до сотых до 1.45, а потом до десятых банкирским до 1.4; сразу до десятых это дало бы более корректное 1.5"; аналогично при правиле типа "0.5 округляется вверх" 1.47->1.5->2 вместо 1.47->1". Но в случае финансов, наоборот, правильно двойное округление! 11.99999999999999 до фунтов — сначала должно быть округлено до центов — до 12.00, а потом уже смотреть, куда его и как округлять; иначе округление вниз до целых фунтов даст 11, а не правильное 12. А так как округление до центов это nearbyint(x*100)/100, то может оказаться, что лучше провести в целых всю цепочку округления, а не одну операцию (а то и вообще перейти на fixed). ·>А почему первое округление именно до пенсов? По-моему логичнее на знак меньше, т.е. до 0.1 фунта.
Тогда пусть у тебя 11 фунтов 97 пенсов, а надо округлить вниз до целых фунтов, а число в double равен 11.969999999999999999995. Если по-моему — округляем до пенсов — получаем 1197 пенсов, и затем от этого идём вниз до 11 фунтов. Если же ты округлишь до 0.1 фунта, оно сразу поднимется до 12.00. И затем до фунтов останешься на 12, а не 11.
·>Т.е. я правильно понял, что технически невозможно считать всё в double с точным соблюдением финансовых правил? Без fixed не обойтись никак?
Я не говорил — невозможно, я говорил — может быть лучше перейти на fixed.
Все эти trunc, round, floor, ceil, nearbyint выдают результат того же типа (float, double), но точно равный целому (предположим, что потери точности целого уже не происходит). Но, умножая или деля обратно для приведения в исходный масштаб, ты снова вносишь особенности двоичного представления (оно там будет "в душе" каким-нибудь 11.9700000000000000003).
Все рабочие значения у тебя (или ТС) в таком варианте — точки равномерной сетки с шагом, например, 0.01 (если это пенсы при фунтах), но из-за двоичного представления она каждая чуть смещена относительно точного, не представимого тут, значения. При "вытаскивании" в целые — например, при перемасштабировании в шаг 1 умножением на 100 — надо привести округлением к ближайшему к точному значению узла сетки с шагом 1. Это всё, что тут однозначно требуется, остальное уже зависит от дальнейших действий. А если этот по-сути-fixed представляется в double — с этим вполне можно работать (тем более, что при целых числах прежних привычных ошибок в мелких разрядах уже нет).
Здравствуйте, кт, Вы писали:
кт>Как раз я ей не занимаюсь и не пытаюсь использовать для финансовых расчетов мантиссу и порядок.
Тогда чем не устраивает обычный integer? Можно условится, что 1 это одна копейка, сотая или, если угодно, миллионная доля копейки и считать в фантастически быстром integer без всяких мантисс и порядков.
кт>А пример с 1/3 непоказателен. В большинстве случаев в финансах нужны не рациональные дроби, а проценты (и особенно сложные проценты). Как раз они прекрасно представляются в десятичном виде.
Очень даже показателен, в финансовых расчетах. 18% "НДС включая" с 10 руб. уже периодическая дробь, и подобное там спошь и рядом. Сумму авансовых платежей по налогу нужно разделить равными долями на три месяца квартала....
кт> Автор старой статьи пытался донести для чего в IA-32 введена двочная арифметика, Float-арифметика и BCD-арифметика.
Про этот калькуляторный рудимент и атавизм netch80 уже написал.
кт>P.S. в статье объяснялась, чем не устраивает Decimal С#
Плохо там объясняется, совершенно без аргументации.
Да, decimal — не BCD, но в пределах своего диапазона значений, более чем достаточного для финансовых расчетов, decimal позволяет точно представить все значения точно представляемые в десятичной системе.
... << RSDN@Home 1.2.0 alpha 5 rev. 1495>>
Re[9]: предлагаю ознакомиться со статьей на соседней ветке
Здравствуйте, pagid, Вы писали:
P>Тогда чем не устраивает обычный integer? Можно условится, что 1 это одна копейка, сотая или, если угодно, миллионная доля копейки и считать в фантастически быстром integer без всяких мантисс и порядков.
опять снова здорово.
Подумайте, а почему 60 лет назад так не делали?
Целые как миллионные доли копейки — это расчет с точностью до миллионный.
Вместо этого был введен аппарат точных расчетов для десятичных дробей, который многими почему-то считается какой-то дикой ерундой. Если это ерунда — раскройте глаза фирме IBM, это они придумывали.
Re[10]: предлагаю ознакомиться со статьей на соседней ветке
Здравствуйте, кт, Вы писали:
P>>Тогда чем не устраивает обычный integer? Можно условится, что 1 это одна копейка, сотая или, если угодно, миллионная доля копейки и считать в фантастически быстром integer без всяких мантисс и порядков. кт>опять снова здорово. кт>Подумайте, а почему 60 лет назад так не делали?
Делали. Именно так и делали. Там, где вы видите какой-нибудь fixed decimal(10,2) в PL/1, "внутре" была целочисленная арифметика и сдвиг порядка там, где это нужно, уже при интерпретации данных. Разница в том, что в тех языках подробности скрывались от прикладников, а с новой волной, пришедшей из системного программирования и основанной в первую очередь на C, потроха оказались вывернуты наружу.
А матфизика никогда не интересовалась десятичными, зато ей была важна максимальная точность и скорость вычислений в мире реальных цифр, которые никогда не точны до конкретной цифры, что тянуло именно в сторону двоичной арифметики.
кт>Целые как миллионные доли копейки — это расчет с точностью до миллионный. кт>Вместо этого был введен аппарат точных расчетов для десятичных дробей, который многими почему-то считается какой-то дикой ерундой. Если это ерунда — раскройте глаза фирме IBM, это они придумывали.
Вы не заметили, что это нововведение IBM — это уже 2000-е годы! До этого decimal floating в железе был очень тяжелоподъёмным и, как следствие, не было стандарта.
Точно так же как исходный IEEE754-1985 вошёл в силу только тогда, когда стало жечь уже невыносимо (а почему так см. подробности бардака на рынке), и несколько фирм (начиная с Intel и Motorola) сделали и отшлифовали аппаратные реализации (передирая особенности друг у друга, начиная с внутреннего 80-битного представления и продолжая абсолютно тупыми решениями вроде переноса denormals на микропрограмму — только сейчас со скрипом избавились от этого, и то наполовину), так и сейчас только стабилизация концепций позволила рискнуть сделать аппаратную реализацию, отработать её и тогда пропихнуть в стандарт.
До этого decimal floating был такой же областью неразберихи, как binary до IEEE754 (и мы видим это на примере дотнетовского decimal). Только в 80-х было порождено первое предложение, которое ещё потом долго и задумчиво шлифовали.
(Судя по всему, про то, что это IBM, Вы прочли у меня. Но проверить хоть чуть-чуть историю и сверить хронологию даже не пытались.)
(И ещё заметьте, что decimal floating в железе пока ни у кого кроме IBM нет, и то — есть только на системах, которые нацелены именно на финансовый рынок — zSeries уходит в основном туда. Остальным не припекло.)
Здравствуйте, FrozenHeart, Вы писали:
FH>Скорее, пример должен быть такой:
FH>
FH>double number = 0.1 + 0.1 + 0.1;
FH>if (numbers == 0.3) { /* do smth */ }
FH>
FH>Вот такие ситуации в случае decimal floating point numbers разве могут когда-то зафейлиться?
1.0/3.0 + 1.0/3.0 + 1.0/3.0 == 1.0 может зафейлиться. Без потери точности мы можем делить только на степени двойки и пятёрки, и то в некоторых пределах.
Здравствуйте, pagid, Вы писали:
Ops>>А еще, например, авансовые платежи в налоговую округляются не до копеек, а до целых рублей. P>И все налоги уже давно округляются до рублей. При этом рекомендуется использовать обычное арифметическое округление.
Там с авансовыми платежами хитрость в том, что ты платишь каждый раз за все предыдущие периоды, вычитая уже уплаченное. Т.е. ошибка округления за год != сумме ошибок всех платежей за год.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, Ops, Вы писали:
Ops>Там с авансовыми платежами хитрость в том, что ты платишь каждый раз за все предыдущие периоды, вычитая уже уплаченное. Т.е. ошибка округления за год != сумме ошибок всех платежей за год.
За год не будет ошибки. А пример только о том, что на три иногда приходится делить.